Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 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
32b76cf
Trim the ClientSession migration section to behavior-relevant changes
maxisbey Jun 15, 2026
a27bca7
Harden ClientSession/dispatcher lifecycle and error handling
maxisbey Jun 15, 2026
9dedb40
Heal 3.11 coverage tracing in the new dispatcher tests
maxisbey Jun 15, 2026
ea52a87
Heal 3.11 coverage tracing at JSONRPCDispatcher.run() exit
maxisbey Jun 15, 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
25 changes: 19 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,22 @@ 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:

- **Callbacks and notifications now run concurrently.** In v1 the receive loop processed one inbound message at a time, so callbacks ran inline and in order. Now each delivery starts in arrival order but runs as its own task. Server-initiated request callbacks (`sampling`, `elicitation`, `roots`) no longer block other traffic, may themselves send requests without deadlocking, and are interrupted if the server sends `notifications/cancelled` (the request is then answered with an error). Notification callbacks (`logging_callback`, `progress_callback`, `message_handler`) may interleave, and a `progress_callback` may run after the request it reports on has returned; there is no built-in bound on concurrent deliveries. Transport-level errors reach `message_handler` the same way, and a `message_handler` that raises is logged rather than fatal to the session. Callbacks that need strict sequencing must coordinate themselves.
- **Timeouts**: a timed-out or abandoned request is now followed by `notifications/cancelled`, so the server stops the handler instead of leaving it running.
- **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