Skip to content

Make custom notifications first-class: typed send, observable drops#3173

Draft
maxisbey wants to merge 1 commit into
mainfrom
custom-notifications
Draft

Make custom notifications first-class: typed send, observable drops#3173
maxisbey wants to merge 1 commit into
mainfrom
custom-notifications

Conversation

@maxisbey

Copy link
Copy Markdown
Contributor

Custom (non-spec) notifications get a typed send surface, and dropping one now leaves a trace in the logs.

Motivation and Context

Feedback from a downstream framework: a server-to-client notification whose method isn't in the negotiated version's core tables only reaches a registered NotificationBinding, and otherwise is dropped at logger.debug. That drop is silent enough that the fix on the framework's side was found by trial and error, and even once you know the channel, send_notification on both sessions is typed to the closed spec unions, so emitting a vendor notification needs a cast() or # type: ignore even though the runtime already sends whatever it's given.

Two changes:

  • ClientSession.send_notification and ServerSession.send_notification accept any mcp_types.Notification subclass, matching the open Request[Any, Any] arm send_request already has in both classes. The runtime path is unchanged; this legalizes the existing behaviour.
  • Dropped-notification logs move from debug to a once-per-method warning that names the remedy: the client when no binding observes the method, the server when a spec notification isn't defined at the negotiated version or a custom method has no registered handler. Repeats of the same method fall back to debug so a chatty stream can't flood the log. An unserved spec notification stays at debug (most servers legitimately ignore notifications/roots/list_changed and friends).

Nothing is teed to message_handler: its contract is the typed spec union, and v1 dropped unknown methods before it too (at WARNING). The one v1 delivery difference is notifications/tasks/status, which was a member of v1's union; the migration guide now says where it went and how to observe it.

How Has This Been Tested?

New unit tests pin the log levels (first drop WARNING, repeat DEBUG, unserved spec method DEBUG) on both sides, plus a wire test for a custom model going through ClientSession.send_notification unchanged. A new interaction test drives the full loop: an MCPServer tool emits vendor notifications through ctx.session.send_notification(..., related_request_id=ctx.request_id) and the declaring ClientExtension's binding receives them in order; that un-defers the previously deferred extensions:client:notification-binding-delivery requirement. Also exercised end-to-end against a running streamable HTTP server at both 2025-11-25 and 2026-07-28.

Breaking Changes

None. The widened annotations only accept more; the log-level change is observability.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Deliberately out of scope, happy to file follow-ups: a catch-all handler for unobserved notifications, a Client(notification_bindings=...) kwarg (a one-off binding on the high-level Client currently needs a ClientExtension, which also advertises its identifier), a notifications() contribution on the server Extension, and outbound era-gating for spec notifications.

AI Disclaimer

Widen ClientSession.send_notification and ServerSession.send_notification
to accept any Notification subclass, mirroring send_request's open Request
arm, so extensions can emit their own methods without casts. Raise
dropped-notification logging from debug to a once-per-method warning that
names the remedy: on the client when no NotificationBinding observes the
method, on the server when a spec notification is not defined at the
negotiated version or a custom method has no registered handler (an
unserved spec notification stays at debug; repeat drops of a method log at
debug so a stream cannot flood the log). Document the message_handler
routing change and the binding channel in the migration guide, fix the
callbacks page's catch-all claim, and cover the vendor-notification round
trip with an interaction test, un-deferring its requirement.
@github-actions

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Preview https://pr-3173.mcp-python-docs.pages.dev
Deployment https://4e2b0446.mcp-python-docs.pages.dev
Commit cad5440
Triggered by @maxisbey
Updated 2026-07-25 22:28:49 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant