Skip to content

serve: dual compatibility with MCP SDK 1.x and 2.x (lifts the <2 pin) - #2311

Closed
NiSHoW wants to merge 3 commits into
Graphify-Labs:v8from
NiSHoW:mcp2-compat
Closed

serve: dual compatibility with MCP SDK 1.x and 2.x (lifts the <2 pin)#2311
NiSHoW wants to merge 3 commits into
Graphify-Labs:v8from
NiSHoW:mcp2-compat

Conversation

@NiSHoW

@NiSHoW NiSHoW commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

0.9.30 pinned mcp>=1,<2 because the mcp 2.0.0 major broke every fresh graphifyy[mcp] install (#2277, #2279, #2291), with "Adapting to the mcp 2.x API is tracked as a follow-up" in the changelog. This PR is that follow-up: graphify/serve.py now works with both SDK majors, and the extras cap is lifted to mcp>=1,<3.

What changed

mcp 2.0 removed the low-level decorator registration API (@server.list_tools() / call_tool / list_resources / read_resource) and changed several models. _build_server now defines the four handlers as plain coroutines and binds them at the end in a version-aware way:

  • mcp 1.x (hasattr(Server, "list_tools")): the existing decorator registration, unchanged behavior;
  • mcp 2.x: the handlers ride the Server(...) constructor as on_list_tools= / on_call_tool= / on_list_resources= / on_read_resource= callbacks with the (ctx, params) -> Result contract, wrapping the same impls in ListToolsResult / CallToolResult / ListResourcesResult / ReadResourceResult (the wrapping the 1.x decorators used to do implicitly).

Model differences handled so one code path serves both majors:

  • Tool.inputSchema is input_schema on 2.x — the constructor accepts the camelCase alias on both majors, but attribute access does not, so the project_path schema injection reads it via getattr fallback;
  • Resource.uri is a plain str on 2.x and rejects AnyUrl instances, so resource URIs are passed as plain strings (1.x coerces them to AnyUrl itself);
  • mcp.types.AnyUrl (dropped as a re-export in 2.0) falls back to pydantic.AnyUrl — same class, and pydantic is an mcp dependency on both majors;
  • on 2.x the server reports graphifyy's own version in serverInfo (the SDK no longer substitutes its own).

Extras: mcp = ["mcp>=1,<3", "starlette>=1.3.1,<2"] (same for all), keeping the starlette cap 0.9.30 introduced. uv.lock re-locked accordingly. Nothing changes for users currently resolving to 1.x.

Verification

  • tests/test_serve.py + tests/test_serve_http.py (153 tests, including the Streamable HTTP transport): passing under both mcp==1.29.0 and mcp==2.0.0 in dedicated venvs.
  • Full stdio handshakes against the installed graphify-mcp under both majors: initializenotifications/initializedtools/list (all 10 tools) → tools/call query_graphresources/list (6 resources) → resources/read.

🤖 Generated with Claude Code

NiSHoW and others added 3 commits July 29, 2026 20:00
mcp 2.0 removed the low-level decorator API (Server.list_tools/call_tool/
list_resources/read_resource) that serve.py is built on, so an install
that resolved the unpinned extra to 2.0.0 crashed at server startup
(first at `from mcp.types import AnyUrl`, then at @server.list_tools()).
Pin mcp>=1,<2 in the `mcp` and `all` extras; import AnyUrl with a
pydantic fallback so that piece is already 2.x-ready. Verified with a
full stdio handshake (initialize / tools/list / tools/call) against the
installed graphify-mcp with mcp 1.29.0. Porting to the 2.x API is a
separate work item.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mcp 2.0 removed the low-level decorator API the server was built on and
changed several models. _build_server now defines the four handlers as
plain coroutines and binds them at the end in a version-aware way: the
@server.list_tools()/... decorators on 1.x, the on_list_tools=/...
constructor callbacks (with explicit ListToolsResult/CallToolResult/
ListResourcesResult/ReadResourceResult wrapping) on 2.x. Model diffs
handled: Tool.inputSchema is input_schema on 2.x (constructor aliases
work on both, attribute access does not); Resource.uri is a plain str
on 2.x and rejects AnyUrl instances, so resource URIs are passed as
strings (1.x coerces them). The 2.x server also reports graphifyy's own
version in serverInfo. Extras relaxed from mcp>=1,<2 to mcp>=1,<3.

Verified: full stdio handshake (initialize / tools/list / tools/call /
resources/list / resources/read) and the tests in test_serve.py +
test_serve_http.py (145 passed) under BOTH mcp 1.29.0 and mcp 2.0.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).


Graphify review — findings

This PR makes the MCP server code in graphify/serve.py compatible with both the 1.x and 2.x versions of the mcp SDK, and lifts the <2 version cap that was previously introduced in 0.9.30. The tool/resource handlers are refactored from decorator-based registration into plain coroutines that are bound to the Server at runtime — via the 1.x @server.list_tools()-style decorators when available, or the 2.x on_* constructor callbacks otherwise. It also adapts for API differences across the two majors: the AnyUrl import (falling back to pydantic), inputSchema/input_schema attribute access, and plain-string vs AnyUrl resource URIs. The dependency specifiers in pyproject.toml (and uv.lock) for the mcp and all extras are updated from mcp>=1,<2 to mcp>=1,<3, and a changelog entry plus code comments are added.

No blocking issues surfaced. 8 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 557 functions depend on the 282 functions this change touches.

Health — this change adds coupling hotspots:

  • worse: read_resource() — 1 callers, 6 callees

Verification — 557 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 427 function(s) in the blast radius were not formally verified this run

· 1 grounded finding(s) anchored inline below.

Comment thread graphify/serve.py
@safishamsi

Copy link
Copy Markdown
Collaborator

Thanks @NiSHoW. Shipped in v0.9.31 (cherry-picked to v8 to preserve authorship) — resolves #2308 and lifts the mcp<2 cap from 0.9.30 to mcp>=1,<3. Verified on the published wheel: a fresh graphifyy[mcp] install resolves mcp 2.0.0 and graphify.serve imports cleanly. Follow-up worth doing: a CI leg pinning mcp>=2 with a real initialize/tools-list handshake. Closed-unmerged here, but it's in the release: https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.31

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.

2 participants