Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
35a3722
Add courtesy-cancel controls and bound shielded dispatcher writes
maxisbey Jun 11, 2026
0ac7937
Contain notification-handler exceptions in the dispatcher
maxisbey Jun 11, 2026
aaacafc
Add an on_stream_exception observer to the dispatcher
maxisbey Jun 11, 2026
83c4ba7
Collapse the dispatcher constructor overloads with a defaulted TypeVar
maxisbey Jun 11, 2026
12c8ef3
Move ClientSession onto JSONRPCDispatcher and delete BaseSession
maxisbey Jun 11, 2026
ad471be
Cover the remaining client-session branches and document the migration
maxisbey Jun 11, 2026
2d4c5cd
Stop logging an error when the standalone SSE stream closes mid-listen
maxisbey Jun 11, 2026
36a091d
Accept a pre-built dispatcher via a constructor keyword
maxisbey Jun 11, 2026
dbd1693
Restore full coverage after the BaseSession deletion
maxisbey Jun 11, 2026
c4720d3
Tighten dispatcher abandon-path writes and response invariants
maxisbey Jun 11, 2026
c67b8d1
Harden ClientSession enter/exit and fault delivery
maxisbey Jun 11, 2026
33a5482
Pin initialize-abandon suppression and tidy the interaction suite
maxisbey Jun 11, 2026
8786a52
Exercise the standalone SSE teardown window deterministically
maxisbey Jun 11, 2026
0d4762f
Document the dispatcher behavior changes in the migration guide
maxisbey Jun 11, 2026
9d4e02e
Tighten comments and docstrings across the dispatcher swap
maxisbey Jun 12, 2026
07f1b6d
Remove the client-side related_request_id surface
maxisbey Jun 12, 2026
48f2b01
Make ClientRequestContext a concrete class
maxisbey Jun 12, 2026
421e65b
Synthesize request ids in DirectDispatcher
maxisbey Jun 12, 2026
2d33ade
Raise CONNECTION_CLOSED for requests sent after the connection closed
maxisbey Jun 12, 2026
1703d42
Make the late-response-ignored pin falsifiable
maxisbey Jun 12, 2026
1672788
Trim the dispatcher-swap migration notes
maxisbey Jun 12, 2026
47616ac
Close the write stream only after the task-group join
maxisbey Jun 12, 2026
04778b6
Pin client request concurrency in both directions
maxisbey Jun 12, 2026
0a54692
Tidy naming and doc nits from review
maxisbey Jun 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -634,11 +634,9 @@ server = Server("my-server", on_call_tool=handle_call_tool)

The `mcp.shared.context` module has been removed. `RequestContext` is now split into `ClientRequestContext` (in `mcp.client.context`) and `ServerRequestContext` (in `mcp.server.context`).

The `RequestContext` class has been split to separate shared fields from server-specific fields. The shared `RequestContext` now only takes 1 type parameter (the session type) instead of 3.

**`RequestContext` changes:**

- Type parameters reduced from `RequestContext[SessionT, LifespanContextT, RequestT]` to `RequestContext[SessionT]`
- The `RequestContext[SessionT, LifespanContextT, RequestT]` generic no longer exists; use `ClientRequestContext` or `ServerRequestContext[LifespanContextT, RequestT]`
- Server-specific fields (`lifespan_context`, `request`, `close_sse_stream`, `close_standalone_sse_stream`) moved to new `ServerRequestContext` class in `mcp.server.context`

**Before (v1):**
Expand Down Expand Up @@ -1122,9 +1120,9 @@ async def handle_call_tool(ctx: ServerRequestContext, params: CallToolRequestPar
)
```

### `RequestContext`: request-specific fields are now optional
### `ServerRequestContext`: request-specific fields are now optional

The `RequestContext` class now uses optional fields for request-specific data (`request_id`, `meta`, etc.) so it can be used for both request and notification handlers. In notification handlers, these fields are `None`.
`ServerRequestContext` now uses optional fields for request-specific data (`request_id`, `meta`, etc.) so it can be used for both request and notification handlers. In notification handlers, these fields are `None`.

```python
from mcp.server import ServerRequestContext
Expand Down Expand Up @@ -1164,7 +1162,28 @@ In practice, replace direct `ServerSession` use with `Server.run(read_stream, wr

`BaseSession._in_flight` and the `RequestResponder` members that supported it (`cancel()`, the `cancelled` and `in_flight` properties, the `on_complete` constructor argument, and the internal `CancelScope`) have been removed. These existed to let `ServerSession` cancel a handler when a `CancelledNotification` arrived; `ServerSession` no longer drives a receive loop, so they were dead code. Inbound-cancellation handling for the server now lives in `JSONRPCDispatcher`.

`BaseSession` is still used by `ClientSession`, which never relied on these members. `RequestResponder.respond()` is unchanged.
`BaseSession` itself has since been removed entirely; see the next section.

### `ClientSession` now runs on `JSONRPCDispatcher`; `BaseSession` removed

`ClientSession`'s public surface is unchanged — same constructor, typed methods, manual `initialize()`, and async context-manager lifecycle — but `BaseSession`, the v1 receive loop underneath it, is removed with no shim. The engine now lives in `JSONRPCDispatcher` (`mcp.shared.jsonrpc_dispatcher`). To customize client behavior, use the `ClientSession` constructor callbacks, or pass a pre-built dispatcher via the new keyword-only `dispatcher=` constructor argument (e.g. a `DirectDispatcher` for in-process embedding).

Behavior changes:

- **Request ids count from 1** (previously 0); progress tokens, which reuse the id, shift too. Ids are opaque per JSON-RPC — do not assign meaning to them.
- **Timeouts**: the error message is now `Request 'tools/call' timed out`, and a timed-out or abandoned request is followed by `notifications/cancelled` so the server stops the handler instead of leaving it running. Exempt: `initialize`, requests sent with resumption metadata (so they stay resumable), and requests whose initial write never completed (the peer never saw the id).
- **Resumption hints sent from inside a request callback are dropped** (stream-routing metadata takes precedence there), so those requests are cancelled like any other.
- **Server-initiated requests run concurrently.** A slow sampling/elicitation/roots callback no longer blocks other traffic, a callback may itself send requests without deadlocking, and a server's `notifications/cancelled` now interrupts the callback (the request is then answered with an error).
- **Session shutdown now answers in-flight server-initiated requests with `CONNECTION_CLOSED` (-32000)**; v1 left them unanswered. The write is bounded (~1s) so closing stays fast.
- **Notification callbacks are concurrent.** `logging_callback`, `progress_callback`, and `message_handler` deliveries start in arrival order but each runs as its own task: they 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, and there is no built-in bound on concurrent deliveries (v1's inline loop processed one message at a time).
- **Transport-level `Exception` items are delivered to `message_handler` the same way** — as their own task, without blocking the receive loop — and a `message_handler` that raises on one is logged, not fatal to the session.
- **Stray responses are no longer surfaced to `message_handler`.** Responses with an unknown id are ignored (as the spec asks; v1 surfaced a `RuntimeError`), and error responses with a null `id` — a peer reporting a parse error — are dropped with a debug log (v1 surfaced the transport's `ValidationError`).
- **A raising request callback** is answered with `code=0` and the exception text; v1 flattened every callback exception to `INVALID_PARAMS`. For a specific error response, return `ErrorData` (unchanged) or raise `MCPError`. One carve-out: pydantic's `ValidationError` is still answered with `INVALID_PARAMS`, as in v1.
- **`send_request` before entering the context manager** raises `RuntimeError` immediately; v1 wrote to the transport and hung until the timeout. After the connection has closed it raises `MCPError` (`CONNECTION_CLOSED`) instead. `send_notification` before entry still works.
- **`send_notification` no longer takes `related_request_id`, and `send_request` no longer accepts `ServerMessageMetadata`.** No client transport ever serialized these hints; progress and response correlation via `progressToken` and the request id is unaffected.
- **Client callbacks now receive `mcp.client.ClientRequestContext`** (its `request_id` is always populated); the private `mcp.shared._context.RequestContext` generic is deleted. Annotations spelled `RequestContext[ClientSession]` become `ClientRequestContext`.

`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.

### Experimental Tasks support removed

Expand Down
2 changes: 1 addition & 1 deletion src/mcp/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from mcp.client.streamable_http import streamable_http_client
from mcp.server import Server
from mcp.server.mcpserver import MCPServer
from mcp.shared.session import ProgressFnT
from mcp.shared.dispatcher import ProgressFnT
from mcp.types import (
CallToolResult,
CompleteResult,
Expand Down
15 changes: 2 additions & 13 deletions src/mcp/client/context.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
"""Request context for MCP client handlers."""

from mcp.client.session import ClientSession
from mcp.shared._context import RequestContext
from mcp.client.session import ClientRequestContext

ClientRequestContext = RequestContext[ClientSession]
"""Context for handling incoming requests in a client session.

This context is passed to client-side callbacks (sampling, elicitation, list_roots) when the server sends requests
to the client.

Attributes:
request_id: The unique identifier for this request.
meta: Optional metadata associated with the request.
session: The client session handling this request.
"""
__all__ = ["ClientRequestContext"]
Loading
Loading