Skip to content

Migrate the MCP driver to SDK v2 with a deliberate v1 compatibility window #263

Description

@dgenio

Summary

Migrate Weaver Kernel's MCP integration to the now-stable MCP Python SDK v2 deliberately, while preserving a bounded v1 compatibility window if doing so remains simple and testable.

Do not solve this by merely widening mcp<2 to mcp<3. v2 changes several exact assumptions in the current driver/support/tests and also implements the 2026-07-28 protocol revision.

Official references:

Current Kernel state

pyproject.toml deliberately pins the optional MCP extra to the v1 line (mcp>=1.17,<2) so ordinary installs cannot accidentally cross the major-version boundary.

PR #264, which simply widened the dependency range, was correctly closed unmerged.

The migration should land after #181 / PR #277 so fail-closed tool classification is the baseline and the v2 port does not reintroduce optimistic safety inference.

Confirmed v2 breakpoints in Kernel

1. McpErrorMCPError

Current drivers/mcp.py dynamically resolves mcp.shared.exceptions.McpError.

v2 renamed it to MCPError (also exported at top-level mcp). The compatibility loader should deliberately accept the correct class for the installed major version and retain Kernel's typed DriverError translation.

2. Protocol model attributes are snake_case

Current mcp_support.py reads v1-style attributes including:

  • readOnlyHint
  • destructiveHint
  • idempotentHint
  • outputSchema
  • isError
  • structuredContent
  • mimeType
  • nextCursor

v2 Python attributes are snake_case while wire JSON remains camelCase. The normalization boundary should support the intentionally supported SDK versions without scattering version checks throughout the driver.

3. Tool-call timeout type changed

Current helper converts seconds into datetime.timedelta before passing read_timeout_seconds.

In v2 ClientSession.call_tool(read_timeout_seconds=...) takes a plain float | None. Passing a timedelta can survive construction and then fail later inside the timeout machinery.

Put version normalization in one helper and test both major versions.

4. Streamable HTTP helper shape changed

The v1 migration example yields (read_stream, write_stream, get_session_id); v2 yields (read_stream, write_stream) and removes the session-id callback.

Kernel currently destructures a two-item result. The supported compatibility wrapper must explicitly normalize the transport result instead of relying on whichever tuple shape happens to be installed.

5. In-memory test helper removed

mcp.shared.memory.create_connected_server_and_client_session is removed in v2.

The v2 testing path is mcp.client.Client(server). Kernel's real in-process interoperability test must use the supported test mechanism for the installed major line rather than importing the removed helper.

6. FastMCPMCPServer

The production driver does not need to construct a server, but tests currently import mcp.server.fastmcp.FastMCP. v2 renamed the high-level server to MCPServer / mcp.server.mcpserver (also exposed through documented server imports).

Test fixtures need a version-aware construction path or separate v1/v2 test lanes.

7. v2 validates protocol results more strictly

list_tools() / call_tool() validate received traffic against the negotiated protocol. Tests should deliberately use valid schemas and treat new validation failures as interoperability findings rather than loosening Kernel normalization blindly.

Recommended compatibility architecture

Keep one Kernel-facing MCP normalization layer in drivers/mcp_support.py (and small focused helpers if needed):

MCP v1 objects ─┐
                ├─> normalized ToolSpec / normalized call result / SessionFactory
MCP v2 objects ─┘
                              ↓
                         MCPDriver

The rest of Kernel should not know which SDK major produced the object.

Version detection

Use installed distribution metadata or feature detection in one internal helper. Do not compare version strings ad hoc in multiple call sites.

Fields

Normalize snake_case first for v2 and known camelCase aliases for the supported v1 line. Tests must prove both shapes map to identical ToolSpec and result semantics.

Error mapping

Resolve MCPError (v2) / McpError (v1) behind one helper. Preserve the distinction between protocol rejection and transport/session failures.

Timeout

Convert Kernel's public read_timeout_seconds: float | None into:

  • timedelta(seconds=x) for v1;
  • float(x) for v2.

Keep the Kernel public contract version-neutral.

Transport tuple

Normalize the transport context-manager output to (read_stream, write_stream) while safely ignoring the v1 session-id callback when present.

CI strategy

Do not replace one blind range with another. Add explicit lanes:

  1. MCP v1 compatibility lane — latest supported 1.x (and/or the declared floor where useful);
  2. MCP v2 stable lane — current supported 2.x;
  3. real/reference-server interoperability (Add an MCP interoperability test matrix against reference servers #173) using the corresponding supported SDK/protocol fixture;
  4. main library CI stays free of network requirements.

Once both lanes are green and documented, widen the package extra to an explicit supported range such as mcp>=<tested-v1-floor>,<3 only if maintaining both majors is low-cost. Otherwise make v2 the next documented integration baseline and provide a clear final v1-compatible Kernel release.

Migration decision rule

Prefer temporary v1+v2 support if the compatibility code stays localized to the normalization/session layer.

Drop v1 in a deliberate breaking release if supporting both requires:

  • duplicated driver implementations;
  • pervasive version branches;
  • weakening v2 protocol validation;
  • materially different security semantics.

The goal is a trustworthy integration, not the widest possible version specifier.

Acceptance criteria

  • Require explicit safety classification for auto-discovered MCP tools #181 / PR fix: fail closed on unclassified MCP tools #277 fail-closed discovery semantics are preserved on both supported SDK majors.
  • MCPError/McpError protocol failures map to the same Kernel DriverError contract.
  • tool annotations/schema/result fields normalize correctly for v1 camelCase and v2 snake_case objects.
  • tool-call read_timeout_seconds uses the correct type on both majors.
  • stdio discovery/invocation passes on supported v1 and v2 lanes.
  • Streamable HTTP session construction handles the supported tuple/lifecycle semantics on each major.
  • the real in-process test uses Client(server) on v2 instead of the removed memory helper.
  • discovery, structured output, is_error/tool error, protocol error and large-output/firewall paths are covered.
  • CI has explicit v1 + v2 compatibility lanes (or a documented deliberate v1 drop with migration notes).
  • Add an MCP interoperability test matrix against reference servers #173 real/reference interoperability is green before broad MCP-native promotion.
  • docs publish the exact supported SDK envelope and protocol assumptions.
  • only after all above passes is the <2 ceiling removed.

Non-goals

  • Do not adopt v2-only features merely because the SDK migration lands.
  • Do not change Kernel's public driver abstraction just to mirror MCP v2's new Client API.
  • Do not weaken fail-closed classification or protocol-result validation for compatibility.
  • Do not reopen the simple dependency-widening approach from chore(deps): update mcp requirement from <2,>=1.6 to >=1.6,<3 #264.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions