-
Notifications
You must be signed in to change notification settings - Fork 3.5k
[v2] Dispatcher/ServerRunner receive-path swap — replaces BaseSession #2710
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
maxisbey
wants to merge
53
commits into
main
Choose a base branch
from
maxisbey/v2-dispatcher-swap
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+5,735
−1,698
Draft
Changes from all commits
Commits
Show all changes
53 commits
Select commit
Hold shift + click to select a range
cfe3ae4
feat: add Dispatcher Protocol and DirectDispatcher
maxisbey f53b056
fix: address coverage gaps and stale RequestSender docstring
maxisbey 2e2b2d7
refactor: rename Dispatcher.call to send_request, replace RequestSend…
maxisbey b5cf756
refactor: rename Outbound.send_request to send_raw_request
maxisbey 163d38f
feat: JSONRPCDispatcher outbound side + parametrized contract tests
maxisbey 4739744
feat: JSONRPCDispatcher receive loop and dispatch (chunk b)
maxisbey 62a555b
feat: JSONRPCDispatcher exception boundary (chunk c)
maxisbey 72913ad
test: JSON-RPC-specific dispatcher tests + coverage to 100%
maxisbey e0ca9bc
ci: run full matrix on PRs targeting any branch
maxisbey afc1789
test: address 3.11/3.14 coverage instrumentation quirks
maxisbey f8f350e
refactor: rename send_request to send_raw_request in JSONRPCDispatcher
maxisbey 689b784
feat: PeerMixin and Peer wrapper
maxisbey 1096712
feat: BaseContext
maxisbey efd7df7
refactor: follow Outbound.send_raw_request rename in PeerMixin/BaseCo…
maxisbey 7d18a7d
feat: Connection, server Context, typed send_request, meta kwarg
maxisbey 551cacb
test: close PR3 coverage gaps to 100%
maxisbey 445f99a
test: move asserts inside async-with for 3.11 coverage instrumentation
maxisbey 63be3e4
docs: drop development-journal language from docstrings/comments
maxisbey 786bc55
refactor: make BaseContext/Context covariant in their type params
maxisbey 958bdd7
feat: ServerRunner skeleton — _on_request, initialize, init-gate
maxisbey fb81056
feat: ServerRunner middleware (two-tier) + _on_notify
maxisbey 954874b
feat: ServerRunner.run() and otel_middleware
maxisbey 87579da
test: ServerRunner coverage to 100% — otel span assertions + connecte…
maxisbey a19735b
test: converge span capture on capfire to fix xdist order-dependence
maxisbey 7123dd9
feat: Server registry stores HandlerEntry; ServerRunner consumes Serv…
maxisbey c46b529
feat: Connection.state + exit_stack; ctx.session_id/headers; Transpor…
maxisbey 47989e7
fix: JSONRPCDispatcher coerces string response/progress IDs to int fo…
maxisbey 7234f0e
feat: DispatchContext.message_metadata passes SessionMessage.metadata…
maxisbey b87060d
feat: DispatchContext.request_id; drop RequestId arg from transport_b…
maxisbey caa7ca9
style: ASCII-only docstrings/comments in v2 dispatcher files
maxisbey 013d406
fix: JSONRPCDispatcher.run enters both streams; ClosedResourceError i…
maxisbey 44574ad
fix: align JSONRPCDispatcher error shapes with existing server
maxisbey e9ee4b4
feat: JSONRPCDispatcher.send_raw_request emits CLIENT span and inject…
maxisbey 130e160
fix: ServerRunner error shapes match existing server (METHOD_NOT_FOUN…
maxisbey 9bdd153
fix: ServerRunner validates spec methods against ClientRequest before…
maxisbey 87e0dbc
feat: ServerRunner builds InitializeResult from InitializationOptions
maxisbey 9d121cf
fix: ServerRunner passes None to notification handlers when params ab…
maxisbey ca0c67b
fix: JSONRPCDispatcher no-builder __init__ overload accepts all kwargs
maxisbey 16fbef6
fix: JSONRPCDispatcher.run cancels in-flight handlers on read-stream EOF
maxisbey 94b3ce9
chore: drop unused Server.capabilities() helper
maxisbey ac5ab57
feat: Server.run drives JSONRPCDispatcher + ServerRunner; ServerSessi…
maxisbey 8cca623
Merge origin/main (Tasks removal #2714) into v2-dispatcher-swap
maxisbey c1c851f
test: rebaseline legacy unit tests for the dispatcher swap; rewrite _…
maxisbey fa20352
fix: notification handlers and progress callbacks cannot crash the co…
maxisbey 07d94ee
fix: late peer-cancel cannot double-respond; mcpserver completion get…
maxisbey 535d621
feat: JSONRPCDispatcher.inline_methods; otel_middleware sets jsonrpc.…
maxisbey 9f63603
fix: InMemoryTransport EOFs the server instead of cancelling; coverag…
maxisbey 50134cb
test: close remaining server-side coverage gaps; ServerSession.check_…
maxisbey 513ccc3
chore: drop write-only _InFlight.cancelled_by_peer; add assertion to …
maxisbey 5b126bf
docs: migration.md entries for the dispatcher swap
maxisbey 67d61ab
fix: address 11 bughunter findings (parity, latent wiring, lifecycle)
maxisbey 6c51892
fix: BaseSession.__aexit__ healing checkpoint for gh-106749 (CI 3.11/…
maxisbey 48e4c23
fix: gh-106749 healing checkpoints at remaining throw sites; anyio>=4…
maxisbey File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -442,7 +442,7 @@ async def handle_set_logging_level(level: str) -> None: | |
| mcp._mcp_server.subscribe_resource()(handle_subscribe) # pyright: ignore[reportPrivateUsage] | ||
| ``` | ||
|
|
||
| In v2, the lowlevel `Server` no longer has decorator methods (handlers are constructor-only), so the equivalent workaround is `_add_request_handler`: | ||
| In v2, the lowlevel `Server` no longer has decorator methods (handlers are constructor-only), so the equivalent workaround is `add_request_handler`: | ||
|
|
||
| **After (v2):** | ||
|
|
||
|
|
@@ -461,11 +461,11 @@ async def handle_subscribe(ctx: ServerRequestContext, params: SubscribeRequestPa | |
| return EmptyResult() | ||
|
|
||
|
|
||
| mcp._lowlevel_server._add_request_handler("logging/setLevel", handle_set_logging_level) # pyright: ignore[reportPrivateUsage] | ||
| mcp._lowlevel_server._add_request_handler("resources/subscribe", handle_subscribe) # pyright: ignore[reportPrivateUsage] | ||
| mcp._lowlevel_server.add_request_handler("logging/setLevel", SetLevelRequestParams, handle_set_logging_level) # pyright: ignore[reportPrivateUsage] | ||
| mcp._lowlevel_server.add_request_handler("resources/subscribe", SubscribeRequestParams, handle_subscribe) # pyright: ignore[reportPrivateUsage] | ||
| ``` | ||
|
|
||
| This is a private API and may change. A public way to register these handlers on `MCPServer` is planned; until then, use this workaround or use the lowlevel `Server` directly. | ||
| `_lowlevel_server` is private and may change. A public way to register these handlers on `MCPServer` is planned; until then, use this workaround or use the lowlevel `Server` directly. | ||
|
|
||
| ### `MCPServer`'s `Context` logging: `message` renamed to `data`, `extra` removed | ||
|
|
||
|
|
@@ -620,6 +620,8 @@ ctx: ClientRequestContext | |
| server_ctx: ServerRequestContext[LifespanContextT, RequestT] | ||
| ``` | ||
|
|
||
| `ServerRequestContext` is now a standalone dataclass — it no longer subclasses `RequestContext[ServerSession]`. It carries the same fields (`session`, `request_id`, `meta`, `lifespan_context`, `request`, `close_sse_stream`, `close_standalone_sse_stream`), so handler code is unaffected, but `isinstance(ctx, RequestContext)` checks and `RequestContext[ServerSession]` annotations need updating to `ServerRequestContext`. | ||
|
|
||
| The high-level `Context` class (injected into `@mcp.tool()` etc.) similarly dropped its `ServerSessionT` parameter: `Context[ServerSessionT, LifespanContextT, RequestT]` → `Context[LifespanContextT, RequestT]`. Both remaining parameters have defaults, so bare `Context` is usually sufficient: | ||
|
|
||
| **Before (v1):** | ||
|
|
@@ -813,6 +815,55 @@ server = Server("my-server", on_list_tools=handle_list_tools) | |
|
|
||
| If you need to check whether a handler is registered, track this yourself — there is currently no public introspection API. | ||
|
|
||
| ### Lowlevel `Server`: `add_request_handler` is now public and takes `params_type` | ||
|
|
||
| The private `_add_request_handler(method, handler)` escape hatch is now the public `add_request_handler(method, params_type, handler)`, alongside a matching `add_notification_handler`. Each takes a `params_type` model that incoming params are validated against before the handler runs. | ||
|
|
||
| ```python | ||
| # Before (v1 / earlier v2 prereleases) | ||
| server._add_request_handler("custom/method", my_handler) | ||
|
|
||
| # After (v2) | ||
| server.add_request_handler("custom/method", MyParams, my_handler) | ||
| server.add_notification_handler("notifications/custom", MyNotifyParams, my_notify_handler) | ||
| ``` | ||
|
|
||
| ### Lowlevel `Server`: private `_handle_*` dispatch methods removed | ||
|
|
||
| `Server._handle_message`, `_handle_request`, and `_handle_notification` have been removed. The receive loop and per-message dispatch now live in `JSONRPCDispatcher` and `ServerRunner`, which `Server.run()` drives internally. | ||
|
|
||
| These were private, but some users subclassed `Server` and overrode them to intercept requests. Use middleware instead: | ||
|
|
||
| ```python | ||
| from typing import Any | ||
|
|
||
| from pydantic import BaseModel | ||
|
|
||
| from mcp.server import Server, ServerRequestContext | ||
| from mcp.server.context import CallNext, HandlerResult | ||
|
|
||
|
|
||
| async def logging_middleware( | ||
| ctx: ServerRequestContext[Any, Any], method: str, params: BaseModel, call_next: CallNext | ||
| ) -> HandlerResult: | ||
| print(f"handling {method}") | ||
| result = await call_next() | ||
| print(f"done {method}") | ||
| return result | ||
|
|
||
|
|
||
| server = Server("my-server", on_call_tool=...) | ||
| server.middleware.append(logging_middleware) | ||
| ``` | ||
|
|
||
| For lower-level interception (raw method/params before validation, including unknown methods), use `DispatchMiddleware` from `mcp.shared.dispatcher`. | ||
|
|
||
| ### Lowlevel `Server.run(raise_exceptions=True)`: transport errors no longer re-raised | ||
|
|
||
| `raise_exceptions=True` now only governs handler exceptions: an exception raised by an `on_*` handler propagates out of `run()` instead of being converted to a JSON-RPC error response. | ||
|
|
||
| Previously it also re-raised exceptions yielded by the transport onto the read stream (e.g. JSON parse errors). Those are now debug-logged and dropped regardless of `raise_exceptions`. If you relied on `run()` exiting on a transport-level parse error, that no longer happens. | ||
|
|
||
| ### Lowlevel `Server`: decorator-based handlers replaced with constructor `on_*` params | ||
|
|
||
| The lowlevel `Server` class no longer uses decorator methods for handler registration. Instead, handlers are passed as `on_*` keyword arguments to the constructor. | ||
|
|
@@ -1039,6 +1090,39 @@ from mcp.server import ServerRequestContext | |
| # but None in notification handlers | ||
| ``` | ||
|
|
||
| ### `ServerSession` is now a thin proxy (no longer a `BaseSession`) | ||
|
|
||
| `ServerSession` no longer subclasses `BaseSession`. It is now a small connection-scoped proxy that exposes `send_request`, `send_notification`, the typed convenience helpers (`create_message`, `elicit_form`, `send_log_message`, `send_tool_list_changed`, ...), `client_params`, and `check_client_capability`. The receive loop, `initialize` handling, and per-request task isolation that previously lived in `ServerSession` have moved to `JSONRPCDispatcher` and `ServerRunner`. | ||
|
|
||
| `ServerSession` is normally constructed for you by `Server.run()` and reached via `ctx.session` in handlers, so most servers are unaffected. If you were constructing or subclassing it directly: | ||
|
|
||
| **Constructor change:** | ||
|
|
||
| ```python | ||
| # Before (v1) | ||
| session = ServerSession(read_stream, write_stream, init_options, stateless=False) | ||
|
|
||
| # After (v2) | ||
| session = ServerSession(dispatcher, connection, stateless=False) | ||
| # where `dispatcher` is a JSONRPCDispatcher and `connection` is a Connection | ||
| ``` | ||
|
|
||
| In practice, replace direct `ServerSession` use with `Server.run(read_stream, write_stream, init_options)` and let the framework wire it up. | ||
|
|
||
| **Removed from `mcp.server.session`:** | ||
|
|
||
| - `InitializationState` enum and `ServerSession._initialization_state` — initialization tracking is now on `Connection` (`connection.initialized` is an `anyio.Event`, `connection.client_params` holds the init params). | ||
| - `ServerRequestResponder` type alias. | ||
| - `ServerSession.incoming_messages` stream — there is no longer a public stream of inbound messages to iterate. Register handlers via the `on_*` constructor params (or `add_request_handler`) and use `Server.middleware` to observe every request. | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. need to make sure middleware works even if a method doesn't exist I think |
||
| - `ServerSession.__aenter__` / `__aexit__` — `ServerSession` is no longer an async context manager. | ||
| - The private `_receive_loop`, `_received_request`, `_received_notification`, and `_handle_incoming` overrides — there is nothing to override on `ServerSession` anymore. To intercept inbound messages, use `Server.middleware` or `DispatchMiddleware` (see the `_handle_*` removal section above). | ||
|
|
||
| ### `BaseSession` / `RequestResponder`: server-side cancellation tracking removed | ||
|
|
||
| `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. | ||
|
|
||
| ### Experimental Tasks support removed | ||
|
|
||
| Tasks (SEP-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 all `Task*` types, the `tasks` capability fields, `Tool.execution`, and the `experimental` properties on `ClientSession`, `ServerSession`, `Server`, and `ServerRequestContext`. | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably wouldn't say workaround here. Instead would say "this is now supported by ___" or something