Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1259334
feat(mcp): add SDK v2 handler with v1 compatibility
mattzcarey Jul 7, 2026
12cc57e
docs(mcp): simplify raw Worker example
mattzcarey Jul 7, 2026
fc0655e
refactor(mcp): hoist modern elicitation handler
mattzcarey Jul 7, 2026
0df17a7
refactor(mcp): clarify v2 and legacy handler APIs
mattzcarey Jul 7, 2026
fafba31
chore(mcp): update server SDK to v2 beta.4
mattzcarey Jul 14, 2026
60b79db
feat(mcp): add SDK v2 client compatibility
mattzcarey Jul 14, 2026
f8717be
refactor(mcp): isolate SDK v2 compatibility concerns
mattzcarey Jul 14, 2026
05c90d6
test(mcp): make conformance reporting truthful
mattzcarey Jul 15, 2026
7218a11
fix(mcp): rediscover migrated OAuth issuers
mattzcarey Jul 15, 2026
075c6e2
fix(mcp): validate stateless handler origins
mattzcarey Jul 15, 2026
af4de81
fix(mcp): address SDK v2 review findings
mattzcarey Jul 20, 2026
399630e
feat(mcp): isolate stateless SDK v2 server path
mattzcarey Jul 22, 2026
1bacd4f
fix(mcp): reconcile v2 client recovery
mattzcarey Jul 22, 2026
2a89d28
test(mcp): trim SDK v2 review surface
mattzcarey Jul 27, 2026
b19c1d8
test(mcp): update conformance referee to alpha.10
mattzcarey Jul 27, 2026
7996d72
fix(mcp): make stateless examples runnable
mattzcarey Jul 27, 2026
612ff5c
docs(mcp): preserve callable handler invocation
mattzcarey Jul 27, 2026
8a5527b
refactor(mcp): narrow stateless handler controls
mattzcarey Jul 27, 2026
bf056b5
refactor(mcp): align handler fetch with SDK v2
mattzcarey Jul 27, 2026
60d720f
docs(mcp): prioritize stateless migration
mattzcarey Jul 27, 2026
4995470
docs(examples): fix MCP startup commands
mattzcarey Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .changeset/mcp-sdk-v2-handler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
"agents": minor
"@cloudflare/codemode": patch
---

Add MCP SDK v2 client and server support. `MCPClientConnection` now uses the exact-pinned `@modelcontextprotocol/client@2.0.0-beta.5`. It probes for stateless MCP with `server/discover`, then falls back to the legacy `initialize` handshake on the same connection when needed. The SDK auto-fulfills stateless elicitation `input_required` results through the existing form and URL elicitation handlers while `callTool`, `getPrompt`, and `readResource` remain pending. OAuth reauthorization discards redirect-scoped discovery after token issuance and preserves discovery-triggered authentication, allowing a changed authorization server to be rediscovered and registered without reusing the prior issuer's credentials. Legacy pushed elicitation, Streamable HTTP, SSE, RPC, OAuth, and hibernation recovery remain supported. Codemode's MCP connector now uses an SDK-neutral structural boundary compatible with both MCP client generations.

Add MCP SDK v2 support to `createMcpHandler`. Pass a factory returning `McpServer` or `Server` from the exact-pinned `@modelcontextprotocol/server@2.0.0-beta.5` peer dependency to serve stateless MCP with legacy compatibility by default. The new `agents/mcp/server` entry exports the stateless Agents handler without retaining `McpAgent`, `WorkerTransport`, MCP client transports, PartyServer, or SDK v1 modules. The returned handler remains callable for Worker dispatch and exposes the lower-level SDK `fetch(request, options?)` method plus typed `notify` methods; upstream `close` and event-bus internals are not part of the Agents surface. The retained v1 server APIs use the exact-pinned `@modelcontextprotocol/sdk@1.29.0` peer dependency.

The legacy compatibility fallback now uses SDK v2's web-standard transport, including fail-fast handling for unsupported server-to-client requests, active-request teardown, and the same 25-second Cloudflare SSE keepalive previously supplied by `WorkerTransport`. It returns `405` for session-only GET and DELETE requests without constructing an application server. `createLegacyMcpHandler` remains an explicit public API for SDK v1 servers and complete WorkerTransport options.

The MCP client storage codec now preserves stateless discovery data with resumed HTTP sessions and preserves the binding name and props required to restore RPC servers. Stored HTTP session IDs from older Agents versions have no associated protocol version. The upgraded client discards those IDs and reconnects instead of sending an unsafe resumed request, so in-flight work tied to an old remote session does not resume.

The v2 callable handler maps verified provider-issued metadata from compatible `@cloudflare/workers-oauth-provider` releases to standard MCP `AuthInfo` while preserving `getMcpAuthContext().props`.

The Workers handler rejects malformed, opaque, and non-HTTP browser Origins. Its default allowlist includes localhost-class Origins, the endpoint's `workers.dev` hostname, and a concrete `corsOptions.origin` hostname. It applies matching Host checks to localhost and `workers.dev` endpoints. Custom-domain deployments with wildcard CORS can set `allowedHostnames` and `allowedOriginHostnames` explicitly, or set `allowedOriginHostnames: "*"` when trusted upstream middleware already enforces the required Origin policy. Requests without Origin remain valid for non-browser MCP clients. Default CORS preflights allow the stateless `Mcp-Method` and `Mcp-Name` request headers.

`@cloudflare/codemode` is now an optional peer. Applications that import `agents/skills` or `agents/browser` install Codemode explicitly; MCP-only applications no longer install it transitively.

Deprecations in this release:

- `McpAgent` is deprecated and feature-frozen as a stateful SDK v1 path. New servers should use an SDK v2 factory with `createMcpHandler` from `agents/mcp/server`.
- Passing an SDK v1 server to the overloaded `createMcpHandler` is deprecated for removal in the next major release. Move the server to an SDK v2 factory. Use `createLegacyMcpHandler` only to temporarily retain sessionful SDK v1 behavior while migrating.
- The explicit result-schema overloads `MCPClientManager.callTool(params, resultSchema, options)` and `withX402Client(...).callTool(confirm, params, resultSchema, options)` are deprecated. Use `callTool(params, options)` or `callTool(confirm, params, options)` instead.

`experimental_createMcpHandler` was already deprecated and remains scheduled for removal in the next major release. Its warning now directs users to an SDK v2 factory first and names `createLegacyMcpHandler` only as a temporary bridge for sessionful SDK v1 behavior.
24 changes: 22 additions & 2 deletions .github/workflows/conformance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,34 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/install
- run: pnpm --filter agents run test:conformance:client
- run: pnpm --filter agents run test:conformance:client:stateless

client-compatibility:
timeout-minutes: 15
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
variant:
- 2025-11-25
- 2025-06-18
- 2025-03-26
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/install
- run: pnpm --filter agents run test:conformance:client:${{ matrix.variant }}

server-conformance:
timeout-minutes: 15
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
variant: [mcp-agent, handler]
variant:
- mcp-agent
- handler
- handler:legacy-compat
- handler:legacy
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/install
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ The [`examples/`](examples) directory has 30+ self-contained demos. A non-exhaus

- **Showcase** — [`playground/`](examples/playground) is the kitchen-sink app: state, callable methods, scheduling, chat, tools, MCP, workflows, email, voice — all in one UI
- **Chat & assistants** — [`assistant/`](examples/assistant), [`agents-as-tools/`](examples/agents-as-tools), [`agent-skills/`](examples/agent-skills), [`workspace-chat/`](examples/workspace-chat), [`resumable-stream-chat/`](examples/resumable-stream-chat), [`structured-input/`](examples/structured-input), [`dynamic-tools/`](examples/dynamic-tools), [`multi-ai-chat/`](examples/multi-ai-chat), [`context-overflow-recovery/`](examples/context-overflow-recovery)
- **MCP** — [`mcp/`](examples/mcp), [`mcp-client/`](examples/mcp-client), [`mcp-server/`](examples/mcp-server), [`mcp-worker/`](examples/mcp-worker), [`mcp-worker-authenticated/`](examples/mcp-worker-authenticated), [`mcp-elicitation/`](examples/mcp-elicitation), [`mcp-rpc-transport/`](examples/mcp-rpc-transport), [`webmcp/`](examples/webmcp)
- **MCP** — [`mcp/`](examples/mcp), [`mcp-client/`](examples/mcp-client), [`mcp-server/`](examples/mcp-server), [`mcp-worker/`](examples/mcp-worker), [`mcp-worker-authenticated/`](examples/mcp-worker-authenticated), [`mcp-elicitation-mrtr/`](examples/mcp-elicitation-mrtr), [`mcp-elicitation/`](examples/mcp-elicitation), [`mcp-rpc-transport/`](examples/mcp-rpc-transport), [`webmcp/`](examples/webmcp)
- **Code Mode & sandboxes** — [`codemode/`](examples/codemode), [`codemode-mcp/`](examples/codemode-mcp), [`codemode-mcp-openapi/`](examples/codemode-mcp-openapi), [`dynamic-workers/`](examples/dynamic-workers), [`dynamic-workers-playground/`](examples/dynamic-workers-playground), [`worker-bundler-playground/`](examples/worker-bundler-playground)
- **Voice** — [`voice-agent/`](examples/voice-agent) is the unified voice pipeline example with Workers AI, AssemblyAI, Telnyx, and ElevenLabs STT; [`voice-input/`](examples/voice-input) covers dictation; [`telnyx-voice-agent/`](examples/telnyx-voice-agent) covers phone transport; [`elevenlabs-starter/`](examples/elevenlabs-starter) covers broader ElevenLabs media APIs
- **Workflows & approvals** — [`workflows/`](examples/workflows), [`a2a/`](examples/a2a)
Expand Down
1 change: 1 addition & 0 deletions design/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Keep it concise. A few paragraphs is fine. These are records, not essays.
| `rfc-workers-ai-gateway-merge.md` | RFC | Merge ai-gateway-provider into workers-ai-provider — registry routing, universal run API, resume (proposed) |
| `rfc-coding-agent.md` | RFC | `CodingAgent` — new `@cloudflare/coding-agent` package (extends AIChatAgent), CLI coding agents in Sandbox, pluggable engine (Cli/Harness), two-lifecycle durability (proposed) |
| `test-coverage-matrix.md` | design doc | Feature × test-layer coverage rollup, CI→layer mapping, skipped-test debt, nightly hygiene |
| `mcp.md` | design doc | Stateless, Legacy compatibility, Legacy sessionful, client, package boundary, and conformance architecture |

## Relationship to `/docs`

Expand Down
103 changes: 103 additions & 0 deletions design/mcp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# MCP architecture

## Terminology

The codebase uses three deliberately distinct terms:

- **Stateless** — the SDK v2 server and client path. A fresh server instance serves each HTTP request. Stateless Elicitation works through multi-round-trip requests (MRTR).
- **Legacy** — retained SDK v1, sessionful server behavior. `McpAgent`, `createLegacyMcpHandler`, and `WorkerTransport` belong here. Legacy Elicitation uses pushed server-to-client requests.
- **Legacy compatibility** — Legacy protocol requests accepted by the Stateless handler without a session. It uses the SDK v2 web-standard transport, not `WorkerTransport`. Catalog and ordinary operation requests work; session operations, pushed server-to-client requests, and SSE recovery do not.

Exact protocol-version strings remain at wire and conformance boundaries, but are not architecture names.

## Public package boundaries

| Import | Responsibility |
| ------------------- | --------------------------------------------------------------------- |
| `agents/mcp/server` | Isolated Stateless Worker handler and Agents auth-context helper |
| `agents/mcp/client` | Agent-side MCP client manager and connection APIs |
| `agents/mcp` | Compatibility barrel retaining existing Legacy and historical imports |

Stateless server constructors and protocol helpers remain owned by `@modelcontextprotocol/server`:

```ts
import { McpServer } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";
```

Agents does not re-export `McpServer`. Keeping the ownership boundary explicit has three benefits:

1. applications declare and can audit the exact SDK beta they use;
2. upstream SDK documentation and types map directly to the import;
3. multiple installed SDK copies are less likely to be hidden behind an Agents alias.

The tradeoff is one additional import and direct peer installation. That is preferable to coupling Agents releases to ownership of the upstream constructor API.

## Source boundaries

### Stateless

- `server.ts` — public, tree-shakeable entry.
- `handler-stateless.ts` — Worker route, CORS, Host/Origin policy, auth context, and dispatch between Stateless and Legacy compatibility requests.
- `handler-legacy-compat.ts` — per-request SDK v2 transport for Legacy compatibility, including fail-fast reverse requests, close tracking, and Cloudflare SSE keepalives.

### Legacy

- `legacy-agent.ts` — deprecated, feature-frozen `McpAgent` implementation.
- `handler-legacy.ts` — explicit SDK v1 handler.
- `worker-transport.ts` — SDK v1 Worker transport with session persistence and SSE keepalives.
- `transport.ts` and `event-store.ts` — sessionful McpAgent transport and replay support.

### Compatibility

- `handler-compat.ts` — historical overloaded `createMcpHandler`; functions remain available from `agents/mcp` without making the Stateless entry retain Legacy modules.
- `index.ts` — compatibility barrel. It contains no implementation.

## Lifecycles

### Stateless server

1. The client sends `server/discover`.
2. Each catalog or operation request constructs a fresh `McpServer`/`Server` from the factory.
3. The handler connects it to a single-exchange transport and returns JSON or SSE.
4. The server and transport close when the response completes, is cancelled, or aborts.
5. Stateless Elicitation returns `input_required`; each retry carries the latest `requestState` and that round's `inputResponses`.

### Legacy compatibility

1. The client initializes through the Stateless endpoint's compatibility lane.
2. Each POST receives a fresh SDK v2 server/transport pair.
3. Catalogs and ordinary tools, prompts, resources, completion, logging, and progress work.
4. GET/DELETE session operations return `405`; pushed sampling/elicitation/list-roots fail immediately; disconnected response streams cannot resume.

### Legacy sessionful server

1. A session ID addresses an `McpAgent` or Agent-backed `createLegacyMcpHandler` instance.
2. `WorkerTransport` persists initialization state through Durable Object hibernation.
3. The response or standalone SSE stream carries pushed requests and notifications.
4. `DurableObjectEventStore` can replay stored SSE events after reconnect.

### Client

The SDK v2 client negotiates Stateless first and falls back to Legacy on the same connection. Agents persists the selected protocol and discovery result alongside resumable HTTP state. An in-flight elicitation handler and continuation remain memory-only; isolate restart rejects the operation and the caller retries it.

## Verification matrix

| Surface | Evidence |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Stateless server lifecycle | Official server referee: discovery, catalogs, tool result shapes, errors, progress, prompts, resources, completion, headers, caching, and input-required cases |
| Legacy compatibility | Official Legacy server scenarios against the Stateless endpoint; expected failures are limited to session/reverse-request behavior |
| Legacy sessionful handler | Official Legacy server scenarios against `createLegacyMcpHandler` |
| McpAgent | Separate official Legacy server lane |
| Stateless client | Official client referee plus `v2-lifecycle.test.ts` and `client-v2-mrtr.test.ts` |
| Legacy client fallback | Dated official client lanes plus the Legacy arm of `v2-lifecycle.test.ts` |
| Bundle isolation | `mcp-server-bundle.test.ts` rejects SDK v1, MCP client, PartyServer, `McpAgent`, and `WorkerTransport` modules |

`packages/agents/conformance/README.md` owns exact scenario counts and expected-failure rationale.

## Tradeoffs

- `agents/mcp` remains broad for backward compatibility; new servers must opt into `agents/mcp/server` to get the isolated graph.
- Legacy compatibility maximizes client reach but cannot emulate a session. Applications requiring Legacy Elicitation or replay must mount a Legacy server.
- Origin validation is enabled by default. `allowedOriginHostnames: "*"` is only appropriate when trusted upstream middleware enforces the required policy.
- Handler-produced protocol errors remain in-band according to MCP transport semantics. Mapping a downstream tool's authentication failure to the endpoint's HTTP authentication status is a separate API/protocol decision, not part of server entry isolation.
10 changes: 10 additions & 0 deletions design/test-coverage-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ Legend: ✅ covered · ⚠️ partial / indirect · — none · 🚫 gated (opt-
| Think fetch tool (allowlist, SSRF, limits, redirects, auto-merge) | ✅ | ✅ | ✅ `think/src/tests/fetch-tools.test.ts` (+ `agents/fetch-tools.ts` fixture) | — | — | — | — |
| Shared-engine genericity (pi / tanstack adapters) | ✅ | ✅ | ✅ | — | ✅ `experimental/pi-recovery`, `tanstack-recovery` (workers-ai leg 🚫) | — | ✅ tanstack leg |

### MCP coverage detail

MCP has a dedicated official-referee matrix outside the layer table. It covers
Stateless client and server behavior, Legacy compatibility, the Legacy handler,
and `McpAgent`. `packages/agents/src/tests/mcp/v2-lifecycle.test.ts` also walks
discovery or initialization, catalogs, a tool call, a prompt, a resource, and
close through both the Stateless and Legacy compatibility client paths. Refer
to [`packages/agents/conformance/README.md`](../packages/agents/conformance/README.md)
for scenario counts and expected-failure rationale.

### Accepted coverage gaps

- **Chat recovery orphan-persist (c) tool-approval dedup:** no real-SIGKILL L4
Expand Down
Loading
Loading