Skip to content

chore(mcp): adopt MCP spec 2026-07-28 and TypeScript SDK v2 #2189

Description

@zachdunn

MCP spec 2026-07-28 shipped alongside the stable v2 TypeScript SDK (@modelcontextprotocol/server@2.0.0, @modelcontextprotocol/client@2.0.0); the monolithic @modelcontextprotocol/sdk is retired at v1. This tracks adopting it across our MCP surface. Sibling work in buildinternet/sunny: #773, Phase-1 PR #774.

Design doc: docs/superpowers/specs/2026-07-29-mcp-sdk-v2-design.md.

Where we stand

The headline breaking change costs us little. Protocol-level sessions are gone and MCP is stateless — each request carries its protocol version and client capabilities in reserved _meta keys. workers/mcp binds no Durable Objects, so the session state its current transport holds is already per-isolate and best-effort. Nothing durable depends on it. We use none of the deprecated features (Roots, Sampling, Logging, HTTP+SSE transport).

Our migration differs from sunny's in one structural way. Sunny hand-wires the SDK's own transport, so its PR is a direct SDK swap. We serve MCP through agents/mcp (the Cloudflare Agents SDK), which already did this migration upstream:

  • agents@0.20.1 moves the MCP SDK from a hard dependency to peers on both v1 @modelcontextprotocol/sdk@1.30.0 and v2 @modelcontextprotocol/server@2.0.0 + /client@2.0.0.
  • agents/mcp's createMcpHandler is now the stateless v2 handler taking an McpServerFactory. The sessionful SDK-v1 handler we use today (agents@0.17.3) is renamed createLegacyMcpHandler.

So Phase 1 is an agents bump plus a call-site change, not a transport rewrite. It also means the legacy leg sunny had to hand-wire (a second WebStandardStreamableHTTPServerTransport with enableJsonResponse: true, to stop 2025-era clients silently receiving SSE) is a single responseMode: "json" option for us.

Already compatible, verified against the v2 type surface: ResourceTemplate + its complete maps (slug-completion.ts), completable() on prompt args, tool annotations, and _meta — which is what carries the MCP Apps UI ui.resourceUri and the ui.csp resourceDomains allowlist (#1230). resultType is a wire-only discriminator, stripped from the handler-facing types, so tool callbacks are unaffected.

Mechanical churn: ~19 raw-shape inputSchema: { … } sites across mcp-agent.ts, follows-tools.ts, whats-changed-tool.ts become z.object(…), and withPagination() returns an object schema. Raw shapes still work via a deprecated overload, so this is de-risking rather than a blocker.

Side benefit: the zod: ~4.3.6 pin in workers/mcp exists only because SDK v1 nested its own copy (#1367). v2 declares zod ^4.2.0, so the pin should be droppable.

Plan

  • Phase 1 — SDK v2 migration. ✅ Shipped in feat(mcp): adopt MCP 2026-07-28 via the v2 TypeScript SDK #2190 (merged 2026-07-29). Bump agents ^0.17.3 → ^0.20.1, drop the direct @modelcontextprotocol/sdk dep, add @modelcontextprotocol/server@^2.0.0 (+ /client for tests). Switch workers/mcp/src/index.ts to the stateless createMcpHandler(factory) with route, responseMode: "json", and explicit allowedOriginHostnames (the 0.20 wrapper does its own Host/Origin validation and custom domains are not covered by default). Set ttlMs: 3_600_000 / cacheScope: "private" cache hints on tools/list, prompts/list, resources/templates/list — static per deploy but auth-gated. Wrap tool/prompt schemas in z.object. Migrate tests/mcp-test-helpers.ts and the five suites on it to the v2 client; add modern-envelope (_meta + Mcp-Method/Mcp-Name) and legacy-initialize tests driving the real fetch handler. Refresh the carved-out workers/mcp/bun.lock. Update docs/architecture/mcp.md and workers/mcp/server.json.
  • Phase 2 — auth: better-auth 1.7 + CIMD. DCR is deprecated in favor of Client ID Metadata Documents. better-auth implemented CIMD in better-auth/better-auth#9159, shipping in 1.7.0 — so this is gated on our own 1.6.23 → 1.7 upgrade, not on upstream. Bigger lift here than in sunny: we run api-key, passkey, stripe, oauth-provider, and organization plugins across workers/api and web. DCR stays spec-valid through the deprecation window. Own issue.
  • Phase 3 — workers/mcp/ui + hardening. @modelcontextprotocol/ext-apps ^1.7.3 → 1.7.5 and its own SDK copy; regenerate the committed release-feed bundle (separate carved-out build with its own lockfile). The server-side _meta contract is unchanged, so the UI keeps working untouched during Phase 1. Consider Mcp-Method-based rate-limit rules now that the method is a header and needs no body parsing.

Not in scope

subscriptions/listen — that is #346, a real feature request, not a migration item. No feature flag: this is a dependency migration with no runtime toggle worth the permanent registry entry.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions