You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Make custom notifications first-class: typed send, observable drops
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.
Copy file name to clipboardExpand all lines: docs/advanced/low-level-server.md
+25-2Lines changed: 25 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -162,10 +162,33 @@ The constructor covers the methods MCP defines. `add_request_handler` covers eve
162
162
--8<--"docs_src/lowlevel/tutorial006.py"
163
163
```
164
164
165
-
* The first argument is the method string. Notifications have a twin, `add_notification_handler`.
165
+
* The first argument is the method string.
166
166
*`params_type` is the model the incoming `params` are validated against **before** your handler runs, so custom methods *do* get the validation tools don't. Subclass `RequestParams` so the `_meta` field parses like every other method's.
167
167
* The handler returns a `BaseModel`, a `dict`, or `None`. The SDK serialises it into the JSON-RPC result.
168
168
169
+
Notifications have a twin in each direction. Inbound, `add_notification_handler(method, params_type, handler)` registers `async (ctx, params) -> None`. Spec-defined notification methods are validated against the negotiated version's tables before dispatch, and one that does not exist at that version is dropped with a warning. Custom methods skip the version gate; your `params_type` is their validation, and a custom notification nobody registered a handler for is dropped with a warning.
170
+
171
+
Outbound, `ctx.session.send_notification(...)` accepts any `mcp_types.Notification` subclass, not just the spec-defined union, so the notifying half of a vendor protocol is one model away:
Pass `related_request_id` so the notification rides the originating request's stream; the 2026-07-28 wire gives standalone notifications no channel outside `subscriptions/listen`. A python-sdk client observes vendor methods with a `NotificationBinding` (**[Extensions](extensions.md)**) and warns about ones it has no binding for.
191
+
169
192
One honest caveat: the high-level `Client` only has verbs for the methods MCP defines, so there is no `client.reindex()`. A vendor method is for a peer that already knows it exists: a client you also ship, or another service of yours speaking JSON-RPC.
170
193
171
194
One method you cannot claim:
@@ -196,7 +219,7 @@ Each of these is one idea you now have the vocabulary for; each has its own page
196
219
* An exception in a handler is a `-32603` protocol error. A tool error the model can read is a `CallToolResult` with `is_error=True` that **you** return.
197
220
*`_meta` on the result is addressed to the client application, not the model.
198
221
*`Server[T]` is generic in what its lifespan yields; `ctx.lifespan_context` is a typed `T`.
199
-
*`add_request_handler(method, params_type, handler)` serves any method. `initialize` is reserved.
222
+
*`add_request_handler(method, params_type, handler)` serves any method. `initialize` is reserved.`add_notification_handler` and `ctx.session.send_notification` are the notification twins.
200
223
* The capabilities a `Server` advertises are derived from which handlers you registered.
201
224
202
225
`Client(server)` treated both servers identically because they *are* the same protocol, which is the whole point. The next layer down isn't a class at all: it's **[Middleware](middleware.md)**.
Copy file name to clipboardExpand all lines: docs/client/callbacks.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -135,7 +135,7 @@ Two more. Neither declares anything.
135
135
136
136
`logging_callback` receives every `notifications/message` a server sends, as `LoggingMessageNotificationParams` (`level`, `logger`, `data`). Protocol logging is itself deprecated by the 2026-07-28 spec (**[Logging](../handlers/logging.md)** has what to do instead), so this callback exists for the servers that still emit it.
137
137
138
-
`message_handler` is the catch-all: every server notification reaches it (as well as its specific callback), and on a stream-backed transport so does every transport-level `Exception`. The one pattern worth knowing is `if isinstance(message, Exception): raise message`, so a broken connection fails loudly instead of vanishing.
138
+
`message_handler` is the catch-all for spec traffic: every notification the negotiated protocol version defines reaches it (as well as its specific callback), and on a stream-backed transport so does every transport-level `Exception`. Vendor and extension methods are outside that set; they are delivered only to a matching `NotificationBinding` (**[Extensions](../advanced/extensions.md)**), and without one they are dropped with a warning. The one pattern worth knowing is `if isinstance(message, Exception): raise message`, so a broken connection fails loudly instead of vanishing.
Copy file name to clipboardExpand all lines: docs/migration.md
+30Lines changed: 30 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1653,6 +1653,36 @@ Behavior changes:
1653
1653
1654
1654
`mcp.shared.session` is now a compatibility module: `ProgressFnT` is re-exported (its home is `mcp.shared.dispatcher`), and `RequestResponder` remains as a typing-only stub so `MessageHandlerFnT` annotations keep importing. `RequestResponder.respond()` no longer exists, and neither do the cancellation-tracking members (`cancel()`, the `cancelled` and `in_flight` properties, the `on_complete` constructor argument) or `BaseSession._in_flight`; inbound cancellation is handled by `JSONRPCDispatcher`.
1655
1655
1656
+
### Vendor notifications never reach `message_handler`; observe them with a `NotificationBinding`
1657
+
1658
+
In v1 every inbound server notification was validated against the closed `ServerNotification` union. Methods outside the union were dropped with a warning before any callback ran, and `notifications/tasks/status` was a union member, so it did reach `message_handler`. In v2 the notification tables are per protocol version and `TaskStatusNotification` is types-only: a method the negotiated version does not define is delivered only to a matching `NotificationBinding`, and without one it is dropped with a warning naming the method. `message_handler` keeps its typed contract and never sees these.
1659
+
1660
+
Code that watched task status (or any vendor method) through `message_handler` registers a binding instead:
1661
+
1662
+
```python
1663
+
from mcp.client import NotificationBinding
1664
+
from mcp_types import TaskStatusNotificationParams
On the high-level `Client`, bindings are declared by a `ClientExtension`'s `notifications()`; see [Extensions](advanced/extensions.md). The sending direction also loses its `cast`: `send_notification` on both `ClientSession` and `ServerSession` accepts any `mcp_types.Notification` subclass, where v1 typed it to the closed unions.
1685
+
1656
1686
### Experimental Tasks support removed
1657
1687
1658
1688
Tasks ([SEP-1686](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1686)) have been removed from the MCP specification and are no longer part of this SDK. The `mcp.client.experimental`, `mcp.server.experimental`, `mcp.shared.experimental`, and `mcp.server.lowlevel.experimental` modules have been removed, along with the `experimental` properties on `ClientSession`, `ServerSession`, `Server`, and `ServerRequestContext`. The corresponding `Task*` types remain in `mcp_types` as types-only definitions, except the `TaskExecutionMode` alias, whose literal is now inlined on `ToolExecution.task_support`.
Copy file name to clipboardExpand all lines: docs/whats-new.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -201,6 +201,7 @@ At 2026-07-28 the standalone HTTP GET stream and `resources/subscribe` are repla
201
201
***Requests are routable without parsing bodies.** Modern HTTP requests carry `Mcp-Method` (and, for the three tool-ish calls, `Mcp-Name`); a tool input-schema property annotated with `x-mcp-header` is mirrored into an `Mcp-Param-*` header and cross-checked by the server ([SEP-2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243)). Gateways and rate limiters can route on headers alone; the **[Migration Guide](migration.md#servers-validate-mcp-param-headers-against-the-request-body-sep-2243)** has the rules.
202
202
***Results carry cache hints.** List and read results declare `ttlMs` and `cacheScope` ([SEP-2549](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2549)); you set them per method with `cache_hints=`, and `Client` honors them with a built-in response cache. A server that sends no hints (every pre-2026 server) sees identical, uncached traffic. **[Caching hints](client/caching.md)**.
203
203
***Extensions are first class.** Servers and clients declare optional capability bundles under reverse-DNS identifiers ([SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)); the built-in `Apps` extension (MCP Apps) is the reference. **[Extensions](advanced/extensions.md)** and **[MCP Apps](advanced/apps.md)**.
204
+
***Vendor notifications are typed end to end.**`send_notification` on both sessions accepts any `mcp_types.Notification` subclass, and a client observes methods outside the negotiated version's tables with a `NotificationBinding` (on `ClientSession` directly, or declared by a `ClientExtension`); a notification nothing observes is dropped with a warning, not silently. **[Extensions](advanced/extensions.md)**.
204
205
***Error codes got standardized.** A missing resource is `-32602` with the URI in `error.data`, and the new spec-reserved codes appear as `-32020` (header mismatch), `-32021` (missing required capability), and `-32022` (unsupported protocol version). **[Troubleshooting](troubleshooting.md)** is keyed by the exact messages.
205
206
***Authorization got harder to hold wrong.** The client validates the `iss` returned with the authorization code ([RFC 9207](https://datatracker.ietf.org/doc/html/rfc9207); your `callback_handler` now returns an `AuthorizationCodeResult`), sends `application_type` when it registers, and never replays credentials against a different authorization server. New in the enterprise corner: the [SEP-990](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/990) identity-assertion flow. The **[Migration Guide](migration.md)** lists every OAuth change; **[OAuth for clients](client/oauth-clients.md)** and **[Identity assertion](client/identity-assertion.md)** are the pages.
206
207
***Every server is traceable.** OpenTelemetry ships on by default as middleware: every request gets a server span, at no cost until the process configures an exporter. When both ends run the SDK, the client also propagates W3C trace context in `_meta`, so the traces join up. **[OpenTelemetry](run/opentelemetry.md)**.
0 commit comments