Skip to content

fix: support MCP Python SDK v2 in the SDK MCP bridge - #1157

Open
guptaishaan wants to merge 1 commit into
anthropics:mainfrom
guptaishaan:fix-1150
Open

fix: support MCP Python SDK v2 in the SDK MCP bridge#1157
guptaishaan wants to merge 1 commit into
anthropics:mainfrom
guptaishaan:fix-1150

Conversation

@guptaishaan

Copy link
Copy Markdown

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, and
Query._handle_sdk_mcp_request dispatched by indexing server.request_handlers by request
type. 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.mimeType and
result.isError reads were wrong there too.

What changed:

  • create_sdk_mcp_server() keeps the same two handler bodies and registers them with the
    decorators on v1 or add_request_handler on v2. The v2 tools/call wrapper turns handler
    exceptions into an isError result, matching what v1's decorator did.
  • Dispatch moved into one helper, _call_sdk_mcp_handler, which handles both lookup shapes
    and unwraps v1's ServerResult.
  • The bridge now dumps by alias where it used to read camelCase attributes, so tools/list
    entries and image content keep their wire names on both versions.
  • mcp>=1.23.0,<3.0.0.
  • The tests that reached into server.request_handlers go through the same helper, and a new
    test_jsonrpc_bridge_round_trip drives 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.0 the
suite went from 47 failed / 1244 passed to 1292 passed / 6 skipped; with mcp==1.29.0 it
stays green at 1293 passed / 5 skipped. ruff and mypy are clean under both.

Two things I did not fix and want to flag:

  • maxResultSizeChars does not survive on v2. It is carried as an unknown extra field on
    mcp.types.ToolAnnotations, which is extra="allow" on v1 but not on v2, so pydantic
    drops it inside the caller's own ToolAnnotations(...) before the SDK sees it. Recovering
    it needs a change to the public tool() signature, so I left it and made
    test_max_result_size_chars_annotation_flows_to_cli skip when
    ToolAnnotations no longer allows extras.
  • v1's call_tool decorator validated arguments against inputSchema. The v2 registration
    path 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_handlers dispatch as the blockers.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support MCP Python SDK v2 for in-process SDK MCP servers

1 participant