fix: support MCP Python SDK v2 in the SDK MCP bridge - #1157
Open
guptaishaan wants to merge 1 commit into
Open
Conversation
create_sdk_mcp_server() registered its handlers with the v1 decorators @server.list_tools() / @server.call_tool(), and Query._handle_sdk_mcp_request dispatched by indexing server.request_handlers by request type. MCP SDK v2 removed both: handlers are registered by method name and looked up with get_request_handler(). v2 also renamed the result model fields to snake_case, keeping the camelCase wire names as pydantic aliases, so the bridge's reads of tool.inputSchema, item.mimeType and result.isError were wrong there as well. The handler bodies are unchanged. They are now registered with the decorators on v1 and with add_request_handler on v2, and dispatch goes through one helper that knows both lookup shapes and unwraps v1's ServerResult. Where the bridge built wire payloads from camelCase attributes it now dumps by alias, which gives the same output on both versions. The v2 tools/call wrapper turns handler exceptions into an isError result, which is what v1's decorator did. The mcp bound is relaxed to >=1.23.0,<3.0.0. Two gaps remain on v2. maxResultSizeChars is passed as an unknown extra field on mcp.types.ToolAnnotations, which is extra="allow" on v1 but not on v2, so pydantic drops it in the caller's own constructor before the SDK sees it; its test now skips when ToolAnnotations rejects extras. And v1's call_tool decorator validated arguments against inputSchema, which the v2 registration path does not do. Verified with mcp 1.29.0 and mcp 2.0.0 on Python 3.13: full suite, ruff and mypy clean under both.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Fixes #1150
The in-process SDK MCP bridge only spoke MCP Python SDK v1:
create_sdk_mcp_server()registered handlers with the
@server.list_tools()/@server.call_tool()decorators, andQuery._handle_sdk_mcp_requestdispatched by indexingserver.request_handlersby requesttype. v2 removed both, and renamed the result model fields to snake_case with the camelCase
wire names kept as pydantic aliases, so the bridge's
tool.inputSchema,item.mimeTypeandresult.isErrorreads were wrong there too.What changed:
create_sdk_mcp_server()keeps the same two handler bodies and registers them with thedecorators on v1 or
add_request_handleron v2. The v2tools/callwrapper turns handlerexceptions into an
isErrorresult, matching what v1's decorator did._call_sdk_mcp_handler, which handles both lookup shapesand unwraps v1's
ServerResult.entries and image content keep their wire names on both versions.
mcp>=1.23.0,<3.0.0.server.request_handlersgo through the same helper, and a newtest_jsonrpc_bridge_round_tripdrives tools/list and tools/call end to end.Verified on Linux, Python 3.13.14, against two venvs on the same tree. With
mcp==2.0.0thesuite went from 47 failed / 1244 passed to 1292 passed / 6 skipped; with
mcp==1.29.0itstays green at 1293 passed / 5 skipped. ruff and mypy are clean under both.
Two things I did not fix and want to flag:
maxResultSizeCharsdoes not survive on v2. It is carried as an unknown extra field onmcp.types.ToolAnnotations, which isextra="allow"on v1 but not on v2, so pydanticdrops it inside the caller's own
ToolAnnotations(...)before the SDK sees it. Recoveringit needs a change to the public
tool()signature, so I left it and madetest_max_result_size_chars_annotation_flows_to_cliskip whenToolAnnotationsno longer allows extras.call_tooldecorator validated arguments againstinputSchema. The v2 registrationpath has no equivalent, so a malformed call now reaches the tool handler.
Not verified: no live CLI run, so the payload shape was checked against the tests rather than
the CLI parser. Only mcp 1.29.0 and 2.0.0 were tested, on Linux only.
Thanks to @RonShub for the report and for pinpointing both the registration and the
request_handlersdispatch as the blockers.