Skip to content

Migrate opencontractserver/mcp/server.py to the mcp>=2.0.0 API #2217

Description

@JSv4

Background

Dependabot PR #2207 proposed bumping mcp from >=1.28.1 to >=2.0.0. It was not merged — see PR #2216, which instead pinned requirements/base.txt to mcp>=1.28.1,<2 with a comment explaining why. This issue tracks the deferred migration work.

Why the bump was rejected

Two independent blockers, investigated directly against the published packages (not just release notes):

  1. Pip resolution conflict. pydantic-ai-slim[mcp]fastmcp-slim caps mcp<2.0 across its entire published range (verified against both pydantic-ai-slim==1.107.1, the newest version satisfying <2, and fastmcp-slim at both its floor 3.3.0 and latest 3.4.5). An unbounded mcp>=1.28.1 pin was only ever resolving to 1.x by transitive accident; Dependabot's mcp>=2.0.0 directly contradicts that cap and pip's resolver gives up with resolution-too-deep. This half is not fixable from our side — it requires fastmcp-slim to ship mcp 2.x support upstream.

  2. Breaking API rewrite. mcp 2.0's Server class (mcp.server.lowlevel.Server) is "rebuilt around a shared dispatcher engine" and drops decorator-based handler registration (@mcp_server.list_resources(), .read_resource(), .list_tools(), .call_tool(), etc.) entirely. Handlers are now passed as on_list_tools=, on_call_tool=, on_list_resources=, on_list_resource_templates=, on_read_resource= constructor kwargs, with a new handler signature (ctx: ServerRequestContext, params) -> Result returning wrapper objects (types.ListToolsResult, types.ListResourcesResult, types.ReadResourceResult) instead of the bare list[Tool] / list[Resource] this codebase returns today.

    opencontractserver/mcp/server.py uses the old decorator pattern at 10 registration sites across two server factories: create_mcp_server() (module-level, ~lines 757, 764, 795, 797, 1024) and create_scoped_mcp_server() (~lines 1396, 1404, 1410, 1412, 1417). Since mcp_server = create_mcp_server() runs at module import time, this is not a "breaks on first request" issue — it would be an AttributeError at Django/ASGI process startup, taking down the whole MCP subsystem.

    By contrast, the plain imports (mcp.server.Server, mcp.server.sse.SseServerTransport, mcp.server.stdio.stdio_server, mcp.server.streamable_http_manager.StreamableHTTPSessionManager, mcp.types.{Resource,ResourceTemplate,TextContent,Tool}) all resolve fine under 2.0.0 — the import lines alone give a false sense that the bump is safe.

What migration work looks like

  1. Confirm fastmcp-slim (or its replacement in the pydantic-ai-slim[mcp] dependency chain) has shipped mcp 2.x support, or drop that indirection and depend on mcp>=2.0.0 directly if pydantic-ai's own MCP client integration is no longer needed here.
  2. Rewrite all 10 handler-registration sites in opencontractserver/mcp/server.py from decorators to on_*= constructor kwargs.
  3. Update every handler's signature to (ctx: ServerRequestContext, params) -> Result and its return type to the appropriate *Result wrapper class.
  4. Re-verify SseServerTransport, stdio_server, and StreamableHTTPSessionManager construction/usage against the 2.0.0 API (imports are unchanged, but confirm no other behavioral shifts — e.g. the 4 MiB request-body cap mentioned in the 2.0.0 release notes for Streamable HTTP servers).
  5. Full pass over opencontractserver/mcp/tests/ (test_mcp.py, test_mcp_extended.py) plus opencontractserver/tests/test_unified_rate_limiting.py's MCP-adjacent coverage.
  6. Once merged, remove the <2 pin and its explanatory comment in requirements/base.txt.

Pointers

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions