Skip to content

Commit 898d7ab

Browse files
committed
Document the dispatcher behavior changes in the migration guide
1 parent 6d13045 commit 898d7ab

1 file changed

Lines changed: 10 additions & 2 deletions

File tree

docs/migration.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,14 +1170,22 @@ In practice, replace direct `ServerSession` use with `Server.run(read_stream, wr
11701170

11711171
`ClientSession` keeps its public surface — the `(read_stream, write_stream, ...)` constructor, every typed method, manual `initialize()`, and the async context-manager lifecycle — but the v1 receive loop (`BaseSession`) underneath it is gone. A new keyword-only `dispatcher=` constructor argument accepts a pre-built dispatcher instead of the stream pair (for example a `DirectDispatcher` for in-process embedding).
11721172

1173+
Code that imported or subclassed `BaseSession` directly has no shim — the class is removed outright. The receive-loop engine it implemented now lives in `JSONRPCDispatcher` (`mcp.shared.jsonrpc_dispatcher`); to customize client behavior, use the `ClientSession` constructor callbacks, or supply your own engine through the `dispatcher=` keyword.
1174+
11731175
Behavior changes:
11741176

11751177
- **Request ids count from 1** (previously 0). Progress tokens, which reuse the request id, shift the same way. Ids are opaque per JSON-RPC; do not assign meaning to them.
11761178
- **Timeouts**: the error message is now `Request 'tools/call' timed out` (previously `Timed out while waiting for response to CallToolRequest. Waited N seconds.`), and a timed-out or abandoned request is followed by `notifications/cancelled` on the wire, so the server stops the handler instead of leaving it running. The `initialize` request is never cancelled this way, and requests sent with resumption metadata are also exempt so they stay resumable.
1179+
- **No cancellation for requests that never reached the wire.** A timed-out or caller-cancelled request whose initial write never completed is failed locally without `notifications/cancelled` — the peer never saw the id, so there is nothing to cancel.
1180+
- **The resumption exemption applies only when the hints reach the transport.** A request sent from inside a request callback carries stream-routing metadata that takes precedence, so its resumption hints are dropped — and an abandoned one gets the courtesy `notifications/cancelled` like any other request.
11771181
- **Server-initiated requests run concurrently.** Sampling/elicitation/roots callbacks no longer serialize the receive loop: a slow callback does not block other traffic, a callback may itself send requests without deadlocking, and a server's `notifications/cancelled` now actually interrupts the callback (the request is then answered with an error response).
1178-
- **Notification callbacks are concurrent.** `logging_callback` and `message_handler` start in arrival order, but there is no completion-before-response guarantee (matching the TypeScript, C#, and Go SDKs). Callbacks that need strict sequencing must coordinate themselves.
1182+
- **Session shutdown answers in-flight server-initiated requests with `CONNECTION_CLOSED`** (-32000, `Connection closed`) instead of -32002. The write is bounded (about one second), so closing a session stays fast even when the transport has stopped accepting writes.
1183+
- **The `REQUEST_CANCELLED` constant is removed from `mcp.types`.** Its value (-32002) collided with the spec's resource-not-found error code, and the shutdown response above was its only use.
1184+
- **Notification callbacks are concurrent.** `logging_callback`, `progress_callback`, and `message_handler` start in arrival order, but each delivery runs as its own task with no completion-before-response guarantee (matching the TypeScript, C#, and Go SDKs): deliveries may interleave, and a `progress_callback` delivery may finish after the request it reports on has returned. Callbacks that need strict sequencing must coordinate themselves.
1185+
- **Transport-level `Exception` items are delivered concurrently too.** An `Exception` the transport places on the read stream is dispatched to `message_handler` as its own task, like notification callbacks, instead of blocking the receive loop — and a `message_handler` that raises on it is logged, not fatal to the session.
11791186
- **Unknown-id responses are ignored**, as the spec asks. v1 surfaced them to `message_handler` as a `RuntimeError`; nothing is surfaced now.
1180-
- **A raising request callback** is answered with `code=0` and the exception text. v1 flattened every callback exception to `INVALID_PARAMS`. Callbacks that want a specific error response should return `ErrorData` (unchanged) or raise `MCPError`.
1187+
- **Error responses with a null `id`** — the JSON-RPC shape for a peer reporting a parse error — are now dropped with a debug log. v1 surfaced them to `message_handler` as an `MCPError`.
1188+
- **A raising request callback** is answered with `code=0` and the exception text. v1 flattened every callback exception to `INVALID_PARAMS`. Callbacks that want a specific error response should return `ErrorData` (unchanged) or raise `MCPError`. One carve-out: a callback that raises pydantic's `ValidationError` is still answered with `INVALID_PARAMS` (`"Invalid request parameters"`, empty `data`) because the dispatcher cannot distinguish it from inbound-params validation — this conflation is pre-existing v1 behavior, and a revisit is pending.
11811189
- **`send_request` before entering the context manager** raises `RuntimeError` immediately; v1 wrote to the transport and hung until the timeout. `send_notification` before entry still works.
11821190

11831191
`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 — it has been unreachable at runtime since the server-side swap. `RequestResponder.respond()` no longer exists.

0 commit comments

Comments
 (0)