From 04d697535eb881abe43c351393ff7c3afaaa446d Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Mon, 20 Jul 2026 18:29:53 +0100 Subject: [PATCH 01/16] [Agents] Add MCP SDK v2 migration guide --- ...26-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx | 74 ++ .../model-context-protocol/apis/agent-api.mdx | 14 +- .../apis/client-api.mdx | 10 +- .../apis/handler-api.mdx | 662 +++++------------- .../guides/build-codemode-mcp-server.mdx | 4 +- .../build-codemode-openapi-mcp-server.mdx | 4 +- .../guides/migrate-to-mcp-sdk-v2.mdx | 383 ++++++++++ .../guides/remote-mcp-server.mdx | 17 +- .../protocol/authorization.mdx | 31 +- .../model-context-protocol/protocol/tools.mdx | 71 +- .../protocol/transport.mdx | 58 +- 11 files changed, 741 insertions(+), 587 deletions(-) create mode 100644 src/content/changelog/agents/2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx create mode 100644 src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx diff --git a/src/content/changelog/agents/2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx b/src/content/changelog/agents/2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx new file mode 100644 index 00000000000..6eda1583a70 --- /dev/null +++ b/src/content/changelog/agents/2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx @@ -0,0 +1,74 @@ +--- +title: "Agents SDK v0.18.0: MCP SDK v2 support" +description: "Agents SDK v0.18.0 adds MCP SDK v2 clients and stateless servers while retaining explicit support for published 2025 protocol deployments." +products: + - agents + - workers +date: 2026-07-20 +--- + +import { PackageManagers, TypeScriptExample } from "~/components"; + +Agents SDK v0.18.0 adds support for the split MCP TypeScript SDK v2 packages. Agents can serve the draft `2026-07-28` protocol, negotiate between modern and published 2025 servers, and handle modern multi-round-trip input requests. + +Existing 2025 server deployments remain supported through `createLegacyMcpHandler`, `WorkerTransport`, and `McpAgent`. + +## Stateless MCP SDK v2 servers + +`createMcpHandler` now accepts a factory that returns a server from `@modelcontextprotocol/server`. The factory creates an isolated server for each request. + + + +```ts +import { McpServer } from "@modelcontextprotocol/server"; +import { createMcpHandler } from "agents/mcp"; + +function createServer() { + return new McpServer({ name: "example", version: "1.0.0" }); +} + +export default createMcpHandler(createServer); +``` + + + +The handler serves draft `2026-07-28` requests. Its default stateless fallback also supports ordinary tools, resources, and prompts from published 2025 clients. Session streams, replay, deletion, and pushed server-to-client requests still require a 2025 sessionful server. + +The Workers wrapper validates present browser Origins, allows the modern `Mcp-Method` and `Mcp-Name` CORS headers, and exposes the upstream handler's `close`, `notify`, and `bus` controls. + +## Explicit 2025 server support + +Existing SDK v1 servers can rename `createMcpHandler` to `createLegacyMcpHandler` without changing their transport behavior: + + + +```ts +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { createLegacyMcpHandler } from "agents/mcp"; + +export default createLegacyMcpHandler( + new McpServer({ name: "legacy", version: "1.0.0" }), +); +``` + + + +`createLegacyMcpHandler` and `WorkerTransport` are not deprecated. Passing an SDK v1 server directly to `createMcpHandler` is deprecated. `McpAgent` remains available for stateful 2025 deployments, but it is deprecated and feature-frozen. + +## Client negotiation and input requests + +The MCP client manager now uses `@modelcontextprotocol/client`. It probes modern servers with `server/discover` and falls back to the published `initialize` handshake. + +Modern `input_required` results use the same form and URL elicitation handlers as pushed 2025 elicitation. The SDK collects input, retries the original operation, and resolves the original `callTool`, `getPrompt`, or `readResource` promise with its final result. + +OAuth callbacks now validate issuer metadata through the v2 SDK. Discovery state and issuer-bound credentials persist across browser redirects and Durable Object hibernation. + +### Upgrade + +To update the Agents SDK: + + + +The MCP SDK v2 packages remain in beta. Applications that import them directly should use the exact versions required by their installed Agents release. + +Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for package changes, compatibility limits, and rollout steps. diff --git a/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx b/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx index c4dbf2ccef4..060d490dc88 100644 --- a/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx @@ -12,7 +12,17 @@ products: import { TypeScriptExample, LinkCard } from "~/components"; -When you build MCP Servers on Cloudflare, you extend the [`McpAgent` class](https://github.com/cloudflare/agents/blob/main/packages/agents/src/mcp/index.ts#L32-L620), from the Agents SDK: +`McpAgent` creates a stateful published 2025 MCP server backed by a Durable Object. + +:::caution[Deprecated] + +`McpAgent` remains available for existing servers that need Durable Object state, RPC, protocol sessions, or pushed server-to-client requests. It is deprecated and feature-frozen. New stateless servers should use [`createMcpHandler`](/agents/model-context-protocol/apis/handler-api/). + +Keep importing its `McpServer` from `@modelcontextprotocol/sdk`. An SDK v2 server from `@modelcontextprotocol/server` cannot run inside `McpAgent`. + +Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) before changing an existing deployment. + +::: @@ -40,7 +50,7 @@ export class MyMCP extends McpAgent { This means that each instance of your MCP server has its own durable state, backed by a [Durable Object](/durable-objects/), with its own [SQL database](/agents/runtime/lifecycle/state/). -Your MCP server doesn't necessarily have to be an Agent. You can build MCP servers that are stateless, and just add [tools](/agents/model-context-protocol/protocol/tools/) to your MCP server using the `@modelcontextprotocol/sdk` package. +A stateless modern server can define [tools](/agents/model-context-protocol/protocol/tools/) with `@modelcontextprotocol/server` and serve them through `createMcpHandler`. But if you want your MCP server to: diff --git a/src/content/docs/agents/model-context-protocol/apis/client-api.mdx b/src/content/docs/agents/model-context-protocol/apis/client-api.mdx index 8552f8b4ce9..3e71c967ede 100644 --- a/src/content/docs/agents/model-context-protocol/apis/client-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/client-api.mdx @@ -12,7 +12,9 @@ products: import { Render, TypeScriptExample, LinkCard } from "~/components"; -Connect your agent to external [Model Context Protocol (MCP)](/agents/model-context-protocol/) servers to use their tools, resources, and prompts. This enables your agent to interact with GitHub, Slack, databases, and other services through a standardized protocol. +Connect your agent to external [Model Context Protocol (MCP)](/agents/model-context-protocol/) servers to use their tools, resources, and prompts. Agents SDK v0.18.0 uses `@modelcontextprotocol/client` and negotiates draft `2026-07-28` or published 2025 protocol behavior automatically. + +Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for package, type, OAuth provider, and rollout changes. ## Overview @@ -686,7 +688,7 @@ async addMcpServer( callbackHost?: string; callbackPath?: string; agentsPrefix?: string; - client?: ClientOptions; + client?: McpClientOptions; transport?: { headers?: HeadersInit; type?: "sse" | "streamable-http" | "auto"; @@ -705,7 +707,7 @@ async addMcpServer( options?: { id?: string; props?: Record; - client?: ClientOptions; + client?: McpClientOptions; retry?: RetryOptions; } ): Promise<{ id: string; state: "ready" }> @@ -720,7 +722,7 @@ async addMcpServer( - `callbackHost` — Host for OAuth callback URL. Only needed for OAuth-authenticated servers. If omitted, automatically derived from the incoming request or WebSocket connection URI — you typically do not need to set this unless you are using a custom domain that differs from the Worker's hostname - `callbackPath` — Custom callback URL path that bypasses the default `/agents/{class}/{name}/callback` construction. **Required when `sendIdentityOnConnect` is `false`** to prevent leaking the instance name. When set, the callback URL becomes `{callbackHost}/{callbackPath}`. You must route this path to the agent instance via `getAgentByName` - `agentsPrefix` — URL prefix for OAuth callback path. Default: `"agents"`. Ignored when `callbackPath` is provided - - `client` — MCP client configuration options (passed to `@modelcontextprotocol/sdk` Client constructor). By default, includes `CfWorkerJsonSchemaValidator` for validating tool parameters against JSON schemas + - `client` — The Agents-supported `McpClientOptions` subset from `@modelcontextprotocol/client`. The default validator supports JSON Schema 2020-12 and legacy draft-07 schemas in Workers - `transport` — Transport layer configuration: - `headers` — Custom HTTP headers for authentication - `type` — Transport type: `"auto"` (default), `"streamable-http"`, or `"sse"` diff --git a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx index 18db6e245d8..277da10c17d 100644 --- a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx @@ -1,7 +1,7 @@ --- pcx_content_type: reference -title: createMcpHandler -description: Create a stateless MCP server fetch handler for a plain Worker using createMcpHandler and streamable HTTP transport. +title: MCP handler APIs +description: Create modern stateless or published 2025 MCP server handlers for Cloudflare Workers with the Agents SDK. tags: - MCP sidebar: @@ -10,543 +10,269 @@ products: - agents --- -import { TypeScriptExample, LinkCard } from "~/components"; +import { LinkCard, PackageManagers, TypeScriptExample } from "~/components"; -The `createMcpHandler` function creates a fetch handler to serve your [MCP server](/agents/model-context-protocol/). Use it when you want a stateless MCP server that runs in a plain Worker (no Durable Object). For stateful MCP servers that persist state across requests, use the [`McpAgent`](/agents/model-context-protocol/apis/agent-api/) class instead. +The `agents/mcp` entry point provides two Worker handler APIs: -It uses an implementation of the MCP Transport interface, `WorkerTransport`, built on top of web standards, which conforms to the [streamable-http](https://modelcontextprotocol.io/specification/draft/basic/transports/#streamable-http) transport specification. +| API | MCP server package | Protocol behavior | +| ------------------------ | ------------------------------ | --------------------------------------------------------------- | +| `createMcpHandler` | `@modelcontextprotocol/server` | Draft `2026-07-28` with stateless 2025 compatibility by default | +| `createLegacyMcpHandler` | `@modelcontextprotocol/sdk` | Published 2025 protocol behavior through `WorkerTransport` | -```ts -import { createMcpHandler, type CreateMcpHandlerOptions } from "agents/mcp"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +`McpAgent` also remains available for stateful 2025 servers. It is deprecated and feature-frozen. Refer to the [`McpAgent` API](/agents/model-context-protocol/apis/agent-api/) for its existing behavior. -function createMcpHandler( - server: McpServer, - options?: CreateMcpHandlerOptions, -): (request: Request, env: Env, ctx: ExecutionContext) => Promise; -``` +:::note[SDK version and protocol version] -#### Parameters +MCP SDK v2 names the split TypeScript packages. Protocol revision `2026-07-28` remains a draft until the MCP project publishes it. -- **server** — An instance of [`McpServer`](https://modelcontextprotocol.io/docs/develop/build-server#node) from the `@modelcontextprotocol/sdk` package -- **options** — Optional configuration object (see [`CreateMcpHandlerOptions`](#createmcphandleroptions)) +::: -#### Returns +## Install dependencies -A Worker fetch handler function with the signature `(request: Request, env: unknown, ctx: ExecutionContext) => Promise`. +For a modern server: -### CreateMcpHandlerOptions + -Configuration options for creating an MCP handler. +For an explicit 2025 server: -```ts -interface CreateMcpHandlerOptions extends WorkerTransportOptions { - /** - * The route path that this MCP handler should respond to. - * If specified, the handler will only process requests that match this route. - * @default "/mcp" - */ - route?: string; - - /** - * An optional auth context to use for handling MCP requests. - * If not provided, the handler will look for props in the execution context. - */ - authContext?: McpAuthContext; - - /** - * An optional transport to use for handling MCP requests. - * If not provided, a WorkerTransport will be created with the provided WorkerTransportOptions. - */ - transport?: WorkerTransport; - - // Inherited from WorkerTransportOptions: - sessionIdGenerator?: () => string; - enableJsonResponse?: boolean; - onsessioninitialized?: (sessionId: string) => void; - corsOptions?: CORSOptions; - storage?: MCPStorageApi; -} -``` - -#### Options - -##### route + -The URL path where the MCP handler responds. Requests to other paths return a 404 response. +Use the exact MCP versions required by your installed Agents release while the v2 SDK remains in beta. -**Default:** `"/mcp"` +## `createMcpHandler` - +`createMcpHandler` creates a stateless Worker handler from an MCP SDK v2 server factory. ```ts -const handler = createMcpHandler(server, { - route: "/api/mcp", // Only respond to requests at /api/mcp -}); -``` - - - -#### authContext - -An authentication context object that will be available to MCP tools via [`getMcpAuthContext()`](/agents/model-context-protocol/apis/handler-api/#authentication-context). +import { + createMcpHandler, + type CreateStatelessMcpHandlerOptions, + type StatelessMcpHandler, +} from "agents/mcp"; +import type { McpServerFactory } from "@modelcontextprotocol/server"; -When using the [`OAuthProvider`](/agents/model-context-protocol/protocol/authorization/) from `@cloudflare/workers-oauth-provider`, the authentication context is automatically populated with information from the OAuth flow. You typically don't need to set this manually. +function createMcpHandler( + factory: McpServerFactory, + options?: CreateStatelessMcpHandlerOptions, +): StatelessMcpHandler; +``` -#### transport +### Parameters -A custom `WorkerTransport` instance. If not provided, a new transport is created on every request. +- `factory` creates a fresh `McpServer` or `Server` from `@modelcontextprotocol/server`. It can be synchronous or asynchronous. +- `options` combines Agents Worker options with supported upstream SDK v2 handler options. - +The factory receives this request context: ```ts -import { createMcpHandler, WorkerTransport } from "agents/mcp"; - -const transport = new WorkerTransport({ - sessionIdGenerator: () => `session-${crypto.randomUUID()}`, - storage: { - get: () => myStorage.get("transport-state"), - set: (state) => myStorage.put("transport-state", state), - }, -}); - -const handler = createMcpHandler(server, { transport }); +interface McpRequestContext { + era: "modern" | "legacy"; + authInfo?: AuthInfo; + requestInfo?: Request; +} ``` - - -## Stateless MCP Servers - -Many MCP Servers are stateless, meaning they do not maintain any session state between requests. The `createMcpHandler` function is a lightweight alternative to the `McpAgent` class that can be used to serve an MCP server straight from a Worker. View the [complete example on GitHub](https://github.com/cloudflare/agents/tree/main/examples/mcp-worker). - -:::caution[Breaking change in MCP SDK 1.26.0] - -**Important:** If you are upgrading from MCP SDK versions before 1.26.0, you must update how you create `McpServer` instances in stateless servers. +A zero-argument factory remains valid. -MCP SDK 1.26.0 introduces a guard that prevents connecting to a server instance that has already been connected to a transport. This fixes a security vulnerability ([CVE](https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-345p-7cg4-v4c7)) where sharing server or transport instances could leak cross-client response data. - -**If your stateless MCP server declares `McpServer` or transport instances in the global scope, you must create new instances per request.** - -See the [migration guide](/agents/model-context-protocol/apis/handler-api/#migration-guide-for-mcp-sdk-1260) below for details. -::: +### Example ```ts title="src/index.ts" +import { McpServer } from "@modelcontextprotocol/server"; import { createMcpHandler } from "agents/mcp"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; function createServer() { const server = new McpServer({ - name: "Hello MCP Server", + name: "hello-server", version: "1.0.0", }); - server.tool( + server.registerTool( "hello", - "Returns a greeting message", - { name: z.string().optional() }, - async ({ name }) => { - return { - content: [ - { - text: `Hello, ${name ?? "World"}!`, - type: "text", - }, - ], - }; + { + description: "Return a greeting", + inputSchema: { name: z.string().optional() }, }, + async ({ name }) => ({ + content: [{ type: "text", text: `Hello, ${name ?? "World"}!` }], + }), ); return server; } -export default { - fetch: async (request: Request, env: Env, ctx: ExecutionContext) => { - // Create new server instance per request - const server = createServer(); - return createMcpHandler(server)(request, env, ctx); - }, -} satisfies ExportedHandler; +export default createMcpHandler(createServer); ``` -Each request to this MCP server creates a new session and server instance. The server does not maintain state between requests. This is the simplest way to implement an MCP server. +Pass the factory itself. Do not create one global server instance or pass a constructed SDK v2 server directly. -## Stateful MCP Servers +### `CreateStatelessMcpHandlerOptions` -For stateful MCP servers that need to maintain session state across multiple requests, you can use the `createMcpHandler` function with a `WorkerTransport` instance directly in an `Agent`. This is useful if you want to make use of advanced client features like elicitation and sampling. +The following options are available: -Provide a custom `WorkerTransport` with persistent storage. View the [complete example on GitHub](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation). +| Option | Type | Default | Description | +| ------------------------ | --------------------------- | ------------------------------------------------- | --------------------------------------------------- | +| `route` | `string` | `"/mcp"` | Exact path handled by the Worker wrapper | +| `corsOptions` | `CORSOptions \| false` | Wildcard CORS | CORS response headers, or `false` to remove them | +| `allowedHostnames` | `string[]` | Localhost or `workers.dev` route | Optional Host restriction for custom domains | +| `allowedOriginHostnames` | `string[]` | Localhost, `workers.dev`, or concrete CORS Origin | Optional browser Origin restriction | +| `authContext` | `McpAuthContext` | Execution context props | Application props returned by `getMcpAuthContext()` | +| `legacy` | `"stateless" \| "reject"` | `"stateless"` | Stateless 2025 fallback or modern-only rejection | +| `responseMode` | `"auto" \| "json" \| "sse"` | `"auto"` | Modern request response shaping | +| `onerror` | `(error: Error) => void` | None | Out-of-band error reporting | +| `bus` | `ServerEventBus` | In-memory bus | Event bus for modern subscriptions | +| `maxSubscriptions` | `number` | `1,024` | Maximum concurrent listen streams | +| `keepAliveMs` | `number` | `15,000` | Keepalive interval for listen streams | - +SDK v1 transport options do not apply to this handler. It rejects options such as `transport`, `storage`, `sessionIdGenerator`, `eventStore`, and `enableJsonResponse`. -```ts title="src/index.ts" -import { Agent } from "agents"; -import { - createMcpHandler, - WorkerTransport, - type TransportState, -} from "agents/mcp"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +Use `responseMode: "json"` instead of `enableJsonResponse: true`. JSON mode drops notifications emitted before a final result. -const STATE_KEY = "mcp-transport-state"; +### Factory lifecycle -type State = { counter: number }; +The handler creates one MCP server for each request. This follows the draft protocol model, where version, identity, and capabilities travel with every request rather than through a protocol session. -export class MyStatefulMcpAgent extends Agent { - server = new McpServer({ - name: "Stateful MCP Server", - version: "1.0.0", - }); +Application data can still be durable. Store cross-request data behind an authenticated handle in a Durable Object, D1, KV, or R2 rather than an MCP session ID. - transport = new WorkerTransport({ - sessionIdGenerator: () => this.name, - storage: { - get: () => { - return this.ctx.storage.get(STATE_KEY); - }, - set: (state: TransportState) => { - this.ctx.storage.put(STATE_KEY, state); - }, - }, - }); +### Origin validation and CORS - async onRequest(request: Request) { - return createMcpHandler(this.server, { - transport: this.transport, - })(request, this.env, this.ctx as unknown as ExecutionContext); - } -} -``` +The Workers wrapper validates every present browser Origin. It rejects malformed, opaque, and non-HTTP Origins with `403`. Origin-less non-browser MCP clients remain valid. - +The default allowlist includes localhost-class Origins, the endpoint's `workers.dev` hostname, and a concrete hostname from `corsOptions.origin`. The handler also applies matching Host checks to localhost and `workers.dev` endpoints. This keeps local DNS rebinding protection without requiring a separate Origin list for the common Workers routes. -In this case we are defining the `sessionIdGenerator` to return the Agent name as the session ID. To make sure we route to the correct Agent we can use `getAgentByName` in the Worker handler: +For a custom domain with wildcard CORS, set `allowedHostnames` and `allowedOriginHostnames` explicitly. If `corsOptions.origin` is a concrete URL, the handler derives its Origin hostname automatically: ```ts -import { getAgentByName } from "agents"; - -export default { - async fetch(request: Request, env: Env, ctx: ExecutionContext) { - // Extract session ID from header or generate a new one - const sessionId = - request.headers.get("mcp-session-id") ?? crypto.randomUUID(); - - // Get the Agent instance by name/session ID - const agent = await getAgentByName(env.MyStatefulMcpAgent, sessionId); - - // Route the MCP request to the agent - return await agent.onRequest(request); +export default createMcpHandler(createServer, { + allowedHostnames: ["mcp.example.com"], + corsOptions: { + origin: "https://app.example.com", }, -} satisfies ExportedHandler; +}); ``` -With persistent storage, the transport preserves: - -- Session ID across reconnections -- Protocol version negotiation state -- Initialization status +Allowlist values are hostnames without a scheme or port. Origin matching ignores scheme and port. -This allows MCP clients to reconnect and resume their session in the event of a connection loss. +CORS response headers are not authentication. Protect the MCP endpoint with OAuth or another authentication layer. -## Migration Guide for MCP SDK 1.26.0 +The handler does not infer a Host allowlist from `request.url`. If a deployment accepts arbitrary Host values, validate them before calling the handler. Local servers outside Cloudflare Workers should follow the upstream SDK DNS rebinding guidance. -The MCP SDK 1.26.0 introduces a breaking change for stateless MCP servers that addresses a critical security vulnerability where responses from one client could leak to another client when using shared server or transport instances. +### Stateless 2025 compatibility -### Who is affected? +The default `legacy: "stateless"` setting accepts ordinary tools, prompts, and resources from published 2025 clients. -| Server Type | Affected? | Action Required | -| --------------------------------------------- | --------- | ------------------------------------------------ | -| Stateful servers using `Agent`/Durable Object | No | No changes needed | -| Stateless servers using `createMcpHandler` | Yes | Create new `McpServer` per request | -| Stateless servers using raw SDK transport | Yes | Create new `McpServer` and transport per request | +This fallback does not provide a complete 2025 session transport: -### Why is this necessary? +- Each POST creates a new server and transport. +- HTTP GET and DELETE return `405`. +- No MCP session ID persists. +- Pushed elicitation, sampling, and roots requests fail immediately. +- Standalone streams, resumability, replay, and session deletion are unavailable. +- Published experimental tasks are not supported through this path. -The previous pattern of declaring `McpServer` instances in the global scope allowed responses from one client to leak to another client. This is a security vulnerability. The new SDK version prevents this by throwing an error if you try to connect a server that is already connected. +Set `legacy: "reject"` for a modern-only endpoint. Use `createLegacyMcpHandler` or `McpAgent` when a 2025 client needs protocol sessions. -### Before (broken with SDK 1.26.0) +### Return value - +The returned handler is callable as a Worker fetch handler: ```ts -import { createMcpHandler } from "agents/mcp"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +const handler = createMcpHandler(createServer); -// INCORRECT: Global server instance -const server = new McpServer({ - name: "Hello MCP Server", - version: "1.0.0", -}); +const response = await handler(request, env, ctx); +``` -server.tool("hello", "Returns a greeting", {}, async () => { - return { - content: [{ text: "Hello, World!", type: "text" }], - }; -}); +It also exposes the upstream handler controls: -export default { - fetch: async (request: Request, env: Env, ctx: ExecutionContext) => { - // This will fail on second request with MCP SDK 1.26.0+ - return createMcpHandler(server)(request, env, ctx); - }, -} satisfies ExportedHandler; +```ts +handler.fetch(request, { authInfo }); +handler.notify.toolsChanged(); +handler.bus.publish(event); +await handler.close(); ``` - +`close()` rejects new requests and closes active modern and stateless legacy work. -### After (correct) +## `createLegacyMcpHandler` - +`createLegacyMcpHandler` serves an SDK v1 server through `WorkerTransport`. ```ts -import { createMcpHandler } from "agents/mcp"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; - -// CORRECT: Factory function to create server instance -function createServer() { - const server = new McpServer({ - name: "Hello MCP Server", - version: "1.0.0", - }); - - server.tool("hello", "Returns a greeting", {}, async () => { - return { - content: [{ text: "Hello, World!", type: "text" }], - }; - }); - - return server; -} +import { + createLegacyMcpHandler, + type CreateLegacyMcpHandlerOptions, + type LegacyMcpHandler, +} from "agents/mcp"; +import type { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -export default { - fetch: async (request: Request, env: Env, ctx: ExecutionContext) => { - // Create new server instance per request - const server = createServer(); - return createMcpHandler(server)(request, env, ctx); - }, -} satisfies ExportedHandler; +function createLegacyMcpHandler( + server: McpServer | Server, + options?: CreateLegacyMcpHandlerOptions, +): LegacyMcpHandler; ``` - - -### For raw SDK transport users - -If you are using the raw SDK transport directly (not via `createMcpHandler`), you must also create new transport instances per request: +Use this handler for published 2025 protocol sessions, transport storage, event replay, and pushed server-to-client requests. -```ts +```ts title="src/index.ts" import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js"; +import { createLegacyMcpHandler } from "agents/mcp"; function createServer() { - const server = new McpServer({ - name: "Hello MCP Server", - version: "1.0.0", - }); - - // Register tools... - - return server; + return new McpServer({ name: "legacy-server", version: "1.0.0" }); } export default { - async fetch(request: Request) { - // Create new transport and server per request - const transport = new WebStandardStreamableHTTPServerTransport(); - const server = createServer(); - server.connect(transport); - return transport.handleRequest(request); + async fetch(request: Request, env: Env, ctx: ExecutionContext) { + return createLegacyMcpHandler(createServer())(request, env, ctx); }, } satisfies ExportedHandler; ``` -### WorkerTransport - -The `WorkerTransport` class implements the MCP Transport interface, handling HTTP request/response cycles, Server-Sent Events (SSE) streaming, session management, and CORS. - -```ts -class WorkerTransport implements Transport { - sessionId?: string; - started: boolean; - onclose?: () => void; - onerror?: (error: Error) => void; - onmessage?: (message: JSONRPCMessage, extra?: MessageExtraInfo) => void; - - constructor(options?: WorkerTransportOptions); - - async handleRequest( - request: Request, - parsedBody?: unknown, - ): Promise; - async send( - message: JSONRPCMessage, - options?: TransportSendOptions, - ): Promise; - async start(): Promise; - async close(): Promise; -} -``` - -#### Constructor Options - -```ts -interface WorkerTransportOptions { - /** - * Function that generates a unique session ID. - * Called when a new session is initialized. - */ - sessionIdGenerator?: () => string; - - /** - * Enable traditional Request/Response mode, disabling streaming. - * When true, responses are returned as JSON instead of SSE streams. - * @default false - */ - enableJsonResponse?: boolean; - - /** - * Callback invoked when a session is initialized. - * Receives the generated or restored session ID. - */ - onsessioninitialized?: (sessionId: string) => void; - - /** - * CORS configuration for cross-origin requests. - * Configures Access-Control-* headers. - */ - corsOptions?: CORSOptions; - - /** - * Optional storage API for persisting transport state. - * Use this to store session state in Durable Object/Agent storage - * so it survives hibernation/restart. - */ - storage?: MCPStorageApi; -} -``` - -#### sessionIdGenerator - -Provides a custom session identifier. This session identifier is used to identify the session in the MCP Client. - - - -```ts -const transport = new WorkerTransport({ - sessionIdGenerator: () => `user-${Date.now()}-${Math.random()}`, -}); -``` - - - -#### enableJsonResponse - -Disables SSE streaming and returns responses as standard JSON. - - - -```ts -const transport = new WorkerTransport({ - enableJsonResponse: true, // Disable streaming, return JSON responses -}); -``` - - - -#### onsessioninitialized - -A callback that fires when a session is initialized, either by creating a new session or restoring from storage. - - - -```ts -const transport = new WorkerTransport({ - onsessioninitialized: (sessionId) => { - console.log(`MCP session initialized: ${sessionId}`); - }, -}); -``` - - - -#### corsOptions - -Configure CORS headers for cross-origin requests. - -```ts -interface CORSOptions { - origin?: string; - methods?: string; - headers?: string; - maxAge?: number; - exposeHeaders?: string; -} -``` - - - -```ts -const transport = new WorkerTransport({ - corsOptions: { - origin: "https://example.com", - methods: "GET, POST, OPTIONS", - headers: "Content-Type, Authorization", - maxAge: 86400, - }, -}); -``` +Passing an SDK v1 server to `createMcpHandler` still works but emits a deprecation warning. Change the call to `createLegacyMcpHandler` to keep the same behavior without the warning. - +`experimental_createMcpHandler` is also deprecated. Replace it with `createLegacyMcpHandler`. -#### storage +### `CreateLegacyMcpHandlerOptions` -Persist transport state to survive Durable Object hibernation or restarts. +`CreateLegacyMcpHandlerOptions` extends `WorkerTransportOptions` and adds these fields: -```ts -interface MCPStorageApi { - get(): Promise | TransportState | undefined; - set(state: TransportState): Promise | void; -} +| Option | Type | Default | Description | +| ------------- | ----------------- | ----------------------- | ------------------------------------- | +| `route` | `string` | `"/mcp"` | Exact path handled by the handler | +| `authContext` | `McpAuthContext` | Execution context props | Application props for tool handlers | +| `transport` | `WorkerTransport` | New transport | Persistent or preconfigured transport | -interface TransportState { - sessionId?: string; - initialized: boolean; - protocolVersion?: ProtocolVersion; -} -``` +Common `WorkerTransportOptions` include: - +| Option | Description | +| ----------------------------------------- | ---------------------------------------------------------- | +| `sessionIdGenerator` | Creates protocol session IDs | +| `enableJsonResponse` | Returns JSON instead of SSE where supported | +| `storage` | Persists transport state through an `{ get, set }` adapter | +| `eventStore` | Persists events for replay and stream recovery | +| `corsOptions` | Adds CORS response and preflight headers | +| `onsessioninitialized`, `onsessionclosed` | Observe session lifecycle changes | -```ts -// Inside an Agent or Durable Object class method: -const transport = new WorkerTransport({ - storage: { - get: async () => { - return await this.ctx.storage.get("mcp-state"); - }, - set: async (state) => { - await this.ctx.storage.put("mcp-state", state); - }, - }, -}); -``` +Create a fresh SDK v1 server for each request unless you provide a persistent transport already connected to that server. One server cannot reconnect to several transports. - +## Authentication context -## Authentication Context +A compatible `@cloudflare/workers-oauth-provider` supplies verified standard `AuthInfo` to SDK v2 callbacks at `context.http.authInfo`. -When using [OAuth authentication](/agents/model-context-protocol/protocol/authorization/) with `createMcpHandler`, user information is made available to your MCP tools through `getMcpAuthContext()`. Under the hood this uses `AsyncLocalStorage` to pass the request to the tool handler, keeping the authentication context available. +The existing `getMcpAuthContext()` helper continues to return application props: ```ts interface McpAuthContext { @@ -554,101 +280,57 @@ interface McpAuthContext { } ``` -### getMcpAuthContext - -Retrieve the current authentication context within an MCP tool handler. This returns user information that was populated by the OAuth provider. Note that if using `McpAgent`, this information is accessible directly on `this.props` instead. - -```ts -import { getMcpAuthContext } from "agents/mcp"; - -function getMcpAuthContext(): McpAuthContext | undefined; -``` - ```ts import { getMcpAuthContext } from "agents/mcp"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; - -function createServer() { - const server = new McpServer({ name: "Auth Server", version: "1.0.0" }); - server.tool("getProfile", "Get the current user's profile", {}, async () => { +server.registerTool( + "whoami", + { description: "Return the current identity", inputSchema: {} }, + async (_args, context) => { const auth = getMcpAuthContext(); - const username = auth?.props?.username as string | undefined; - const email = auth?.props?.email as string | undefined; return { content: [ { type: "text", - text: `User: ${username ?? "anonymous"}, Email: ${email ?? "none"}`, + text: JSON.stringify({ + clientId: context.http?.authInfo?.clientId, + scopes: context.http?.authInfo?.scopes, + userId: auth?.props.userId, + }), }, ], }; - }); - - return server; -} + }, +); ``` -:::note -For a complete guide on setting up OAuth authentication with MCP servers, see the [MCP Authorization documentation](/agents/model-context-protocol/protocol/authorization/). View the [complete authenticated MCP server in a Worker example on GitHub](https://github.com/cloudflare/agents/tree/main/examples/mcp-worker-authenticated). -::: - -## Error Handling +Do not log or return `authInfo.token` or `authInfo.extra.props`. -The `createMcpHandler` automatically catches errors and returns JSON-RPC error responses with code `-32603` (Internal error). +## Migration - +Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) before changing an existing server. The migration guide covers dual-era routing, stateful servers, client changes, and rollout checks. -```ts -server.tool("riskyOperation", "An operation that might fail", {}, async () => { - if (Math.random() > 0.5) { - throw new Error("Random failure occurred"); - } - return { - content: [{ type: "text", text: "Success!" }], - }; -}); - -// Errors are automatically caught and returned as: -// { -// "jsonrpc": "2.0", -// "error": { -// "code": -32603, -// "message": "Random failure occurred" -// }, -// "id": -// } -``` - - - -## Related Resources - - +## Related resources diff --git a/src/content/docs/agents/model-context-protocol/guides/build-codemode-mcp-server.mdx b/src/content/docs/agents/model-context-protocol/guides/build-codemode-mcp-server.mdx index fd5772e279c..b14ec56d106 100644 --- a/src/content/docs/agents/model-context-protocol/guides/build-codemode-mcp-server.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/build-codemode-mcp-server.mdx @@ -62,7 +62,7 @@ You need a Cloudflare Workers project and an existing `McpServer`. import { DynamicWorkerExecutor } from "@cloudflare/codemode"; import { codeMcpServer } from "@cloudflare/codemode/mcp"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; - import { createMcpHandler } from "agents/mcp"; + import { createLegacyMcpHandler } from "agents/mcp"; import { z } from "zod"; function createOrderServer() { @@ -105,7 +105,7 @@ You need a Cloudflare Workers project and an existing `McpServer`. executor, }); - return createMcpHandler(server, { route: "/mcp" })( + return createLegacyMcpHandler(server, { route: "/mcp" })( request, env, ctx, diff --git a/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx b/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx index da690a54fc2..f2b57a2e45f 100644 --- a/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx @@ -64,7 +64,7 @@ You need a Cloudflare Workers project, an OpenAPI 3.x document, and a host-side ```ts import { DynamicWorkerExecutor } from "@cloudflare/codemode"; import { openApiMcpServer } from "@cloudflare/codemode/mcp"; - import { createMcpHandler } from "agents/mcp"; + import { createLegacyMcpHandler } from "agents/mcp"; const SPEC_URL = "https://api.example.com/openapi.json"; const API_ORIGIN = "https://api.example.com"; @@ -139,7 +139,7 @@ You need a Cloudflare Workers project, an OpenAPI 3.x document, and a host-side }, }); - return createMcpHandler(server, { route: "/mcp" })( + return createLegacyMcpHandler(server, { route: "/mcp" })( request, env, ctx, diff --git a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx new file mode 100644 index 00000000000..8f34bad7065 --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx @@ -0,0 +1,383 @@ +--- +title: Migrate to MCP SDK v2 +description: Migrate Agents SDK MCP servers and clients to the split MCP TypeScript SDK v2 packages while retaining published 2025 protocol compatibility. +pcx_content_type: how-to +sidebar: + order: 13 +products: + - agents +--- + +import { PackageManagers, Steps, TypeScriptExample } from "~/components"; + +This guide covers the MCP SDK v2 upgrade in Agents SDK v0.18.0. It explains how to move stateless servers to `@modelcontextprotocol/server`, keep existing 2025 servers on an explicit legacy handler, and update MCP clients. + +:::note[SDK version and protocol version] + +MCP SDK v2 refers to the split TypeScript packages. It does not make protocol revision `2026-07-28` a published stable specification. + +The v2 packages implement the draft `2026-07-28` revision. They also support the published `2025-11-25` revision. This guide calls these the modern and legacy protocol eras. + +::: + +## Choose a server path + +Use the following table to select a migration path: + +| Current server | Migration path | +| --------------------------------------------------------- | --------------------------------------------------------------------------------------- | +| `createMcpHandler` with an SDK v1 server | Rename the call to `createLegacyMcpHandler` to keep the same behavior. | +| Stateless server ready for draft `2026-07-28` | Move to `@modelcontextprotocol/server` and pass a factory to `createMcpHandler`. | +| Server that must support published specifications only | Keep SDK v1 and use `createLegacyMcpHandler`. | +| `McpAgent` using state, props, RPC, or pushed elicitation | Keep `McpAgent` temporarily. It remains available but is deprecated and feature-frozen. | +| `McpAgent` without per-session state | Move its tools to an SDK v2 factory and use `createMcpHandler`. | + +`createLegacyMcpHandler` and `WorkerTransport` are not deprecated. The following APIs are deprecated: + +- Passing an SDK v1 server to `createMcpHandler` +- `experimental_createMcpHandler` +- `McpAgent` + +## Install the MCP packages + +Install only the MCP package generations that your application imports. Keep the v2 beta version exact. + +For a modern server: + + + +For an explicit 2025 server: + + + +For an Agent that connects to MCP servers: + + + +Follow peer dependency instructions from your package manager. The exact v2 pin will change with later Agents releases while the MCP SDK remains in beta. + +## Keep existing SDK v1 behavior + +Use this path when your server depends on any of these features: + +- Protocol sessions or a supplied `WorkerTransport` +- Transport storage or event replay +- Standalone GET streams +- Pushed elicitation, sampling, or roots requests +- Session deletion with HTTP `DELETE` + + + +1. Keep importing `McpServer` from `@modelcontextprotocol/sdk`. + +2. Replace `createMcpHandler` with `createLegacyMcpHandler`. + +3. Keep the existing `WorkerTransport` options. + +4. Test initialization, tool calls, reconnects, session deletion, OAuth, and server-to-client requests. + + + + + +```ts title="src/index.ts" +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { createLegacyMcpHandler } from "agents/mcp"; + +function createServer() { + return new McpServer({ name: "legacy-server", version: "1.0.0" }); +} + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext) { + return createLegacyMcpHandler(createServer())(request, env, ctx); + }, +} satisfies ExportedHandler; +``` + + + +Create a new SDK v1 server for each request unless you provide a persistent transport that is already connected to that server. Reconnecting one server instance to several transports is invalid. + +## Move a stateless server to SDK v2 + +The modern `createMcpHandler` accepts a factory. The factory returns `McpServer` or `Server` from `@modelcontextprotocol/server`. + + + +1. Follow the upstream [TypeScript SDK v2 migration guide](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/migration/upgrade-to-v2.md) for server registration changes. + +2. Import the server from `@modelcontextprotocol/server`. + +3. Move server construction and registration into a factory. + +4. Pass the factory itself to `createMcpHandler`. + +5. Remove SDK v1 transport and session options. + +6. Test the endpoint with modern and published 2025 clients. + + + + + +```ts title="src/index.ts" +import { McpServer } from "@modelcontextprotocol/server"; +import { createMcpHandler } from "agents/mcp"; +import { z } from "zod"; + +function createServer() { + const server = new McpServer({ + name: "example-server", + version: "1.0.0", + }); + + server.registerTool( + "hello", + { + description: "Return a greeting", + inputSchema: { name: z.string().optional() }, + }, + async ({ name }) => ({ + content: [{ type: "text", text: `Hello, ${name ?? "World"}!` }], + }), + ); + + return server; +} + +export default createMcpHandler(createServer); +``` + + + +The handler creates one server for each request. Concurrent Worker requests never share a connected server instance. + +### Modern handler options + +The Agents wrapper adds `route`, `corsOptions`, `allowedHostnames`, `allowedOriginHostnames`, and `authContext`. It also passes supported SDK v2 options through to the upstream handler. + +Common options include: + +| Option | Behavior | +| ---------------------------------------- | ---------------------------------------------------------------------------------------- | +| `route` | Sets the exact request path. The default is `/mcp`. | +| `legacy` | Uses stateless 2025 compatibility by default. Set `"reject"` for a modern-only endpoint. | +| `responseMode` | Selects automatic, JSON, or SSE response handling. | +| `allowedHostnames` | Restricts Host headers to specific hostnames. | +| `allowedOriginHostnames` | Restricts browser Origins to specific hostnames. | +| `corsOptions` | Controls CORS response headers. Set `false` to remove them. | +| `onerror` | Reports handler errors without changing the response. | +| `bus`, `maxSubscriptions`, `keepAliveMs` | Configure `subscriptions/listen` delivery. | + +The modern handler rejects these SDK v1 options: + +- `transport` +- `storage` +- `sessionIdGenerator` +- `onsessioninitialized` and `onsessionclosed` +- `enableJsonResponse` +- `eventStore` +- `allowedHosts` and `allowedOrigins` +- `enableDnsRebindingProtection` +- `retryInterval` + +Use `responseMode: "json"` instead of `enableJsonResponse: true`. JSON mode drops notifications emitted before the final result. + +### Origin validation on Workers + +The Workers wrapper validates every present Origin. It rejects malformed, opaque, and non-HTTP Origins with `403`. + +Its default allowlist includes localhost-class Origins and the endpoint's `workers.dev` hostname. A concrete `corsOptions.origin` also adds its hostname automatically. The handler applies matching Host checks to localhost and `workers.dev` endpoints. + +For a custom domain with wildcard CORS, configure both Host and Origin restrictions explicitly: + + + +```ts +export default createMcpHandler(createServer, { + allowedHostnames: ["mcp.example.com"], + allowedOriginHostnames: ["app.example.com"], + corsOptions: { origin: "https://app.example.com" }, +}); +``` + + + +CORS headers do not authenticate a request. Protect the endpoint with OAuth or another authentication layer. + +The handler does not infer a trusted Host allowlist from `request.url`. If your deployment accepts arbitrary Host values, validate them before calling the handler. For local servers outside Cloudflare Workers, follow the upstream SDK Host and Origin validation guidance. + +### Understand the 2025 fallback + +The default `legacy: "stateless"` setting supports ordinary 2025 tools, resources, and prompts. It is not a complete sessionful `2025-11-25` transport. + +The fallback has these limits: + +- Each POST receives a new server and transport. +- HTTP GET and DELETE return `405`. +- No MCP session ID or protocol session state persists. +- Pushed sampling, elicitation, and roots requests fail immediately. +- Standalone streams, event replay, and session deletion are unavailable. +- Published experimental tasks are not supported through this fallback. + +Use `createLegacyMcpHandler` or `McpAgent` when a 2025 client needs those features. + +## Migrate an McpAgent server + +`McpAgent` remains an SDK v1 server. Do not change its server import to `@modelcontextprotocol/server`. + +Keep `McpAgent` temporarily if tools use `this.state`, `this.props`, Durable Object hibernation, RPC, pushed server-to-client requests, or session replay. + +To migrate fully, move application state outside the MCP protocol session. A tool can use authenticated server-issued handles to reach a Durable Object, D1, KV, or R2. Then expose the tool surface through an SDK v2 factory. + +### Run modern and legacy lanes together + +A single URL can route modern requests to SDK v2 and published 2025 requests to the existing sessionful server. + + + +```ts +import { isLegacyRequest } from "@modelcontextprotocol/server"; +import { createMcpHandler } from "agents/mcp"; + +const modern = createMcpHandler(createModernServer, { + route: "/mcp", + legacy: "reject", +}); + +const legacy = MyMcpAgent.serve("/mcp"); + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext) { + if (await isLegacyRequest(request)) { + return legacy.fetch(request, env, ctx); + } + return modern(request, env, ctx); + }, +} satisfies ExportedHandler; +``` + + + +Keep `legacy: "reject"` on the modern handler. Otherwise, its stateless fallback consumes 2025 traffic before the sessionful route receives it. + +Deploy both routes before removing a Durable Object binding. Let existing sessions drain, then handle Durable Object migration configuration separately. + +## Update MCP clients + +Agents now uses `@modelcontextprotocol/client` internally. Existing `addMcpServer` calls negotiate the protocol era automatically. + +Modern servers use `server/discover`. Agents falls back to the published `initialize` handshake for legacy Streamable HTTP, SSE, and RPC servers. + +The client API includes these changes: + +- `callTool(params, options)` is the preferred signature. +- `callTool(params, resultSchema, options)` remains available but is deprecated. +- MCP client types now come from `@modelcontextprotocol/client`. +- Required modern HTTP headers are handled by the SDK. +- List changes use modern subscriptions or legacy notifications based on the negotiated era. + +### Configure multi-round-trip input + +Modern tools, prompts, and resources can return `input_required`. The SDK calls the configured elicitation handler and retries the original operation. Your original `callTool`, `getPrompt`, or `readResource` promise remains pending. + + + +```ts +export class MyAgent extends Agent { + onStart() { + this.mcp.configureElicitationHandlers({ + form: async (request, serverId, signal) => { + return collectInput(request, serverId, signal); + }, + url: async (request, serverId, signal) => { + return openExternalFlow(request, serverId, signal); + }, + }); + } +} +``` + + + +Handlers and in-flight calls remain in memory. Hibernation, isolate restart, transport loss, or connection reconstruction rejects an active interactive call. Retry the operation after the connection recovers. + +Treat manually handled `requestState` as untrusted input. Bind it to the authenticated user and operation, protect its integrity, and set a short expiry. + +### Update custom OAuth providers + +A custom `AgentMcpOAuthProvider` must implement the v2 `OAuthClientProvider` contract: + +- Import OAuth types from `@modelcontextprotocol/client`. +- Store `StoredOAuthClientInformation` and `StoredOAuthTokens`. +- Preserve the SDK issuer stamp on credentials. +- Persist `OAuthDiscoveryState` across browser redirects. +- Accept `"discovery"` in `invalidateCredentials`. +- Keep credentials separate when authorization issuers differ. + +SDK v2 validates OAuth metadata issuers by default. A trusted legacy server with known mismatched metadata can use `skipIssuerMetadataValidation: true`. This weakens OAuth mix-up protection and should not be a general fallback. + +## Review protocol differences + +The draft `2026-07-28` revision changes the transport and lifecycle model: + +| Area | Published 2025 behavior | Draft `2026-07-28` behavior | +| --------------------- | --------------------------------------------------- | ---------------------------------------------------------------------- | +| Startup | `initialize` handshake | No handshake; `server/discover` is optional for clients | +| Request metadata | Connection-scoped negotiation | Version, client capabilities, and identity metadata on each request | +| Sessions | Optional `Mcp-Session-Id` | No protocol session | +| Server input requests | Server sends JSON-RPC requests | Server returns `input_required`; client retries the original operation | +| Change notifications | Standalone GET stream and list-change notifications | `subscriptions/listen` POST with an SSE response | +| Stream recovery | `Last-Event-ID` can resume configured streams | Listen streams reopen after failure; no `Last-Event-ID` replay | + +Custom transports, proxies, and gateways must preserve the draft request headers: + +- `MCP-Protocol-Version` +- `Mcp-Method` +- `Mcp-Name` for tool, prompt, and resource operations +- Declared `Mcp-Param-*` tool headers + +The exact beta used by Agents is a snapshot of the draft. Beta.4 requires `io.modelcontextprotocol/clientInfo` and returns server identity in the `DiscoverResult` body. Later draft changes may alter those raw wire details. Use the high-level SDK and update MCP packages with the Agents release that supports a newer snapshot. Raw implementations must include `resultType` on beta.4 draft results. + +The draft deprecates Roots, Sampling, Logging, the old HTTP+SSE transport, and Dynamic Client Registration. The types remain available during the deprecation window for published 2025 compatibility. The published experimental task methods become the `io.modelcontextprotocol/tasks` extension. Agents SDK v0.18.0 does not add that extension. + +### Integration compatibility + +The following integrations retain SDK v1 server output in this release: + +- Current Code Mode `codeMcpServer` and `openApiMcpServer` helpers +- The server-side `withX402` helper +- Existing OpenAI Apps examples that import an SDK v1 `McpServer` + +Serve these results with `createLegacyMcpHandler`. The Code Mode MCP connector and `withX402Client` accept either client generation. + +## Plan the rollout + + + +1. Classify each endpoint as modern, explicit legacy, or `McpAgent`. + +2. Pin the MCP SDK versions required by the Agents release. + +3. Rename SDK v1 handler calls before changing their behavior. + +4. Add modern handlers beside existing sessionful routes. + +5. Test draft `2026-07-28` and published `2025-11-25` clients independently. + +6. Test required HTTP headers through every proxy and gateway. + +7. Test OAuth after a clean login and after Durable Object hibernation. + +8. Test cancellation, multiple input rounds, and transport loss. + +9. Verify valid Origins and reject invalid Origins with `403`. + +10. Remove legacy routes only after existing sessions drain. + + + +Stored HTTP session IDs from Agents releases before v0.18.0 do not include the negotiated protocol version. The upgraded client discards those IDs and reconnects instead of sending an unsafe resumed request. Existing in-flight work tied to an old remote session does not resume. + +For API details, refer to [`createMcpHandler`](/agents/model-context-protocol/apis/handler-api/) and [`McpClient`](/agents/model-context-protocol/apis/client-api/). diff --git a/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx b/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx index 1e78e89fc21..68595c2e4eb 100644 --- a/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx @@ -12,7 +12,7 @@ products: import { Details, Render, PackageManagers, LinkCard } from "~/components"; -This guide will show you how to deploy your own remote MCP server on Cloudflare using [Streamable HTTP transport](/agents/model-context-protocol/protocol/transport/), the current MCP specification standard. You have two options: +This guide shows how to deploy a remote MCP server on Cloudflare using [Streamable HTTP transport](/agents/model-context-protocol/protocol/transport/). You have two options: - **Without authentication** — anyone can connect and use the server (no login required). - **With [authentication and authorization](/agents/model-context-protocol/guides/remote-mcp-server/#add-authentication)** — users sign in before accessing tools, and you can control which tools an agent can call based on the user's permissions. @@ -21,15 +21,14 @@ This guide will show you how to deploy your own remote MCP server on Cloudflare The Agents SDK provides multiple ways to create MCP servers. Choose the approach that fits your use case: -| Approach | Stateful? | Requires Durable Objects? | Best for | -| -------------------------------------------------------------- | --------- | ------------------------- | ---------------------------------------------- | -| [`createMcpHandler()`](/agents/model-context-protocol/apis/handler-api/) | No | No | Stateless tools, simplest setup | -| [`McpAgent`](/agents/model-context-protocol/apis/agent-api/) | Yes | Yes | Stateful tools, per-session state, elicitation | -| Raw `WebStandardStreamableHTTPServerTransport` | No | No | Full control, no SDK dependency | +| Approach | Stateful? | Protocol path | Best for | +| ----------------------------------------------------------------------------------------------------- | -------------------- | ------------------------------------------------ | --------------------------------------- | +| [`createMcpHandler()`](/agents/model-context-protocol/apis/handler-api/) | No | Draft `2026-07-28`, with stateless 2025 fallback | New stateless tools | +| [`createLegacyMcpHandler()`](/agents/model-context-protocol/apis/handler-api/#createlegacymcphandler) | Optional | Published 2025 | Existing `WorkerTransport` servers | +| [`McpAgent`](/agents/model-context-protocol/apis/agent-api/) | Yes | Published 2025 | Existing Durable Object and RPC servers | +| Raw SDK transport | Depends on transport | Depends on SDK package | Custom transport ownership | -- **`createMcpHandler()`** is the fastest way to get a stateless MCP server running. Use it when your tools do not need per-session state. -- **`McpAgent`** gives you a Durable Object per session with built-in state management, elicitation support, and both SSE and Streamable HTTP transports. -- **Raw transport** gives you full control if you want to use the `@modelcontextprotocol/sdk` directly without the Agents SDK helpers. +Use `createMcpHandler` for a new stateless server. `McpAgent` remains available but is deprecated and feature-frozen. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) when updating an existing server. ## Deploy your first MCP server diff --git a/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx b/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx index 586c15644f1..9148c8b0c77 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx @@ -204,27 +204,40 @@ export class MyMCP extends McpAgent { ### With createMcpHandler -Use `getMcpAuthContext()` to access the same information from within a tool handler. This uses `AsyncLocalStorage` under the hood. +A compatible Workers OAuth Provider supplies standard token metadata at `context.http.authInfo`. Use `getMcpAuthContext()` for existing application props. ```ts import { createMcpHandler, getMcpAuthContext } from "agents/mcp"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; function createServer() { const server = new McpServer({ name: "Auth Demo", version: "1.0.0" }); - server.tool("whoami", "Get the current user", {}, async () => { - const auth = getMcpAuthContext(); - const name = (auth?.props?.name as string) ?? "anonymous"; - return { - content: [{ type: "text", text: `Hello, ${name}!` }], - }; - }); + server.registerTool( + "whoami", + { description: "Get the current user", inputSchema: {} }, + async (_args, context) => { + const auth = getMcpAuthContext(); + const name = (auth?.props.name as string) ?? "anonymous"; + return { + content: [ + { + type: "text", + text: `${name}: ${context.http?.authInfo?.clientId}`, + }, + ], + }; + }, + ); return server; } + +export default createMcpHandler(createServer); ``` +Do not log or return the raw access token. + ## Permission-based tool access You can control which tools are available based on user permissions. There are two approaches: check permissions inside the tool handler, or conditionally register tools. diff --git a/src/content/docs/agents/model-context-protocol/protocol/tools.mdx b/src/content/docs/agents/model-context-protocol/protocol/tools.mdx index 507c18060cb..55478606d2e 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/tools.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/tools.mdx @@ -12,9 +12,9 @@ products: import { TypeScriptExample, LinkCard } from "~/components"; -MCP tools are functions that an [MCP server](/agents/model-context-protocol/) exposes for clients to call. When an LLM decides it needs to take an action — look up data, run a calculation, call an API — it invokes a tool. The MCP server executes the tool and returns the result. +MCP tools are functions that an [MCP server](/agents/model-context-protocol/) exposes for clients to call. An LLM can invoke a tool to look up data, run a calculation, or call an API. The MCP server executes the tool and returns its result. -Tools are defined using the `@modelcontextprotocol/sdk` package. The Agents SDK handles transport and lifecycle; the tool definitions are the same regardless of whether you use [`createMcpHandler`](/agents/model-context-protocol/apis/handler-api/) or [`McpAgent`](/agents/model-context-protocol/apis/agent-api/). +Use `@modelcontextprotocol/server` for a modern `createMcpHandler` server. Existing `McpAgent` servers must keep using `@modelcontextprotocol/sdk`. :::note[Experimental WebMCP adapter] @@ -30,21 +30,23 @@ The Agents SDK also includes the experimental `agents/experimental/webmcp` adapt ## Defining tools -Use `server.tool()` to register a tool on an `McpServer` instance. Each tool has a name, a description (used by the LLM to decide when to call it), an input schema defined with [Zod](https://zod.dev), and a handler function. +Use `server.registerTool()` to register a tool on a modern `McpServer` instance. Each tool has a name, a description, an input schema defined with [Zod](https://zod.dev), and a handler function. ```ts -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; import { z } from "zod"; function createServer() { const server = new McpServer({ name: "Math", version: "1.0.0" }); - server.tool( + server.registerTool( "add", - "Add two numbers together", - { a: z.number(), b: z.number() }, + { + description: "Add two numbers together", + inputSchema: { a: z.number(), b: z.number() }, + }, async ({ a, b }) => ({ content: [{ type: "text", text: String(a + b) }], }), @@ -65,10 +67,12 @@ Tool results are returned as an array of content parts. The most common type is ```ts -server.tool( +server.registerTool( "lookup", - "Look up a user by ID", - { userId: z.string() }, + { + description: "Look up a user by ID", + inputSchema: { userId: z.string() }, + }, async ({ userId }) => { const user = await db.getUser(userId); @@ -105,21 +109,23 @@ Tool inputs are defined as Zod schemas and validated automatically before the ha ```ts -server.tool( +server.registerTool( "search", - "Search for documents by query", { - query: z.string().describe("The search query"), - limit: z - .number() - .min(1) - .max(100) - .default(10) - .describe("Maximum number of results to return"), - category: z - .enum(["docs", "blog", "api"]) - .optional() - .describe("Filter by content category"), + description: "Search for documents by query", + inputSchema: { + query: z.string().describe("The search query"), + limit: z + .number() + .min(1) + .max(100) + .default(10) + .describe("Maximum number of results to return"), + category: z + .enum(["docs", "blog", "api"]) + .optional() + .describe("Filter by content category"), + }, }, async ({ query, limit, category }) => { const results = await searchIndex(query, { limit, category }); @@ -140,25 +146,24 @@ For stateless MCP servers, define tools inside a factory function and pass the s ```ts import { createMcpHandler } from "agents/mcp"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; import { z } from "zod"; function createServer() { const server = new McpServer({ name: "My Tools", version: "1.0.0" }); - server.tool("ping", "Check if the server is alive", {}, async () => ({ - content: [{ type: "text", text: "pong" }], - })); + server.registerTool( + "ping", + { description: "Check if the server is alive", inputSchema: {} }, + async () => ({ + content: [{ type: "text", text: "pong" }], + }), + ); return server; } -export default { - fetch: (request: Request, env: Env, ctx: ExecutionContext) => { - const server = createServer(); - return createMcpHandler(server)(request, env, ctx); - }, -} satisfies ExportedHandler; +export default createMcpHandler(createServer); ``` diff --git a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx index c657f14078e..69d230a0f28 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx @@ -41,7 +41,7 @@ Create an MCP server using `createMcpHandler`. View the [complete example on Git ```ts import { createMcpHandler } from "agents/mcp"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; import { z } from "zod"; function createServer() { @@ -66,13 +66,7 @@ function createServer() { return server; } -export default { - fetch: (request: Request, env: Env, ctx: ExecutionContext) => { - // Create a new server instance per request - const server = createServer(); - return createMcpHandler(server)(request, env, ctx); - }, -} satisfies ExportedHandler; +export default createMcpHandler(createServer); ``` @@ -86,26 +80,20 @@ If your MCP server implements authentication & authorization using the [Workers ```ts export default new OAuthProvider({ apiRoute: "/mcp", - apiHandler: { - fetch: (request: Request, env: Env, ctx: ExecutionContext) => { - // Create a new server instance per request - const server = createServer(); - return createMcpHandler(server)(request, env, ctx); - }, - }, + apiHandler: createMcpHandler(createServer), // ... other OAuth configuration }); ``` -### Stateful MCP servers +### Stateful published 2025 servers -If your MCP server needs to maintain state across requests, use `createMcpHandler` with a `WorkerTransport` inside an [Agent](/agents/) class. This allows you to persist session state in Durable Object storage and use advanced MCP features like [elicitation](https://modelcontextprotocol.io/specification/draft/client/elicitation) and [sampling](https://modelcontextprotocol.io/specification/draft/client/sampling). +The draft `2026-07-28` protocol has no protocol-level session. Applications can store durable business data behind a separate storage boundary. -See [Stateful MCP Servers](/agents/model-context-protocol/apis/handler-api/#stateful-mcp-servers) for implementation details. +Existing servers that require published 2025 sessions can use `createLegacyMcpHandler` with `WorkerTransport`, or keep `McpAgent` temporarily. These APIs support transport state, event replay, pushed elicitation, sampling, and roots requests. `McpAgent` is deprecated and feature-frozen. -Streamable HTTP streams are resumable: configure an `EventStore` so clients can reconnect with a `Last-Event-ID` header and replay missed events, keeping in-flight tool calls alive across the edge idle-stream watchdog. `DurableObjectEventStore` is exported from `agents/mcp` for stateful `WorkerTransport` callers. Refer to [`McpAgent`: Stream resumability](/agents/model-context-protocol/apis/agent-api/#stream-resumability). +Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) before changing a stateful endpoint. Refer to [`McpAgent`: Stream resumability](/agents/model-context-protocol/apis/agent-api/#stream-resumability) for existing stream behavior. ## RPC transport @@ -203,15 +191,15 @@ In your `wrangler.jsonc`, define bindings for both Durable Objects: "durable_objects": { "bindings": [ { "name": "Chat", "class_name": "Chat" }, - { "name": "MyMCP", "class_name": "MyMCP" } - ] + { "name": "MyMCP", "class_name": "MyMCP" }, + ], }, "migrations": [ { "new_sqlite_classes": ["MyMCP", "Chat"], - "tag": "v1" - } - ] + "tag": "v1", + }, + ], } ``` @@ -273,9 +261,7 @@ export class MyMCP extends McpAgent< const role = this.props?.role || "guest"; return { - content: [ - { type: "text", text: `User ID: ${userId}, Role: ${role}` }, - ], + content: [{ type: "text", text: `User ID: ${userId}, Role: ${role}` }], }; }); } @@ -320,19 +306,19 @@ export class MyMCP extends McpAgent { ## Choosing a transport -| Transport | Use when | Pros | Cons | -| ------------------- | ------------------------------------- | ---------------------------------------- | ------------------------------- | -| **Streamable HTTP** | External MCP servers, production apps | Standard protocol, secure, supports auth | Slight network overhead | +| Transport | Use when | Pros | Cons | +| ------------------- | ------------------------------------- | ---------------------------------------- | ------------------------------------- | +| **Streamable HTTP** | External MCP servers, production apps | Standard protocol, secure, supports auth | Slight network overhead | | **RPC** | Internal agents on Cloudflare | Fastest, simplest setup | No auth, Durable Object bindings only | -| **SSE** | Legacy compatibility | Backwards compatible | Deprecated, use Streamable HTTP | +| **SSE** | Legacy compatibility | Backwards compatible | Deprecated, use Streamable HTTP | + +### Migrate from McpAgent -### Migrating from McpAgent +Move stateless tools to a server factory from `@modelcontextprotocol/server`, then pass that factory to `createMcpHandler`. -If you have an existing MCP server using the `McpAgent` class: +Keep `McpAgent` temporarily when an endpoint needs Durable Object state, RPC, or published 2025 sessions. It remains available but is deprecated and feature-frozen. -- **Not using state?** Replace your `McpAgent` class with `McpServer` from `@modelcontextprotocol/sdk` and use `createMcpHandler(server)` in a Worker `fetch` handler. -- **Using state?** Use `createMcpHandler` with a `WorkerTransport` inside an [Agent](/agents/) class. See [Stateful MCP Servers](/agents/model-context-protocol/apis/handler-api/#stateful-mcp-servers) for details. -- **Need SSE support?** Continue using `McpAgent` with `serveSSE()` for legacy client compatibility. See the [McpAgent API reference](/agents/model-context-protocol/apis/agent-api/). +Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for dual-era routing, state migration, and rollout steps. ### Testing with MCP clients From f59a3ea4570cd60582ac92601a604a119670fb41 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Mon, 20 Jul 2026 18:42:57 +0100 Subject: [PATCH 02/16] fix: address PR #32175 review feedback - clarify stateless MCP 2026-07-28 release naming\n- tighten McpAgent migration guidance\n- remove unnecessary deprecation and beta caveats --- ...26-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx | 6 ++---- .../guides/migrate-to-mcp-sdk-v2.mdx | 20 +++++++++---------- .../protocol/transport.mdx | 4 ++-- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/content/changelog/agents/2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx b/src/content/changelog/agents/2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx index 6eda1583a70..25857d852eb 100644 --- a/src/content/changelog/agents/2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx +++ b/src/content/changelog/agents/2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx @@ -1,5 +1,5 @@ --- -title: "Agents SDK v0.18.0: MCP SDK v2 support" +title: "Agents SDK v0.18.0: stateless MCP 2026-07-28 by default" description: "Agents SDK v0.18.0 adds MCP SDK v2 clients and stateless servers while retaining explicit support for published 2025 protocol deployments." products: - agents @@ -53,7 +53,7 @@ export default createLegacyMcpHandler( -`createLegacyMcpHandler` and `WorkerTransport` are not deprecated. Passing an SDK v1 server directly to `createMcpHandler` is deprecated. `McpAgent` remains available for stateful 2025 deployments, but it is deprecated and feature-frozen. +Passing an SDK v1 server directly to `createMcpHandler` is deprecated. `McpAgent` remains available for stateful 2025 deployments, but it is deprecated and feature-frozen. ## Client negotiation and input requests @@ -69,6 +69,4 @@ To update the Agents SDK: -The MCP SDK v2 packages remain in beta. Applications that import them directly should use the exact versions required by their installed Agents release. - Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for package changes, compatibility limits, and rollout steps. diff --git a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx index 8f34bad7065..689d2103d21 100644 --- a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx @@ -24,15 +24,15 @@ The v2 packages implement the draft `2026-07-28` revision. They also support the Use the following table to select a migration path: -| Current server | Migration path | -| --------------------------------------------------------- | --------------------------------------------------------------------------------------- | -| `createMcpHandler` with an SDK v1 server | Rename the call to `createLegacyMcpHandler` to keep the same behavior. | -| Stateless server ready for draft `2026-07-28` | Move to `@modelcontextprotocol/server` and pass a factory to `createMcpHandler`. | -| Server that must support published specifications only | Keep SDK v1 and use `createLegacyMcpHandler`. | -| `McpAgent` using state, props, RPC, or pushed elicitation | Keep `McpAgent` temporarily. It remains available but is deprecated and feature-frozen. | -| `McpAgent` without per-session state | Move its tools to an SDK v2 factory and use `createMcpHandler`. | +| Current server | Migration path | +| ------------------------------------------------------ | -------------------------------------------------------------------------------- | +| `createMcpHandler` with an SDK v1 server | Rename the call to `createLegacyMcpHandler` to keep the same behavior. | +| Stateless server ready for draft `2026-07-28` | Move to `@modelcontextprotocol/server` and pass a factory to `createMcpHandler`. | +| Server that must support published specifications only | Keep SDK v1 and use `createLegacyMcpHandler`. | +| `McpAgent` using legacy stateful operations | Keep it only while those operations are required, and plan a migration. | +| `McpAgent` without legacy stateful operations | Migrate when convenient to stateless MRTR and future MCP features. | -`createLegacyMcpHandler` and `WorkerTransport` are not deprecated. The following APIs are deprecated: +The following APIs are deprecated: - Passing an SDK v1 server to `createMcpHandler` - `experimental_createMcpHandler` @@ -227,9 +227,9 @@ Use `createLegacyMcpHandler` or `McpAgent` when a 2025 client needs those featur `McpAgent` remains an SDK v1 server. Do not change its server import to `@modelcontextprotocol/server`. -Keep `McpAgent` temporarily if tools use `this.state`, `this.props`, Durable Object hibernation, RPC, pushed server-to-client requests, or session replay. +Keep `McpAgent` only when the endpoint depends on legacy stateful operations, such as application state tied to the MCP session, RPC, pushed server-to-client requests, or session replay. -To migrate fully, move application state outside the MCP protocol session. A tool can use authenticated server-issued handles to reach a Durable Object, D1, KV, or R2. Then expose the tool surface through an SDK v2 factory. +If the endpoint does not require those operations, migrate when convenient to use stateless MRTR and future MCP features. Move durable application state outside the MCP protocol session. A tool can use authenticated server-issued handles to reach a Durable Object, D1, KV, or R2. Then expose the tool surface through an SDK v2 factory. ### Run modern and legacy lanes together diff --git a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx index 69d230a0f28..aadd8605cd2 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx @@ -314,9 +314,9 @@ export class MyMCP extends McpAgent { ### Migrate from McpAgent -Move stateless tools to a server factory from `@modelcontextprotocol/server`, then pass that factory to `createMcpHandler`. +Keep `McpAgent` only when an endpoint depends on legacy stateful operations, such as state tied to an MCP session, RPC, pushed server-to-client requests, or stream replay. -Keep `McpAgent` temporarily when an endpoint needs Durable Object state, RPC, or published 2025 sessions. It remains available but is deprecated and feature-frozen. +If the endpoint does not require those operations, migrate when convenient to a stateless server factory from `@modelcontextprotocol/server` and pass it to `createMcpHandler`. The stateless path supports modern multi-round-trip requests (MRTR) and will receive future MCP features. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for dual-era routing, state migration, and rollout steps. From 8d064be653c659d237dd967fa53c2399de047ab6 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Mon, 20 Jul 2026 18:49:55 +0100 Subject: [PATCH 03/16] docs(agents): clarify stateful MCP migration --- .../model-context-protocol/apis/agent-api.mdx | 8 +++-- .../apis/handler-api.mdx | 2 +- .../guides/migrate-to-mcp-sdk-v2.mdx | 35 +++++++++++++------ .../guides/remote-mcp-server.mdx | 2 +- .../protocol/transport.mdx | 10 +++--- 5 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx b/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx index 060d490dc88..6965b97ea80 100644 --- a/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx @@ -16,11 +16,13 @@ import { TypeScriptExample, LinkCard } from "~/components"; :::caution[Deprecated] -`McpAgent` remains available for existing servers that need Durable Object state, RPC, protocol sessions, or pushed server-to-client requests. It is deprecated and feature-frozen. New stateless servers should use [`createMcpHandler`](/agents/model-context-protocol/apis/handler-api/). +`McpAgent` remains available for published 2025 servers that use legacy stateful features. It is deprecated and feature-frozen. A server without those dependencies can migrate directly to [`createMcpHandler`](/agents/model-context-protocol/apis/handler-api/). -Keep importing its `McpServer` from `@modelcontextprotocol/sdk`. An SDK v2 server from `@modelcontextprotocol/server` cannot run inside `McpAgent`. +A server that depends on MCP session state, RPC, pushed server-to-client requests, standalone streams, or replay needs a staged migration. Design stateless equivalents, add a modern route, and serve both eras until clients migrate and existing sessions drain. -Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) before changing an existing deployment. +Keep importing the legacy server's `McpServer` from `@modelcontextprotocol/sdk`. An SDK v2 server from `@modelcontextprotocol/server` cannot run inside `McpAgent`. + +Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the feature mapping and dual-era rollout. ::: diff --git a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx index 277da10c17d..4fb23ba0d36 100644 --- a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx @@ -19,7 +19,7 @@ The `agents/mcp` entry point provides two Worker handler APIs: | `createMcpHandler` | `@modelcontextprotocol/server` | Draft `2026-07-28` with stateless 2025 compatibility by default | | `createLegacyMcpHandler` | `@modelcontextprotocol/sdk` | Published 2025 protocol behavior through `WorkerTransport` | -`McpAgent` also remains available for stateful 2025 servers. It is deprecated and feature-frozen. Refer to the [`McpAgent` API](/agents/model-context-protocol/apis/agent-api/) for its existing behavior. +`McpAgent` remains available for stateful 2025 servers while their stateless equivalents are built. It is deprecated and feature-frozen. Refer to the [migration guide](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for dual-era rollout guidance. :::note[SDK version and protocol version] diff --git a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx index 689d2103d21..73bddffaa08 100644 --- a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx @@ -24,13 +24,13 @@ The v2 packages implement the draft `2026-07-28` revision. They also support the Use the following table to select a migration path: -| Current server | Migration path | -| ------------------------------------------------------ | -------------------------------------------------------------------------------- | -| `createMcpHandler` with an SDK v1 server | Rename the call to `createLegacyMcpHandler` to keep the same behavior. | -| Stateless server ready for draft `2026-07-28` | Move to `@modelcontextprotocol/server` and pass a factory to `createMcpHandler`. | -| Server that must support published specifications only | Keep SDK v1 and use `createLegacyMcpHandler`. | -| `McpAgent` using legacy stateful operations | Keep it only while those operations are required, and plan a migration. | -| `McpAgent` without legacy stateful operations | Migrate when convenient to stateless MRTR and future MCP features. | +| Current server | Migration path | +| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | +| `createMcpHandler` with an SDK v1 server | Rename the call to `createLegacyMcpHandler` to keep the same behavior. | +| Stateless server ready for draft `2026-07-28` | Move to `@modelcontextprotocol/server` and pass a factory to `createMcpHandler`. | +| Server that must support published specifications only | Keep SDK v1 and use `createLegacyMcpHandler`. | +| `McpAgent` without legacy stateful features | Migrate directly to an SDK v2 factory and `createMcpHandler`. | +| `McpAgent` using legacy stateful features | Design stateless equivalents, serve modern and legacy lanes together, then drain the legacy lane. | The following APIs are deprecated: @@ -227,9 +227,24 @@ Use `createLegacyMcpHandler` or `McpAgent` when a 2025 client needs those featur `McpAgent` remains an SDK v1 server. Do not change its server import to `@modelcontextprotocol/server`. -Keep `McpAgent` only when the endpoint depends on legacy stateful operations, such as application state tied to the MCP session, RPC, pushed server-to-client requests, or session replay. +### Migrate directly when the server does not use legacy stateful features -If the endpoint does not require those operations, migrate when convenient to use stateless MRTR and future MCP features. Move durable application state outside the MCP protocol session. A tool can use authenticated server-issued handles to reach a Durable Object, D1, KV, or R2. Then expose the tool surface through an SDK v2 factory. +If the server does not depend on MCP session state, RPC, pushed server-to-client requests, standalone streams, or event replay, move its tools to an SDK v2 factory and serve it with `createMcpHandler`. + +### Plan stateless equivalents for stateful features + +If the server uses legacy stateful features, keep the existing `McpAgent` route while you design and deploy stateless equivalents: + +| Legacy stateful feature | Stateless design | +| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Application data keyed by an MCP session | Store data behind an explicit application boundary such as a Durable Object, D1, KV, or R2. Address it with an authenticated, server-issued handle instead of an MCP session ID. | +| Multi-step interaction state | Return integrity-protected `requestState` with `input_required`. Bind it to the authenticated user, original method and parameters, and an expiry. | +| Pushed elicitation, sampling, or roots requests | Return `inputRequired(...)`. The client fulfils the embedded requests and retries the original operation. | +| Standalone list-change stream | Publish changes through `subscriptions/listen`. Clients reopen the subscription if its stream ends. | +| Session replay or transport recovery | Make each modern request independently recoverable. Persist business progress in application storage rather than the MCP transport. | +| Agent-to-`McpAgent` RPC | Replace the protocol-session dependency with an explicit application RPC or HTTP boundary, then expose the stateless MCP tools separately. | + +Do not remove the legacy route as soon as the modern implementation exists. Serve both eras while clients migrate and existing 2025 sessions drain. ### Run modern and legacy lanes together @@ -262,7 +277,7 @@ export default { Keep `legacy: "reject"` on the modern handler. Otherwise, its stateless fallback consumes 2025 traffic before the sessionful route receives it. -Deploy both routes before removing a Durable Object binding. Let existing sessions drain, then handle Durable Object migration configuration separately. +Deploy both routes before moving clients. Monitor the legacy lane and let existing sessions drain. Remove the old route and its protocol-only Durable Object binding only after no clients depend on them. Handle Durable Object migration configuration as a separate deployment step. ## Update MCP clients diff --git a/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx b/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx index 68595c2e4eb..947e29098c2 100644 --- a/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx @@ -28,7 +28,7 @@ The Agents SDK provides multiple ways to create MCP servers. Choose the approach | [`McpAgent`](/agents/model-context-protocol/apis/agent-api/) | Yes | Published 2025 | Existing Durable Object and RPC servers | | Raw SDK transport | Depends on transport | Depends on SDK package | Custom transport ownership | -Use `createMcpHandler` for a new stateless server. `McpAgent` remains available but is deprecated and feature-frozen. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) when updating an existing server. +Use `createMcpHandler` for a new stateless server. An existing `McpAgent` without legacy stateful dependencies can migrate directly. If it uses MCP session state, RPC, pushed requests, streams, or replay, plan the stateless equivalents and serve both eras during the transition. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the staged rollout. ## Deploy your first MCP server diff --git a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx index aadd8605cd2..fac4675c602 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx @@ -91,9 +91,9 @@ export default new OAuthProvider({ The draft `2026-07-28` protocol has no protocol-level session. Applications can store durable business data behind a separate storage boundary. -Existing servers that require published 2025 sessions can use `createLegacyMcpHandler` with `WorkerTransport`, or keep `McpAgent` temporarily. These APIs support transport state, event replay, pushed elicitation, sampling, and roots requests. `McpAgent` is deprecated and feature-frozen. +Existing servers that require published 2025 sessions can use `createLegacyMcpHandler` with `WorkerTransport`, or keep `McpAgent` while their stateless equivalents are built. These APIs support transport state, event replay, pushed elicitation, sampling, and roots requests. `McpAgent` is deprecated and feature-frozen. -Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) before changing a stateful endpoint. Refer to [`McpAgent`: Stream resumability](/agents/model-context-protocol/apis/agent-api/#stream-resumability) for existing stream behavior. +Add the modern route before moving clients, and keep both lanes until existing sessions drain. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the staged migration. Refer to [`McpAgent`: Stream resumability](/agents/model-context-protocol/apis/agent-api/#stream-resumability) for existing stream behavior. ## RPC transport @@ -314,11 +314,11 @@ export class MyMCP extends McpAgent { ### Migrate from McpAgent -Keep `McpAgent` only when an endpoint depends on legacy stateful operations, such as state tied to an MCP session, RPC, pushed server-to-client requests, or stream replay. +If the endpoint does not use legacy stateful features, migrate directly to a stateless server factory from `@modelcontextprotocol/server` and pass it to `createMcpHandler`. -If the endpoint does not require those operations, migrate when convenient to a stateless server factory from `@modelcontextprotocol/server` and pass it to `createMcpHandler`. The stateless path supports modern multi-round-trip requests (MRTR) and will receive future MCP features. +If it depends on MCP session state, RPC, pushed server-to-client requests, standalone streams, or replay, first design stateless equivalents. For example, move business state behind explicit application storage and replace pushed input requests with multi-round-trip `input_required` results. Serve modern and legacy lanes together while clients migrate and existing sessions drain. -Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for dual-era routing, state migration, and rollout steps. +Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the feature mapping, dual-era routing, and rollout steps. ### Testing with MCP clients From 6b2753839253a6fe8e6ef0f2c84ec33ecd3025c2 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Wed, 22 Jul 2026 14:22:25 +0100 Subject: [PATCH 04/16] docs(agents): align MCP v2 migration with stateless APIs --- ...26-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx | 24 ++-- .../model-context-protocol/apis/agent-api.mdx | 16 +-- .../apis/client-api.mdx | 22 +-- .../apis/handler-api.mdx | 88 ++++++------ .../guides/build-codemode-mcp-server.mdx | 2 + .../build-codemode-openapi-mcp-server.mdx | 2 + .../guides/migrate-to-mcp-sdk-v2.mdx | 128 +++++++++--------- .../guides/remote-mcp-server.mdx | 16 +-- .../protocol/authorization.mdx | 4 +- .../model-context-protocol/protocol/tools.mdx | 8 +- .../protocol/transport.mdx | 14 +- 11 files changed, 165 insertions(+), 159 deletions(-) diff --git a/src/content/changelog/agents/2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx b/src/content/changelog/agents/2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx index 25857d852eb..0c7a85cea30 100644 --- a/src/content/changelog/agents/2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx +++ b/src/content/changelog/agents/2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx @@ -1,6 +1,6 @@ --- -title: "Agents SDK v0.18.0: stateless MCP 2026-07-28 by default" -description: "Agents SDK v0.18.0 adds MCP SDK v2 clients and stateless servers while retaining explicit support for published 2025 protocol deployments." +title: "Agents SDK v0.18.0: Stateless MCP SDK v2 support" +description: "Agents SDK v0.18.0 adds Stateless MCP SDK v2 clients and servers while retaining explicit Legacy support." products: - agents - workers @@ -9,11 +9,11 @@ date: 2026-07-20 import { PackageManagers, TypeScriptExample } from "~/components"; -Agents SDK v0.18.0 adds support for the split MCP TypeScript SDK v2 packages. Agents can serve the draft `2026-07-28` protocol, negotiate between modern and published 2025 servers, and handle modern multi-round-trip input requests. +Agents SDK v0.18.0 adds support for the split MCP TypeScript SDK v2 packages. Agents can serve Stateless MCP, fall back to Legacy servers, and handle Stateless Elicitation. -Existing 2025 server deployments remain supported through `createLegacyMcpHandler`, `WorkerTransport`, and `McpAgent`. +Existing Legacy server deployments remain supported through `createLegacyMcpHandler`, `WorkerTransport`, and `McpAgent`. -## Stateless MCP SDK v2 servers +## Stateless servers `createMcpHandler` now accepts a factory that returns a server from `@modelcontextprotocol/server`. The factory creates an isolated server for each request. @@ -21,7 +21,7 @@ Existing 2025 server deployments remain supported through `createLegacyMcpHandle ```ts import { McpServer } from "@modelcontextprotocol/server"; -import { createMcpHandler } from "agents/mcp"; +import { createMcpHandler } from "agents/mcp/server"; function createServer() { return new McpServer({ name: "example", version: "1.0.0" }); @@ -32,11 +32,11 @@ export default createMcpHandler(createServer); -The handler serves draft `2026-07-28` requests. Its default stateless fallback also supports ordinary tools, resources, and prompts from published 2025 clients. Session streams, replay, deletion, and pushed server-to-client requests still require a 2025 sessionful server. +The isolated `agents/mcp/server` entry keeps `McpAgent`, `WorkerTransport`, MCP client transports, and SDK v1 modules out of Stateless server bundles. Its Legacy compatibility lane supports ordinary tools, resources, and prompts. Session streams, replay, deletion, and pushed server-to-client requests still require a Legacy sessionful server. -The Workers wrapper validates present browser Origins, allows the modern `Mcp-Method` and `Mcp-Name` CORS headers, and exposes the upstream handler's `close`, `notify`, and `bus` controls. +The Workers wrapper validates present browser Origins, supports explicit delegation to trusted Origin middleware, and exposes the upstream handler's `close`, `notify`, and `bus` controls. -## Explicit 2025 server support +## Explicit Legacy support Existing SDK v1 servers can rename `createMcpHandler` to `createLegacyMcpHandler` without changing their transport behavior: @@ -53,13 +53,13 @@ export default createLegacyMcpHandler( -Passing an SDK v1 server directly to `createMcpHandler` is deprecated. `McpAgent` remains available for stateful 2025 deployments, but it is deprecated and feature-frozen. +Passing an SDK v1 server directly to `createMcpHandler` is deprecated. `McpAgent` remains available for stateful Legacy deployments, but it is deprecated and feature-frozen. ## Client negotiation and input requests -The MCP client manager now uses `@modelcontextprotocol/client`. It probes modern servers with `server/discover` and falls back to the published `initialize` handshake. +The MCP client manager now uses `@modelcontextprotocol/client`. It probes Stateless servers with `server/discover` and falls back to the Legacy `initialize` handshake. -Modern `input_required` results use the same form and URL elicitation handlers as pushed 2025 elicitation. The SDK collects input, retries the original operation, and resolves the original `callTool`, `getPrompt`, or `readResource` promise with its final result. +Stateless Elicitation uses `input_required` through multi-round-trip requests (MRTR). Legacy Elicitation uses the same form and URL handlers for pushed requests. The SDK collects input, retries the original operation, and resolves the original `callTool`, `getPrompt`, or `readResource` promise with its final result. OAuth callbacks now validate issuer metadata through the v2 SDK. Discovery state and issuer-bound credentials persist across browser redirects and Durable Object hibernation. diff --git a/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx b/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx index 6965b97ea80..00f5fa61886 100644 --- a/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx @@ -12,15 +12,15 @@ products: import { TypeScriptExample, LinkCard } from "~/components"; -`McpAgent` creates a stateful published 2025 MCP server backed by a Durable Object. +`McpAgent` creates a stateful Legacy MCP server backed by a Durable Object. :::caution[Deprecated] -`McpAgent` remains available for published 2025 servers that use legacy stateful features. It is deprecated and feature-frozen. A server without those dependencies can migrate directly to [`createMcpHandler`](/agents/model-context-protocol/apis/handler-api/). +`McpAgent` remains available for Legacy servers that use stateful features. It is deprecated and feature-frozen. A server without those dependencies can migrate directly to [`createMcpHandler`](/agents/model-context-protocol/apis/handler-api/). -A server that depends on MCP session state, RPC, pushed server-to-client requests, standalone streams, or replay needs a staged migration. Design stateless equivalents, add a modern route, and serve both eras until clients migrate and existing sessions drain. +A server that depends on MCP session state, RPC, pushed server-to-client requests, standalone streams, or replay needs a staged migration. Design Stateless equivalents, add a Stateless route, and serve both lanes until clients migrate and existing sessions drain. -Keep importing the legacy server's `McpServer` from `@modelcontextprotocol/sdk`. An SDK v2 server from `@modelcontextprotocol/server` cannot run inside `McpAgent`. +Keep importing the Legacy server `McpServer` from `@modelcontextprotocol/sdk`. An SDK v2 server from `@modelcontextprotocol/server` cannot run inside `McpAgent`. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the feature mapping and dual-era rollout. @@ -52,7 +52,7 @@ export class MyMCP extends McpAgent { This means that each instance of your MCP server has its own durable state, backed by a [Durable Object](/durable-objects/), with its own [SQL database](/agents/runtime/lifecycle/state/). -A stateless modern server can define [tools](/agents/model-context-protocol/protocol/tools/) with `@modelcontextprotocol/server` and serve them through `createMcpHandler`. +A Stateless server can define [tools](/agents/model-context-protocol/protocol/tools/) with `@modelcontextprotocol/server` and serve them through `createMcpHandler`. But if you want your MCP server to: @@ -269,9 +269,9 @@ export class MyMCP extends McpAgent { -## Elicitation +## Legacy Elicitation -[MCP elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation) lets a server request user input while handling another request, such as a tool call. The current stable MCP specification defines two modes: +[MCP elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation) lets a server request user input while handling another request, such as a tool call. Legacy Elicitation defines two modes: - **Form mode** collects structured, non-sensitive data through the client. - **URL mode** sends the user to an out-of-band interaction, such as third-party authorization or payment. @@ -383,7 +383,7 @@ switch (result.action) { :::note[MCP client support] -Not all MCP clients implement elicitation. Check the client before depending on it and provide a fallback when appropriate. Agents acting as MCP clients can handle both modes through [MCP client elicitation handlers](/agents/model-context-protocol/apis/client-api/#elicitation). +Not all MCP clients implement Legacy Elicitation. Check the client before depending on it and provide a fallback when appropriate. Agents acting as MCP clients can handle both modes through [MCP client elicitation handlers](/agents/model-context-protocol/apis/client-api/#elicitation). ::: For more human-in-the-loop patterns, refer to [Human-in-the-loop patterns](/agents/concepts/agentic-patterns/human-in-the-loop/). diff --git a/src/content/docs/agents/model-context-protocol/apis/client-api.mdx b/src/content/docs/agents/model-context-protocol/apis/client-api.mdx index 3e71c967ede..57bdb77955c 100644 --- a/src/content/docs/agents/model-context-protocol/apis/client-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/client-api.mdx @@ -12,7 +12,7 @@ products: import { Render, TypeScriptExample, LinkCard } from "~/components"; -Connect your agent to external [Model Context Protocol (MCP)](/agents/model-context-protocol/) servers to use their tools, resources, and prompts. Agents SDK v0.18.0 uses `@modelcontextprotocol/client` and negotiates draft `2026-07-28` or published 2025 protocol behavior automatically. +Connect your agent to external [Model Context Protocol (MCP)](/agents/model-context-protocol/) servers to use their tools, resources, and prompts. Agents SDK v0.18.0 uses `@modelcontextprotocol/client` and negotiates Stateless or Legacy behavior automatically. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for package, type, OAuth provider, and rollout changes. @@ -395,15 +395,15 @@ for (const prompt of state.prompts) { ### Elicitation -[MCP elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation) lets a server request user input while handling another request, such as a tool call. The current stable MCP specification defines form and URL modes. +MCP servers can request user input while handling another operation. Stateless Elicitation returns `input_required` and completes through multi-round-trip requests (MRTR). Legacy Elicitation sends pushed `elicitation/create` requests. Both use form and URL modes. -Register a handler for each mode your Agent supports in `onStart()`: +Register a handler for each mode your Agent supports in `onStart()`. The same handlers serve both lanes: ```ts import { Agent } from "agents"; -import type { ElicitRequest, ElicitResult } from "agents/mcp"; +import type { ElicitRequest, ElicitResult } from "agents/mcp/client"; class MyAgent extends Agent { onStart() { @@ -434,7 +434,7 @@ The `serverId` identifies the connection that sent the request. Use it to tell t #### Capability negotiation and hibernation -At the MCP `initialize` handshake, a connection advertises only the modes with configured handlers. A form-only handler advertises form mode. A URL-only handler advertises URL mode. A connection without handlers advertises no elicitation capability, which lets the server use its fallback. +The SDK advertises only the modes with configured handlers. Legacy connections advertise them during `initialize`. Stateless requests carry them with request capabilities. A form-only handler advertises form mode. A URL-only handler advertises URL mode. A connection without handlers advertises no elicitation capability, which lets the server use its fallback. The SDK stores the advertised modes with each server registration. A connection restored after Durable Object hibernation can therefore advertise the same modes when it reconnects. Callback functions remain in memory and reattach when `onStart()` runs. @@ -512,7 +512,7 @@ A handler returns a promise, but the response often comes from a browser. Broadc ```ts import { Agent, callable } from "agents"; -import type { ElicitRequest, ElicitResult } from "agents/mcp"; +import type { ElicitRequest, ElicitResult } from "agents/mcp/client"; type PendingResolver = { resolve: (result: ElicitResult) => void; @@ -572,9 +572,9 @@ class MyAgent extends Agent { The example uses a 55-second timeout because MCP SDK requests default to 60 seconds. If your client call sets a longer request timeout, adjust this timeout to finish first. -Refer to the [`mcp-client` example](https://github.com/cloudflare/agents/tree/main/examples/mcp-client) for the browser implementation. The [`mcp-elicitation` example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) is a server that sends both modes. +Refer to the [`mcp-client` example](https://github.com/cloudflare/agents/tree/main/examples/mcp-client) for the browser implementation. [`mcp-elicitation-mrtr`](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) demonstrates Stateless Elicitation. [`mcp-elicitation`](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) demonstrates Legacy Elicitation. -To send elicitation requests from an MCP server, refer to [`elicitInput`](/agents/model-context-protocol/apis/agent-api/#elicitinputoptions-context). +For server-side patterns, refer to [Stateless Elicitation](/agents/model-context-protocol/apis/handler-api/#stateless-elicitation) and [Legacy Elicitation](/agents/model-context-protocol/apis/agent-api/#legacy-elicitation). ## Managing servers @@ -872,7 +872,7 @@ export class MyAgent extends Agent { ### `configureElicitationHandlers()` -Configure handlers for server-initiated `elicitation/create` requests. Add a handler for each elicitation mode your Agent supports. +Configure handlers for Stateless Elicitation and Legacy `elicitation/create` requests. Add a handler for each elicitation mode your Agent supports. ```txt this.mcp.configureElicitationHandlers(handlers?: { @@ -901,7 +901,7 @@ Passing `undefined` clears all configured handlers. #### Capability behavior -The client advertises only modes with configured handlers during the MCP `initialize` handshake. Handler changes apply immediately to live connections, but servers receive updated advertised modes after those connections reconnect. +The client advertises only modes with configured handlers during Legacy negotiation and on Stateless requests. Handler changes apply immediately to live connections, but servers receive updated advertised modes after those connections reconnect. The SDK stores the handler-derived modes with each MCP server registration. Restored connections advertise those modes after Durable Object hibernation, and callbacks reattach when `onStart()` runs. @@ -913,7 +913,7 @@ Configure handlers in `onStart()`: ```ts import { Agent } from "agents"; -import type { ElicitRequest, ElicitResult } from "agents/mcp"; +import type { ElicitRequest, ElicitResult } from "agents/mcp/client"; export class MyAgent extends Agent { onStart() { diff --git a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx index 4fb23ba0d36..2f5a27b5efd 100644 --- a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx @@ -1,7 +1,7 @@ --- pcx_content_type: reference title: MCP handler APIs -description: Create modern stateless or published 2025 MCP server handlers for Cloudflare Workers with the Agents SDK. +description: Create Stateless or Legacy MCP server handlers for Cloudflare Workers with the Agents SDK. tags: - MCP sidebar: @@ -12,28 +12,22 @@ products: import { LinkCard, PackageManagers, TypeScriptExample } from "~/components"; -The `agents/mcp` entry point provides two Worker handler APIs: +The Agents SDK provides two server handler paths: -| API | MCP server package | Protocol behavior | -| ------------------------ | ------------------------------ | --------------------------------------------------------------- | -| `createMcpHandler` | `@modelcontextprotocol/server` | Draft `2026-07-28` with stateless 2025 compatibility by default | -| `createLegacyMcpHandler` | `@modelcontextprotocol/sdk` | Published 2025 protocol behavior through `WorkerTransport` | +| API | Import path | MCP server package | Behavior | +| ------------------------ | ------------------- | ------------------------------ | ---------------------------------------------- | +| `createMcpHandler` | `agents/mcp/server` | `@modelcontextprotocol/server` | Stateless with Legacy compatibility by default | +| `createLegacyMcpHandler` | `agents/mcp` | `@modelcontextprotocol/sdk` | Legacy sessions through `WorkerTransport` | -`McpAgent` remains available for stateful 2025 servers while their stateless equivalents are built. It is deprecated and feature-frozen. Refer to the [migration guide](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for dual-era rollout guidance. - -:::note[SDK version and protocol version] - -MCP SDK v2 names the split TypeScript packages. Protocol revision `2026-07-28` remains a draft until the MCP project publishes it. - -::: +`McpAgent` remains available for Legacy servers while Stateless equivalents are built. It is deprecated and feature-frozen. Refer to the [migration guide](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for staged rollout guidance. ## Install dependencies -For a modern server: +For a Stateless server: - + -For an explicit 2025 server: +For an explicit Legacy server: @@ -46,14 +40,14 @@ Use the exact MCP versions required by your installed Agents release while the v ```ts import { createMcpHandler, - type CreateStatelessMcpHandlerOptions, + type CreateMcpHandlerOptions, type StatelessMcpHandler, -} from "agents/mcp"; +} from "agents/mcp/server"; import type { McpServerFactory } from "@modelcontextprotocol/server"; function createMcpHandler( factory: McpServerFactory, - options?: CreateStatelessMcpHandlerOptions, + options?: CreateMcpHandlerOptions, ): StatelessMcpHandler; ``` @@ -80,7 +74,7 @@ A zero-argument factory remains valid. ```ts title="src/index.ts" import { McpServer } from "@modelcontextprotocol/server"; -import { createMcpHandler } from "agents/mcp"; +import { createMcpHandler } from "agents/mcp/server"; import { z } from "zod"; function createServer() { @@ -110,23 +104,23 @@ export default createMcpHandler(createServer); Pass the factory itself. Do not create one global server instance or pass a constructed SDK v2 server directly. -### `CreateStatelessMcpHandlerOptions` +### `CreateMcpHandlerOptions` The following options are available: -| Option | Type | Default | Description | -| ------------------------ | --------------------------- | ------------------------------------------------- | --------------------------------------------------- | -| `route` | `string` | `"/mcp"` | Exact path handled by the Worker wrapper | -| `corsOptions` | `CORSOptions \| false` | Wildcard CORS | CORS response headers, or `false` to remove them | -| `allowedHostnames` | `string[]` | Localhost or `workers.dev` route | Optional Host restriction for custom domains | -| `allowedOriginHostnames` | `string[]` | Localhost, `workers.dev`, or concrete CORS Origin | Optional browser Origin restriction | -| `authContext` | `McpAuthContext` | Execution context props | Application props returned by `getMcpAuthContext()` | -| `legacy` | `"stateless" \| "reject"` | `"stateless"` | Stateless 2025 fallback or modern-only rejection | -| `responseMode` | `"auto" \| "json" \| "sse"` | `"auto"` | Modern request response shaping | -| `onerror` | `(error: Error) => void` | None | Out-of-band error reporting | -| `bus` | `ServerEventBus` | In-memory bus | Event bus for modern subscriptions | -| `maxSubscriptions` | `number` | `1,024` | Maximum concurrent listen streams | -| `keepAliveMs` | `number` | `15,000` | Keepalive interval for listen streams | +| Option | Type | Default | Description | +| ------------------------ | --------------------------- | ------------------------------------------------- | ------------------------------------------------------------- | +| `route` | `string` | `"/mcp"` | Exact path handled by the Worker wrapper | +| `corsOptions` | `CORSOptions \| false` | Wildcard CORS | CORS response headers, or `false` to remove them | +| `allowedHostnames` | `string[]` | Localhost or `workers.dev` route | Optional Host restriction for custom domains | +| `allowedOriginHostnames` | `string[] \| "*"` | Localhost, `workers.dev`, or concrete CORS Origin | Browser Origin restriction, or explicit middleware delegation | +| `authContext` | `McpAuthContext` | Execution context props | Application props returned by `getMcpAuthContext()` | +| `legacy` | `"stateless" \| "reject"` | `"stateless"` | Legacy compatibility or Stateless-only rejection | +| `responseMode` | `"auto" \| "json" \| "sse"` | `"auto"` | Stateless request response shaping | +| `onerror` | `(error: Error) => void` | None | Out-of-band error reporting | +| `bus` | `ServerEventBus` | In-memory bus | Event bus for Stateless subscriptions | +| `maxSubscriptions` | `number` | `1,024` | Maximum concurrent listen streams | +| `keepAliveMs` | `number` | `15,000` | Keepalive interval for listen streams | SDK v1 transport options do not apply to this handler. It rejects options such as `transport`, `storage`, `sessionIdGenerator`, `eventStore`, and `enableJsonResponse`. @@ -138,6 +132,14 @@ The handler creates one MCP server for each request. This follows the draft prot Application data can still be durable. Store cross-request data behind an authenticated handle in a Durable Object, D1, KV, or R2 rather than an MCP session ID. +### Stateless Elicitation + +Stateless Elicitation returns `input_required` and completes through multi-round-trip requests (MRTR). The SDK carries `requestState` and `inputResponses` between requests. The Worker does not remain suspended while a user responds. + +Use `inputRequired(...)` to request input. Read accepted form content from `context.mcpReq.inputResponses` with `acceptedContent(...)`. + +Refer to the [Stateless Elicitation example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) for a two-round tool flow. For stateful pushed requests, refer to [Legacy Elicitation](/agents/model-context-protocol/apis/agent-api/#legacy-elicitation). + ### Origin validation and CORS The Workers wrapper validates every present browser Origin. It rejects malformed, opaque, and non-HTTP Origins with `403`. Origin-less non-browser MCP clients remain valid. @@ -161,15 +163,17 @@ export default createMcpHandler(createServer, { Allowlist values are hostnames without a scheme or port. Origin matching ignores scheme and port. +Set `allowedOriginHostnames: "*"` only when trusted middleware validates Origins before calling the handler. This value turns off the handler Origin check, including malformed and opaque Origin rejection. MCP HTTP servers must validate browser Origins. + CORS response headers are not authentication. Protect the MCP endpoint with OAuth or another authentication layer. The handler does not infer a Host allowlist from `request.url`. If a deployment accepts arbitrary Host values, validate them before calling the handler. Local servers outside Cloudflare Workers should follow the upstream SDK DNS rebinding guidance. -### Stateless 2025 compatibility +### Legacy compatibility -The default `legacy: "stateless"` setting accepts ordinary tools, prompts, and resources from published 2025 clients. +The default `legacy: "stateless"` setting accepts ordinary Legacy tools, prompts, and resources. This lane uses the SDK v2 web-standard transport and does not import `WorkerTransport`. -This fallback does not provide a complete 2025 session transport: +Legacy compatibility does not provide a complete session transport: - Each POST creates a new server and transport. - HTTP GET and DELETE return `405`. @@ -178,7 +182,7 @@ This fallback does not provide a complete 2025 session transport: - Standalone streams, resumability, replay, and session deletion are unavailable. - Published experimental tasks are not supported through this path. -Set `legacy: "reject"` for a modern-only endpoint. Use `createLegacyMcpHandler` or `McpAgent` when a 2025 client needs protocol sessions. +Set `legacy: "reject"` for a Stateless-only endpoint. Use `createLegacyMcpHandler` or `McpAgent` when a Legacy client needs protocol sessions. ### Return value @@ -199,7 +203,7 @@ handler.bus.publish(event); await handler.close(); ``` -`close()` rejects new requests and closes active modern and stateless legacy work. +`close()` rejects new requests and closes active Stateless and Legacy compatibility work. ## `createLegacyMcpHandler` @@ -220,7 +224,7 @@ function createLegacyMcpHandler( ): LegacyMcpHandler; ``` -Use this handler for published 2025 protocol sessions, transport storage, event replay, and pushed server-to-client requests. +Use this handler for Legacy sessions, transport storage, event replay, and pushed server-to-client requests. @@ -283,7 +287,7 @@ interface McpAuthContext { ```ts -import { getMcpAuthContext } from "agents/mcp"; +import { getMcpAuthContext } from "agents/mcp/server"; server.registerTool( "whoami", @@ -326,7 +330,7 @@ Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-t diff --git a/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx b/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx index f2b57a2e45f..b8825d2f558 100644 --- a/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx @@ -33,6 +33,8 @@ Code Mode is experimental and may have breaking changes. Use caution in producti You need a Cloudflare Workers project, an OpenAPI 3.x document, and a host-side method for authenticating API requests. +`openApiMcpServer()` currently returns an SDK v1 server. Serve it through the explicit Legacy `createLegacyMcpHandler` API. + ## Publish the service diff --git a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx index 73bddffaa08..8e19acdf6e1 100644 --- a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx @@ -1,6 +1,6 @@ --- title: Migrate to MCP SDK v2 -description: Migrate Agents SDK MCP servers and clients to the split MCP TypeScript SDK v2 packages while retaining published 2025 protocol compatibility. +description: Migrate Agents SDK MCP servers and clients to the split MCP TypeScript SDK v2 packages while retaining Legacy compatibility. pcx_content_type: how-to sidebar: order: 13 @@ -10,27 +10,19 @@ products: import { PackageManagers, Steps, TypeScriptExample } from "~/components"; -This guide covers the MCP SDK v2 upgrade in Agents SDK v0.18.0. It explains how to move stateless servers to `@modelcontextprotocol/server`, keep existing 2025 servers on an explicit legacy handler, and update MCP clients. - -:::note[SDK version and protocol version] - -MCP SDK v2 refers to the split TypeScript packages. It does not make protocol revision `2026-07-28` a published stable specification. - -The v2 packages implement the draft `2026-07-28` revision. They also support the published `2025-11-25` revision. This guide calls these the modern and legacy protocol eras. - -::: +This guide covers the MCP SDK v2 upgrade in Agents SDK v0.18.0. It explains how to move Stateless servers to `@modelcontextprotocol/server`, keep existing servers on an explicit Legacy handler, and update MCP clients. ## Choose a server path Use the following table to select a migration path: -| Current server | Migration path | -| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | -| `createMcpHandler` with an SDK v1 server | Rename the call to `createLegacyMcpHandler` to keep the same behavior. | -| Stateless server ready for draft `2026-07-28` | Move to `@modelcontextprotocol/server` and pass a factory to `createMcpHandler`. | -| Server that must support published specifications only | Keep SDK v1 and use `createLegacyMcpHandler`. | -| `McpAgent` without legacy stateful features | Migrate directly to an SDK v2 factory and `createMcpHandler`. | -| `McpAgent` using legacy stateful features | Design stateless equivalents, serve modern and legacy lanes together, then drain the legacy lane. | +| Current server | Migration path | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `createMcpHandler` with an SDK v1 server | Rename the call to `createLegacyMcpHandler` to keep the same behavior. | +| Server ready for Stateless request handling | Move to `@modelcontextprotocol/server` and pass a factory to `createMcpHandler`. | +| Server that must retain Legacy behavior | Keep SDK v1 and use `createLegacyMcpHandler`. | +| `McpAgent` without Legacy stateful features | Migrate directly to an SDK v2 factory and `createMcpHandler`. | +| `McpAgent` using Legacy stateful features | Design Stateless equivalents, serve Stateless and Legacy lanes together, then drain the Legacy lane. | The following APIs are deprecated: @@ -42,21 +34,21 @@ The following APIs are deprecated: Install only the MCP package generations that your application imports. Keep the v2 beta version exact. -For a modern server: +For a Stateless server: - + -For an explicit 2025 server: +For an explicit Legacy server: For an Agent that connects to MCP servers: - + Follow peer dependency instructions from your package manager. The exact v2 pin will change with later Agents releases while the MCP SDK remains in beta. -## Keep existing SDK v1 behavior +## Keep existing Legacy behavior Use this path when your server depends on any of these features: @@ -99,9 +91,11 @@ export default { Create a new SDK v1 server for each request unless you provide a persistent transport that is already connected to that server. Reconnecting one server instance to several transports is invalid. -## Move a stateless server to SDK v2 +Refer to the [Legacy Elicitation example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) for a sessionful server with Durable Object state and SSE replay. -The modern `createMcpHandler` accepts a factory. The factory returns `McpServer` or `Server` from `@modelcontextprotocol/server`. +## Move a Stateless server to SDK v2 + +The Stateless `createMcpHandler` accepts a factory. The factory returns `McpServer` or `Server` from `@modelcontextprotocol/server`. @@ -115,7 +109,7 @@ The modern `createMcpHandler` accepts a factory. The factory returns `McpServer` 5. Remove SDK v1 transport and session options. -6. Test the endpoint with modern and published 2025 clients. +6. Test the endpoint with Stateless and Legacy clients. @@ -123,7 +117,7 @@ The modern `createMcpHandler` accepts a factory. The factory returns `McpServer` ```ts title="src/index.ts" import { McpServer } from "@modelcontextprotocol/server"; -import { createMcpHandler } from "agents/mcp"; +import { createMcpHandler } from "agents/mcp/server"; import { z } from "zod"; function createServer() { @@ -153,24 +147,24 @@ export default createMcpHandler(createServer); The handler creates one server for each request. Concurrent Worker requests never share a connected server instance. -### Modern handler options +### Stateless handler options The Agents wrapper adds `route`, `corsOptions`, `allowedHostnames`, `allowedOriginHostnames`, and `authContext`. It also passes supported SDK v2 options through to the upstream handler. Common options include: -| Option | Behavior | -| ---------------------------------------- | ---------------------------------------------------------------------------------------- | -| `route` | Sets the exact request path. The default is `/mcp`. | -| `legacy` | Uses stateless 2025 compatibility by default. Set `"reject"` for a modern-only endpoint. | -| `responseMode` | Selects automatic, JSON, or SSE response handling. | -| `allowedHostnames` | Restricts Host headers to specific hostnames. | -| `allowedOriginHostnames` | Restricts browser Origins to specific hostnames. | -| `corsOptions` | Controls CORS response headers. Set `false` to remove them. | -| `onerror` | Reports handler errors without changing the response. | -| `bus`, `maxSubscriptions`, `keepAliveMs` | Configure `subscriptions/listen` delivery. | +| Option | Behavior | +| ---------------------------------------- | ----------------------------------------------------------------------------------- | +| `route` | Sets the exact request path. The default is `/mcp`. | +| `legacy` | Uses Legacy compatibility by default. Set `"reject"` for a Stateless-only endpoint. | +| `responseMode` | Selects automatic, JSON, or SSE response handling. | +| `allowedHostnames` | Restricts Host headers to specific hostnames. | +| `allowedOriginHostnames` | Restricts browser Origins, or accepts `"*"` when trusted middleware validates them. | +| `corsOptions` | Controls CORS response headers. Set `false` to remove them. | +| `onerror` | Reports handler errors without changing the response. | +| `bus`, `maxSubscriptions`, `keepAliveMs` | Configure `subscriptions/listen` delivery. | -The modern handler rejects these SDK v1 options: +The Stateless handler rejects these SDK v1 options: - `transport` - `storage` @@ -204,13 +198,15 @@ export default createMcpHandler(createServer, { +Set `allowedOriginHostnames: "*"` only when trusted middleware validates Origins before calling the handler. This value turns off the handler Origin check. MCP HTTP servers must validate browser Origins. + CORS headers do not authenticate a request. Protect the endpoint with OAuth or another authentication layer. The handler does not infer a trusted Host allowlist from `request.url`. If your deployment accepts arbitrary Host values, validate them before calling the handler. For local servers outside Cloudflare Workers, follow the upstream SDK Host and Origin validation guidance. -### Understand the 2025 fallback +### Understand Legacy compatibility -The default `legacy: "stateless"` setting supports ordinary 2025 tools, resources, and prompts. It is not a complete sessionful `2025-11-25` transport. +The default `legacy: "stateless"` setting supports ordinary Legacy tools, resources, and prompts. This lane uses the SDK v2 web-standard transport. It does not import `WorkerTransport` and is not a complete sessionful transport. The fallback has these limits: @@ -221,19 +217,19 @@ The fallback has these limits: - Standalone streams, event replay, and session deletion are unavailable. - Published experimental tasks are not supported through this fallback. -Use `createLegacyMcpHandler` or `McpAgent` when a 2025 client needs those features. +Use `createLegacyMcpHandler` or `McpAgent` when a Legacy client needs those features. ## Migrate an McpAgent server `McpAgent` remains an SDK v1 server. Do not change its server import to `@modelcontextprotocol/server`. -### Migrate directly when the server does not use legacy stateful features +### Migrate directly without Legacy stateful features If the server does not depend on MCP session state, RPC, pushed server-to-client requests, standalone streams, or event replay, move its tools to an SDK v2 factory and serve it with `createMcpHandler`. ### Plan stateless equivalents for stateful features -If the server uses legacy stateful features, keep the existing `McpAgent` route while you design and deploy stateless equivalents: +If the server uses Legacy stateful features, keep the existing `McpAgent` route while you design and deploy Stateless equivalents: | Legacy stateful feature | Stateless design | | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -241,22 +237,22 @@ If the server uses legacy stateful features, keep the existing `McpAgent` route | Multi-step interaction state | Return integrity-protected `requestState` with `input_required`. Bind it to the authenticated user, original method and parameters, and an expiry. | | Pushed elicitation, sampling, or roots requests | Return `inputRequired(...)`. The client fulfils the embedded requests and retries the original operation. | | Standalone list-change stream | Publish changes through `subscriptions/listen`. Clients reopen the subscription if its stream ends. | -| Session replay or transport recovery | Make each modern request independently recoverable. Persist business progress in application storage rather than the MCP transport. | +| Session replay or transport recovery | Make each Stateless request independently recoverable. Persist business progress in application storage rather than the MCP transport. | | Agent-to-`McpAgent` RPC | Replace the protocol-session dependency with an explicit application RPC or HTTP boundary, then expose the stateless MCP tools separately. | -Do not remove the legacy route as soon as the modern implementation exists. Serve both eras while clients migrate and existing 2025 sessions drain. +Do not remove the Legacy route as soon as the Stateless implementation exists. Serve both lanes while clients migrate and existing sessions drain. -### Run modern and legacy lanes together +### Run Stateless and Legacy lanes together -A single URL can route modern requests to SDK v2 and published 2025 requests to the existing sessionful server. +A single URL can route Stateless requests to SDK v2 and Legacy requests to the existing sessionful server. ```ts import { isLegacyRequest } from "@modelcontextprotocol/server"; -import { createMcpHandler } from "agents/mcp"; +import { createMcpHandler } from "agents/mcp/server"; -const modern = createMcpHandler(createModernServer, { +const stateless = createMcpHandler(createStatelessServer, { route: "/mcp", legacy: "reject", }); @@ -268,34 +264,36 @@ export default { if (await isLegacyRequest(request)) { return legacy.fetch(request, env, ctx); } - return modern(request, env, ctx); + return stateless(request, env, ctx); }, } satisfies ExportedHandler; ``` -Keep `legacy: "reject"` on the modern handler. Otherwise, its stateless fallback consumes 2025 traffic before the sessionful route receives it. +Keep `legacy: "reject"` on the Stateless handler. Otherwise, its Legacy compatibility lane consumes requests before the sessionful route receives them. -Deploy both routes before moving clients. Monitor the legacy lane and let existing sessions drain. Remove the old route and its protocol-only Durable Object binding only after no clients depend on them. Handle Durable Object migration configuration as a separate deployment step. +Deploy both routes before moving clients. Monitor the Legacy lane and let existing sessions drain. Remove the Legacy route and its protocol-only Durable Object binding only after no clients depend on them. Handle Durable Object migration configuration as a separate deployment step. ## Update MCP clients Agents now uses `@modelcontextprotocol/client` internally. Existing `addMcpServer` calls negotiate the protocol era automatically. -Modern servers use `server/discover`. Agents falls back to the published `initialize` handshake for legacy Streamable HTTP, SSE, and RPC servers. +Stateless servers use `server/discover`. Agents falls back to `initialize` for Legacy Streamable HTTP, SSE, and RPC servers. The client API includes these changes: - `callTool(params, options)` is the preferred signature. - `callTool(params, resultSchema, options)` remains available but is deprecated. - MCP client types now come from `@modelcontextprotocol/client`. -- Required modern HTTP headers are handled by the SDK. -- List changes use modern subscriptions or legacy notifications based on the negotiated era. +- Required Stateless HTTP headers are handled by the SDK. +- List changes use Stateless subscriptions or Legacy notifications based on the negotiated lane. + +### Configure Stateless Elicitation -### Configure multi-round-trip input +Stateless tools, prompts, and resources can return `input_required` through multi-round-trip requests (MRTR). The SDK calls the configured elicitation handler and retries the original operation. Your original `callTool`, `getPrompt`, or `readResource` promise remains pending. -Modern tools, prompts, and resources can return `input_required`. The SDK calls the configured elicitation handler and retries the original operation. Your original `callTool`, `getPrompt`, or `readResource` promise remains pending. +Refer to the [Stateless Elicitation example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) for a two-round tool flow. @@ -335,9 +333,9 @@ SDK v2 validates OAuth metadata issuers by default. A trusted legacy server with ## Review protocol differences -The draft `2026-07-28` revision changes the transport and lifecycle model: +Stateless MCP changes the transport and lifecycle model: -| Area | Published 2025 behavior | Draft `2026-07-28` behavior | +| Area | Legacy behavior | Stateless behavior | | --------------------- | --------------------------------------------------- | ---------------------------------------------------------------------- | | Startup | `initialize` handshake | No handshake; `server/discover` is optional for clients | | Request metadata | Connection-scoped negotiation | Version, client capabilities, and identity metadata on each request | @@ -353,9 +351,9 @@ Custom transports, proxies, and gateways must preserve the draft request headers - `Mcp-Name` for tool, prompt, and resource operations - Declared `Mcp-Param-*` tool headers -The exact beta used by Agents is a snapshot of the draft. Beta.4 requires `io.modelcontextprotocol/clientInfo` and returns server identity in the `DiscoverResult` body. Later draft changes may alter those raw wire details. Use the high-level SDK and update MCP packages with the Agents release that supports a newer snapshot. Raw implementations must include `resultType` on beta.4 draft results. +The exact beta used by Agents is a draft snapshot. Beta.5 makes `clientInfo` optional and places server identity in result `_meta`. Use high-level SDK APIs and update MCP packages with the Agents release that supports each snapshot. Raw Stateless results must include `resultType`. -The draft deprecates Roots, Sampling, Logging, the old HTTP+SSE transport, and Dynamic Client Registration. The types remain available during the deprecation window for published 2025 compatibility. The published experimental task methods become the `io.modelcontextprotocol/tasks` extension. Agents SDK v0.18.0 does not add that extension. +The draft deprecates Roots, Sampling, Logging, the old HTTP+SSE transport, and Dynamic Client Registration. The types remain available during the deprecation window for Legacy compatibility. The published experimental task methods become the `io.modelcontextprotocol/tasks` extension. Agents SDK v0.18.0 does not add that extension. ### Integration compatibility @@ -371,15 +369,15 @@ Serve these results with `createLegacyMcpHandler`. The Code Mode MCP connector a -1. Classify each endpoint as modern, explicit legacy, or `McpAgent`. +1. Classify each endpoint as Stateless, explicit Legacy, or `McpAgent`. 2. Pin the MCP SDK versions required by the Agents release. 3. Rename SDK v1 handler calls before changing their behavior. -4. Add modern handlers beside existing sessionful routes. +4. Add Stateless handlers beside existing sessionful routes. -5. Test draft `2026-07-28` and published `2025-11-25` clients independently. +5. Test Stateless and Legacy clients independently. 6. Test required HTTP headers through every proxy and gateway. @@ -389,7 +387,7 @@ Serve these results with `createLegacyMcpHandler`. The Code Mode MCP connector a 9. Verify valid Origins and reject invalid Origins with `403`. -10. Remove legacy routes only after existing sessions drain. +10. Remove Legacy routes only after existing sessions drain. diff --git a/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx b/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx index 947e29098c2..9733ec419a1 100644 --- a/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx @@ -21,14 +21,14 @@ This guide shows how to deploy a remote MCP server on Cloudflare using [Streamab The Agents SDK provides multiple ways to create MCP servers. Choose the approach that fits your use case: -| Approach | Stateful? | Protocol path | Best for | -| ----------------------------------------------------------------------------------------------------- | -------------------- | ------------------------------------------------ | --------------------------------------- | -| [`createMcpHandler()`](/agents/model-context-protocol/apis/handler-api/) | No | Draft `2026-07-28`, with stateless 2025 fallback | New stateless tools | -| [`createLegacyMcpHandler()`](/agents/model-context-protocol/apis/handler-api/#createlegacymcphandler) | Optional | Published 2025 | Existing `WorkerTransport` servers | -| [`McpAgent`](/agents/model-context-protocol/apis/agent-api/) | Yes | Published 2025 | Existing Durable Object and RPC servers | -| Raw SDK transport | Depends on transport | Depends on SDK package | Custom transport ownership | - -Use `createMcpHandler` for a new stateless server. An existing `McpAgent` without legacy stateful dependencies can migrate directly. If it uses MCP session state, RPC, pushed requests, streams, or replay, plan the stateless equivalents and serve both eras during the transition. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the staged rollout. +| Approach | Stateful? | Protocol path | Best for | +| ----------------------------------------------------------------------------------------------------- | -------------------- | ----------------------------------- | --------------------------------------- | +| [`createMcpHandler()`](/agents/model-context-protocol/apis/handler-api/) | No | Stateless with Legacy compatibility | New Stateless tools | +| [`createLegacyMcpHandler()`](/agents/model-context-protocol/apis/handler-api/#createlegacymcphandler) | Optional | Legacy | Existing `WorkerTransport` servers | +| [`McpAgent`](/agents/model-context-protocol/apis/agent-api/) | Yes | Legacy | Existing Durable Object and RPC servers | +| Raw SDK transport | Depends on transport | Depends on SDK package | Custom transport ownership | + +Use `createMcpHandler` for a new stateless server. An existing `McpAgent` without legacy stateful dependencies can migrate directly. If it uses MCP session state, RPC, pushed requests, streams, or replay, plan the Stateless equivalents and serve Stateless and Legacy lanes during the transition. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the staged rollout. ## Deploy your first MCP server diff --git a/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx b/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx index 9148c8b0c77..36af057713b 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx @@ -202,12 +202,12 @@ export class MyMCP extends McpAgent { } ``` -### With createMcpHandler +### With Stateless createMcpHandler A compatible Workers OAuth Provider supplies standard token metadata at `context.http.authInfo`. Use `getMcpAuthContext()` for existing application props. ```ts -import { createMcpHandler, getMcpAuthContext } from "agents/mcp"; +import { createMcpHandler, getMcpAuthContext } from "agents/mcp/server"; import { McpServer } from "@modelcontextprotocol/server"; function createServer() { diff --git a/src/content/docs/agents/model-context-protocol/protocol/tools.mdx b/src/content/docs/agents/model-context-protocol/protocol/tools.mdx index 55478606d2e..6859740e1d9 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/tools.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/tools.mdx @@ -14,7 +14,7 @@ import { TypeScriptExample, LinkCard } from "~/components"; MCP tools are functions that an [MCP server](/agents/model-context-protocol/) exposes for clients to call. An LLM can invoke a tool to look up data, run a calculation, or call an API. The MCP server executes the tool and returns its result. -Use `@modelcontextprotocol/server` for a modern `createMcpHandler` server. Existing `McpAgent` servers must keep using `@modelcontextprotocol/sdk`. +Use `@modelcontextprotocol/server` for a Stateless `createMcpHandler` server. Existing Legacy `McpAgent` servers must keep using `@modelcontextprotocol/sdk`. :::note[Experimental WebMCP adapter] @@ -30,7 +30,7 @@ The Agents SDK also includes the experimental `agents/experimental/webmcp` adapt ## Defining tools -Use `server.registerTool()` to register a tool on a modern `McpServer` instance. Each tool has a name, a description, an input schema defined with [Zod](https://zod.dev), and a handler function. +Use `server.registerTool()` to register a tool on a Stateless `McpServer` instance. Each tool has a name, a description, an input schema defined with [Zod](https://zod.dev), and a handler function. @@ -145,7 +145,7 @@ For stateless MCP servers, define tools inside a factory function and pass the s ```ts -import { createMcpHandler } from "agents/mcp"; +import { createMcpHandler } from "agents/mcp/server"; import { McpServer } from "@modelcontextprotocol/server"; import { z } from "zod"; @@ -170,7 +170,7 @@ export default createMcpHandler(createServer); ## Using tools with `McpAgent` -For stateful MCP servers, define tools in the `init()` method of an [`McpAgent`](/agents/model-context-protocol/apis/agent-api/). Tools have access to the agent instance via `this`, which means they can read and write state. +For Legacy stateful MCP servers, define tools in the `init()` method of an [`McpAgent`](/agents/model-context-protocol/apis/agent-api/). Tools have access to the agent instance via `this`, which means they can read and write state. diff --git a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx index fac4675c602..d255477ef2b 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx @@ -40,7 +40,7 @@ Create an MCP server using `createMcpHandler`. View the [complete example on Git ```ts -import { createMcpHandler } from "agents/mcp"; +import { createMcpHandler } from "agents/mcp/server"; import { McpServer } from "@modelcontextprotocol/server"; import { z } from "zod"; @@ -87,13 +87,13 @@ export default new OAuthProvider({ -### Stateful published 2025 servers +### Legacy servers -The draft `2026-07-28` protocol has no protocol-level session. Applications can store durable business data behind a separate storage boundary. +Stateless MCP has no protocol-level session. Applications can store durable business data behind a separate storage boundary. -Existing servers that require published 2025 sessions can use `createLegacyMcpHandler` with `WorkerTransport`, or keep `McpAgent` while their stateless equivalents are built. These APIs support transport state, event replay, pushed elicitation, sampling, and roots requests. `McpAgent` is deprecated and feature-frozen. +Servers that require Legacy sessions can use `createLegacyMcpHandler` with `WorkerTransport`, or keep `McpAgent` while their Stateless equivalents are built. These APIs support transport state, event replay, pushed elicitation, sampling, and roots requests. `McpAgent` is deprecated and feature-frozen. -Add the modern route before moving clients, and keep both lanes until existing sessions drain. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the staged migration. Refer to [`McpAgent`: Stream resumability](/agents/model-context-protocol/apis/agent-api/#stream-resumability) for existing stream behavior. +Add the Stateless route before moving clients, and keep both lanes until existing sessions drain. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the staged migration. Refer to [`McpAgent`: Stream resumability](/agents/model-context-protocol/apis/agent-api/#stream-resumability) for existing stream behavior. ## RPC transport @@ -314,9 +314,9 @@ export class MyMCP extends McpAgent { ### Migrate from McpAgent -If the endpoint does not use legacy stateful features, migrate directly to a stateless server factory from `@modelcontextprotocol/server` and pass it to `createMcpHandler`. +If the endpoint does not use Legacy stateful features, migrate directly to a Stateless server factory from `@modelcontextprotocol/server` and pass it to `createMcpHandler`. -If it depends on MCP session state, RPC, pushed server-to-client requests, standalone streams, or replay, first design stateless equivalents. For example, move business state behind explicit application storage and replace pushed input requests with multi-round-trip `input_required` results. Serve modern and legacy lanes together while clients migrate and existing sessions drain. +If it depends on MCP session state, RPC, pushed server-to-client requests, standalone streams, or replay, first design Stateless equivalents. For example, move business state behind explicit application storage and replace pushed input requests with Stateless Elicitation. Serve Stateless and Legacy lanes together while clients migrate and existing sessions drain. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the feature mapping, dual-era routing, and rollout steps. From d7a3bbf8706e76b01d5f20ef0194d2998a7dc2bb Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Sun, 26 Jul 2026 18:08:30 +0100 Subject: [PATCH 05/16] docs(agents): schedule MCP SDK v2 changelog --- ...2.mdx => 2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx} | 8 ++++---- .../agents/model-context-protocol/apis/client-api.mdx | 2 +- .../guides/migrate-to-mcp-sdk-v2.mdx | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) rename src/content/changelog/agents/{2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx => 2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx} (91%) diff --git a/src/content/changelog/agents/2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx similarity index 91% rename from src/content/changelog/agents/2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx rename to src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx index 0c7a85cea30..d01d8ab6a8f 100644 --- a/src/content/changelog/agents/2026-07-20-agents-sdk-v0.18.0-mcp-sdk-v2.mdx +++ b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx @@ -1,15 +1,15 @@ --- -title: "Agents SDK v0.18.0: Stateless MCP SDK v2 support" -description: "Agents SDK v0.18.0 adds Stateless MCP SDK v2 clients and servers while retaining explicit Legacy support." +title: "Agents SDK v0.20.0: Stateless MCP 2026-07-28 support" +description: "Agents SDK v0.20.0 adds Stateless MCP 2026-07-28 clients and servers while retaining explicit Legacy support." products: - agents - workers -date: 2026-07-20 +date: 2026-07-27 --- import { PackageManagers, TypeScriptExample } from "~/components"; -Agents SDK v0.18.0 adds support for the split MCP TypeScript SDK v2 packages. Agents can serve Stateless MCP, fall back to Legacy servers, and handle Stateless Elicitation. +Agents SDK v0.20.0 adds support for the split MCP TypeScript SDK v2 packages. Agents can serve Stateless MCP, fall back to Legacy servers, and handle Stateless Elicitation. Existing Legacy server deployments remain supported through `createLegacyMcpHandler`, `WorkerTransport`, and `McpAgent`. diff --git a/src/content/docs/agents/model-context-protocol/apis/client-api.mdx b/src/content/docs/agents/model-context-protocol/apis/client-api.mdx index 57bdb77955c..259e6293ff9 100644 --- a/src/content/docs/agents/model-context-protocol/apis/client-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/client-api.mdx @@ -12,7 +12,7 @@ products: import { Render, TypeScriptExample, LinkCard } from "~/components"; -Connect your agent to external [Model Context Protocol (MCP)](/agents/model-context-protocol/) servers to use their tools, resources, and prompts. Agents SDK v0.18.0 uses `@modelcontextprotocol/client` and negotiates Stateless or Legacy behavior automatically. +Connect your agent to external [Model Context Protocol (MCP)](/agents/model-context-protocol/) servers to use their tools, resources, and prompts. Agents SDK v0.20.0 uses `@modelcontextprotocol/client` and negotiates Stateless or Legacy behavior automatically. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for package, type, OAuth provider, and rollout changes. diff --git a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx index 8e19acdf6e1..7283f54b215 100644 --- a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx @@ -10,7 +10,7 @@ products: import { PackageManagers, Steps, TypeScriptExample } from "~/components"; -This guide covers the MCP SDK v2 upgrade in Agents SDK v0.18.0. It explains how to move Stateless servers to `@modelcontextprotocol/server`, keep existing servers on an explicit Legacy handler, and update MCP clients. +This guide covers the MCP SDK v2 upgrade in Agents SDK v0.20.0. It explains how to move Stateless servers to `@modelcontextprotocol/server`, keep existing servers on an explicit Legacy handler, and update MCP clients. ## Choose a server path @@ -353,7 +353,7 @@ Custom transports, proxies, and gateways must preserve the draft request headers The exact beta used by Agents is a draft snapshot. Beta.5 makes `clientInfo` optional and places server identity in result `_meta`. Use high-level SDK APIs and update MCP packages with the Agents release that supports each snapshot. Raw Stateless results must include `resultType`. -The draft deprecates Roots, Sampling, Logging, the old HTTP+SSE transport, and Dynamic Client Registration. The types remain available during the deprecation window for Legacy compatibility. The published experimental task methods become the `io.modelcontextprotocol/tasks` extension. Agents SDK v0.18.0 does not add that extension. +The draft deprecates Roots, Sampling, Logging, the old HTTP+SSE transport, and Dynamic Client Registration. The types remain available during the deprecation window for Legacy compatibility. The published experimental task methods become the `io.modelcontextprotocol/tasks` extension. Agents SDK v0.20.0 does not add that extension. ### Integration compatibility @@ -391,6 +391,6 @@ Serve these results with `createLegacyMcpHandler`. The Code Mode MCP connector a -Stored HTTP session IDs from Agents releases before v0.18.0 do not include the negotiated protocol version. The upgraded client discards those IDs and reconnects instead of sending an unsafe resumed request. Existing in-flight work tied to an old remote session does not resume. +Stored HTTP session IDs from Agents releases before v0.20.0 do not include the negotiated protocol version. The upgraded client discards those IDs and reconnects instead of sending an unsafe resumed request. Existing in-flight work tied to an old remote session does not resume. For API details, refer to [`createMcpHandler`](/agents/model-context-protocol/apis/handler-api/) and [`McpClient`](/agents/model-context-protocol/apis/client-api/). From af7143cabde3b7c6b0fe61e455c7ec9fec9ed4d0 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Mon, 27 Jul 2026 10:41:00 +0100 Subject: [PATCH 06/16] docs(agents): simplify MCP changelog title --- .../agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx index d01d8ab6a8f..01982bab578 100644 --- a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx +++ b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx @@ -1,5 +1,5 @@ --- -title: "Agents SDK v0.20.0: Stateless MCP 2026-07-28 support" +title: "Agents SDK adds Stateless MCP 2026-07-28 support" description: "Agents SDK v0.20.0 adds Stateless MCP 2026-07-28 clients and servers while retaining explicit Legacy support." products: - agents From c2796d3f2ba10fe6194a54aac8ba4ae0af92374e Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Mon, 27 Jul 2026 11:40:36 +0100 Subject: [PATCH 07/16] fix: address PR #32175 review feedback - orient the changelog around MCP 2026-07-28 and link upstream context - align migration examples and terminology with the published SDK APIs - fix style-review findings and document elicitation cancellation --- ...26-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx | 20 ++-- .../model-context-protocol/apis/agent-api.mdx | 16 +-- .../apis/client-api.mdx | 17 +-- .../apis/handler-api.mdx | 42 +++---- .../guides/build-codemode-mcp-server.mdx | 12 +- .../build-codemode-openapi-mcp-server.mdx | 12 +- .../guides/migrate-to-mcp-sdk-v2.mdx | 108 +++++++++--------- .../guides/remote-mcp-server.mdx | 8 +- .../protocol/authorization.mdx | 2 +- .../model-context-protocol/protocol/tools.mdx | 6 +- .../protocol/transport.mdx | 14 +-- 11 files changed, 130 insertions(+), 127 deletions(-) diff --git a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx index 01982bab578..3696c43db3d 100644 --- a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx +++ b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx @@ -1,6 +1,6 @@ --- -title: "Agents SDK adds Stateless MCP 2026-07-28 support" -description: "Agents SDK v0.20.0 adds Stateless MCP 2026-07-28 clients and servers while retaining explicit Legacy support." +title: "Agents SDK adds MCP Specification 2026-07-28 support" +description: "Agents SDK v0.20.0 adds stateless-by-default MCP 2026-07-28 clients and servers while retaining explicit legacy support." products: - agents - workers @@ -9,11 +9,11 @@ date: 2026-07-27 import { PackageManagers, TypeScriptExample } from "~/components"; -Agents SDK v0.20.0 adds support for the split MCP TypeScript SDK v2 packages. Agents can serve Stateless MCP, fall back to Legacy servers, and handle Stateless Elicitation. +The [MCP 2026-07-28 release candidate](https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/) makes the protocol stateless by default. Agents SDK v0.20.0 adopts this model through the split MCP TypeScript SDK v2 packages. New MCP servers can run in a Worker without keeping transport state in a Durable Object, and clients can call tools without an `initialize` handshake. Applications can still use Durable Objects or other storage for their own state. -Existing Legacy server deployments remain supported through `createLegacyMcpHandler`, `WorkerTransport`, and `McpAgent`. +Agents clients negotiate stateless or legacy behavior automatically. Existing legacy server deployments remain supported through `createLegacyMcpHandler`, `WorkerTransport`, and `McpAgent`. -## Stateless servers +## Run stateless servers `createMcpHandler` now accepts a factory that returns a server from `@modelcontextprotocol/server`. The factory creates an isolated server for each request. @@ -32,11 +32,11 @@ export default createMcpHandler(createServer); -The isolated `agents/mcp/server` entry keeps `McpAgent`, `WorkerTransport`, MCP client transports, and SDK v1 modules out of Stateless server bundles. Its Legacy compatibility lane supports ordinary tools, resources, and prompts. Session streams, replay, deletion, and pushed server-to-client requests still require a Legacy sessionful server. +The isolated `agents/mcp/server` entry keeps `McpAgent`, `WorkerTransport`, MCP client transports, and SDK v1 modules out of stateless server bundles. Its legacy compatibility lane supports ordinary tools, resources, and prompts. Session streams, replay, deletion, and pushed server-to-client requests still require a legacy sessionful server. The Workers wrapper validates present browser Origins, supports explicit delegation to trusted Origin middleware, and exposes the upstream handler's `close`, `notify`, and `bus` controls. -## Explicit Legacy support +## Keep legacy support Existing SDK v1 servers can rename `createMcpHandler` to `createLegacyMcpHandler` without changing their transport behavior: @@ -53,13 +53,13 @@ export default createLegacyMcpHandler( -Passing an SDK v1 server directly to `createMcpHandler` is deprecated. `McpAgent` remains available for stateful Legacy deployments, but it is deprecated and feature-frozen. +Passing an SDK v1 server directly to `createMcpHandler` is deprecated. `McpAgent` remains available for stateful legacy deployments, but it is deprecated and feature-frozen. ## Client negotiation and input requests -The MCP client manager now uses `@modelcontextprotocol/client`. It probes Stateless servers with `server/discover` and falls back to the Legacy `initialize` handshake. +The MCP client manager now uses `@modelcontextprotocol/client`. It probes stateless servers with `server/discover` and falls back to the legacy `initialize` handshake. -Stateless Elicitation uses `input_required` through multi-round-trip requests (MRTR). Legacy Elicitation uses the same form and URL handlers for pushed requests. The SDK collects input, retries the original operation, and resolves the original `callTool`, `getPrompt`, or `readResource` promise with its final result. +For stateless requests, elicitation uses `input_required` through multi-round-trip requests (MRTR). The legacy path uses the same form and URL handlers for pushed requests. The SDK collects input, retries the original operation, and resolves the original `callTool`, `getPrompt`, or `readResource` promise with its final result. OAuth callbacks now validate issuer metadata through the v2 SDK. Discovery state and issuer-bound credentials persist across browser redirects and Durable Object hibernation. diff --git a/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx b/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx index 00f5fa61886..3e3caa1ad81 100644 --- a/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx @@ -12,15 +12,15 @@ products: import { TypeScriptExample, LinkCard } from "~/components"; -`McpAgent` creates a stateful Legacy MCP server backed by a Durable Object. +`McpAgent` creates a stateful legacy MCP server backed by a Durable Object. :::caution[Deprecated] -`McpAgent` remains available for Legacy servers that use stateful features. It is deprecated and feature-frozen. A server without those dependencies can migrate directly to [`createMcpHandler`](/agents/model-context-protocol/apis/handler-api/). +`McpAgent` remains available for legacy servers that use stateful features. It is deprecated and feature-frozen. A server without those dependencies can migrate directly to [`createMcpHandler`](/agents/model-context-protocol/apis/handler-api/). -A server that depends on MCP session state, RPC, pushed server-to-client requests, standalone streams, or replay needs a staged migration. Design Stateless equivalents, add a Stateless route, and serve both lanes until clients migrate and existing sessions drain. +A server that depends on MCP session state, RPC, pushed server-to-client requests, standalone streams, or replay needs a staged migration. Design stateless equivalents, add a stateless route, and serve both lanes until clients migrate and existing sessions drain. -Keep importing the Legacy server `McpServer` from `@modelcontextprotocol/sdk`. An SDK v2 server from `@modelcontextprotocol/server` cannot run inside `McpAgent`. +Keep importing the legacy server `McpServer` from `@modelcontextprotocol/sdk`. An SDK v2 server from `@modelcontextprotocol/server` cannot run inside `McpAgent`. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the feature mapping and dual-era rollout. @@ -52,7 +52,7 @@ export class MyMCP extends McpAgent { This means that each instance of your MCP server has its own durable state, backed by a [Durable Object](/durable-objects/), with its own [SQL database](/agents/runtime/lifecycle/state/). -A Stateless server can define [tools](/agents/model-context-protocol/protocol/tools/) with `@modelcontextprotocol/server` and serve them through `createMcpHandler`. +A stateless server can define [tools](/agents/model-context-protocol/protocol/tools/) with `@modelcontextprotocol/server` and serve them through `createMcpHandler`. But if you want your MCP server to: @@ -269,9 +269,9 @@ export class MyMCP extends McpAgent { -## Legacy Elicitation +## Elicitation on legacy servers -[MCP elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation) lets a server request user input while handling another request, such as a tool call. Legacy Elicitation defines two modes: +[MCP elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation) lets a server request user input while handling another request, such as a tool call. The legacy path defines two modes: - **Form mode** collects structured, non-sensitive data through the client. - **URL mode** sends the user to an out-of-band interaction, such as third-party authorization or payment. @@ -383,7 +383,7 @@ switch (result.action) { :::note[MCP client support] -Not all MCP clients implement Legacy Elicitation. Check the client before depending on it and provide a fallback when appropriate. Agents acting as MCP clients can handle both modes through [MCP client elicitation handlers](/agents/model-context-protocol/apis/client-api/#elicitation). +Not all MCP clients implement legacy elicitation. Check the client before depending on it and provide a fallback when appropriate. Agents acting as MCP clients can handle both modes through [MCP client elicitation handlers](/agents/model-context-protocol/apis/client-api/#elicitation). ::: For more human-in-the-loop patterns, refer to [Human-in-the-loop patterns](/agents/concepts/agentic-patterns/human-in-the-loop/). diff --git a/src/content/docs/agents/model-context-protocol/apis/client-api.mdx b/src/content/docs/agents/model-context-protocol/apis/client-api.mdx index 259e6293ff9..2b061bc435e 100644 --- a/src/content/docs/agents/model-context-protocol/apis/client-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/client-api.mdx @@ -12,7 +12,7 @@ products: import { Render, TypeScriptExample, LinkCard } from "~/components"; -Connect your agent to external [Model Context Protocol (MCP)](/agents/model-context-protocol/) servers to use their tools, resources, and prompts. Agents SDK v0.20.0 uses `@modelcontextprotocol/client` and negotiates Stateless or Legacy behavior automatically. +Connect your agent to external [Model Context Protocol (MCP)](/agents/model-context-protocol/) servers to use their tools, resources, and prompts. Agents SDK v0.20.0 uses `@modelcontextprotocol/client` and negotiates stateless or legacy behavior automatically. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for package, type, OAuth provider, and rollout changes. @@ -395,7 +395,7 @@ for (const prompt of state.prompts) { ### Elicitation -MCP servers can request user input while handling another operation. Stateless Elicitation returns `input_required` and completes through multi-round-trip requests (MRTR). Legacy Elicitation sends pushed `elicitation/create` requests. Both use form and URL modes. +MCP servers can request user input while handling another operation. On the stateless path, elicitation returns `input_required` and completes through multi-round-trip requests (MRTR). On the legacy path, the server sends pushed `elicitation/create` requests. Both use form and URL modes. Register a handler for each mode your Agent supports in `onStart()`. The same handlers serve both lanes: @@ -434,7 +434,7 @@ The `serverId` identifies the connection that sent the request. Use it to tell t #### Capability negotiation and hibernation -The SDK advertises only the modes with configured handlers. Legacy connections advertise them during `initialize`. Stateless requests carry them with request capabilities. A form-only handler advertises form mode. A URL-only handler advertises URL mode. A connection without handlers advertises no elicitation capability, which lets the server use its fallback. +The SDK advertises only the modes with configured handlers. Connections on the legacy path advertise them during `initialize`. Requests on the stateless path carry them with request capabilities. A form-only handler advertises form mode. A URL-only handler advertises URL mode. A connection without handlers advertises no elicitation capability, which lets the server use its fallback. The SDK stores the advertised modes with each server registration. A connection restored after Durable Object hibernation can therefore advertise the same modes when it reconnects. Callback functions remain in memory and reattach when `onStart()` runs. @@ -572,9 +572,9 @@ class MyAgent extends Agent { The example uses a 55-second timeout because MCP SDK requests default to 60 seconds. If your client call sets a longer request timeout, adjust this timeout to finish first. -Refer to the [`mcp-client` example](https://github.com/cloudflare/agents/tree/main/examples/mcp-client) for the browser implementation. [`mcp-elicitation-mrtr`](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) demonstrates Stateless Elicitation. [`mcp-elicitation`](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) demonstrates Legacy Elicitation. +Refer to the [`mcp-client` example](https://github.com/cloudflare/agents/tree/main/examples/mcp-client) for the browser implementation. [`mcp-elicitation-mrtr`](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) demonstrates stateless elicitation. [`mcp-elicitation`](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) demonstrates legacy elicitation. -For server-side patterns, refer to [Stateless Elicitation](/agents/model-context-protocol/apis/handler-api/#stateless-elicitation) and [Legacy Elicitation](/agents/model-context-protocol/apis/agent-api/#legacy-elicitation). +For server-side patterns, refer to [Elicitation with a stateless handler](/agents/model-context-protocol/apis/handler-api/#elicitation-with-a-stateless-handler) and [Elicitation on legacy servers](/agents/model-context-protocol/apis/agent-api/#elicitation-on-legacy-servers). ## Managing servers @@ -872,17 +872,19 @@ export class MyAgent extends Agent { ### `configureElicitationHandlers()` -Configure handlers for Stateless Elicitation and Legacy `elicitation/create` requests. Add a handler for each elicitation mode your Agent supports. +Configure handlers for stateless elicitation and legacy `elicitation/create` requests. Add a handler for each elicitation mode your Agent supports. ```txt this.mcp.configureElicitationHandlers(handlers?: { form?: ( request: ElicitRequest, serverId: string, + signal?: AbortSignal, ) => Promise; url?: ( request: ElicitRequest, serverId: string, + signal?: AbortSignal, ) => Promise; }): void ``` @@ -894,6 +896,7 @@ this.mcp.configureElicitationHandlers(handlers?: { - `url` (function, optional) — Handles URL-mode requests for out-of-band interactions. - `request` (`ElicitRequest`) — The MCP elicitation request. Inspect `request.params.mode` for the mode-specific fields. - `serverId` (string) — The ID of the MCP server connection that sent the request. +- `signal` (`AbortSignal`, optional) — Aborts when the originating MCP operation is cancelled. Each handler returns a promise containing an `ElicitResult`. Return `accept`, `decline`, or `cancel`. Accepted form responses include `content` that matches `requestedSchema`. URL responses omit `content`. @@ -901,7 +904,7 @@ Passing `undefined` clears all configured handlers. #### Capability behavior -The client advertises only modes with configured handlers during Legacy negotiation and on Stateless requests. Handler changes apply immediately to live connections, but servers receive updated advertised modes after those connections reconnect. +The client advertises only modes with configured handlers during legacy negotiation and on stateless requests. Handler changes apply immediately to live connections, but servers receive updated advertised modes after those connections reconnect. The SDK stores the handler-derived modes with each MCP server registration. Restored connections advertise those modes after Durable Object hibernation, and callbacks reattach when `onStart()` runs. diff --git a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx index 2f5a27b5efd..f0ac3b0a8f4 100644 --- a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx @@ -1,7 +1,7 @@ --- pcx_content_type: reference title: MCP handler APIs -description: Create Stateless or Legacy MCP server handlers for Cloudflare Workers with the Agents SDK. +description: Create stateless or legacy MCP server handlers for Cloudflare Workers with the Agents SDK. tags: - MCP sidebar: @@ -16,18 +16,18 @@ The Agents SDK provides two server handler paths: | API | Import path | MCP server package | Behavior | | ------------------------ | ------------------- | ------------------------------ | ---------------------------------------------- | -| `createMcpHandler` | `agents/mcp/server` | `@modelcontextprotocol/server` | Stateless with Legacy compatibility by default | -| `createLegacyMcpHandler` | `agents/mcp` | `@modelcontextprotocol/sdk` | Legacy sessions through `WorkerTransport` | +| `createMcpHandler` | `agents/mcp/server` | `@modelcontextprotocol/server` | stateless with legacy compatibility by default | +| `createLegacyMcpHandler` | `agents/mcp` | `@modelcontextprotocol/sdk` | legacy sessions through `WorkerTransport` | -`McpAgent` remains available for Legacy servers while Stateless equivalents are built. It is deprecated and feature-frozen. Refer to the [migration guide](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for staged rollout guidance. +`McpAgent` remains available for legacy servers while stateless equivalents are built. It is deprecated and feature-frozen. Refer to the [migration guide](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for staged rollout guidance. ## Install dependencies -For a Stateless server: +For a stateless server: -For an explicit Legacy server: +For an explicit legacy server: @@ -115,10 +115,10 @@ The following options are available: | `allowedHostnames` | `string[]` | Localhost or `workers.dev` route | Optional Host restriction for custom domains | | `allowedOriginHostnames` | `string[] \| "*"` | Localhost, `workers.dev`, or concrete CORS Origin | Browser Origin restriction, or explicit middleware delegation | | `authContext` | `McpAuthContext` | Execution context props | Application props returned by `getMcpAuthContext()` | -| `legacy` | `"stateless" \| "reject"` | `"stateless"` | Legacy compatibility or Stateless-only rejection | -| `responseMode` | `"auto" \| "json" \| "sse"` | `"auto"` | Stateless request response shaping | +| `legacy` | `"stateless" \| "reject"` | `"stateless"` | legacy compatibility or stateless-only rejection | +| `responseMode` | `"auto" \| "json" \| "sse"` | `"auto"` | stateless request response shaping | | `onerror` | `(error: Error) => void` | None | Out-of-band error reporting | -| `bus` | `ServerEventBus` | In-memory bus | Event bus for Stateless subscriptions | +| `bus` | `ServerEventBus` | In-memory bus | Event bus for stateless subscriptions | | `maxSubscriptions` | `number` | `1,024` | Maximum concurrent listen streams | | `keepAliveMs` | `number` | `15,000` | Keepalive interval for listen streams | @@ -132,13 +132,13 @@ The handler creates one MCP server for each request. This follows the draft prot Application data can still be durable. Store cross-request data behind an authenticated handle in a Durable Object, D1, KV, or R2 rather than an MCP session ID. -### Stateless Elicitation +### Elicitation with a stateless handler -Stateless Elicitation returns `input_required` and completes through multi-round-trip requests (MRTR). The SDK carries `requestState` and `inputResponses` between requests. The Worker does not remain suspended while a user responds. +Elicitation through a stateless handler returns `input_required` and completes through multi-round-trip requests (MRTR). The SDK carries `requestState` and `inputResponses` between requests. The Worker does not remain suspended while a user responds. Use `inputRequired(...)` to request input. Read accepted form content from `context.mcpReq.inputResponses` with `acceptedContent(...)`. -Refer to the [Stateless Elicitation example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) for a two-round tool flow. For stateful pushed requests, refer to [Legacy Elicitation](/agents/model-context-protocol/apis/agent-api/#legacy-elicitation). +Refer to the [stateless elicitation example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) for a two-round tool flow. For stateful pushed requests, refer to [Elicitation on legacy servers](/agents/model-context-protocol/apis/agent-api/#elicitation-on-legacy-servers). ### Origin validation and CORS @@ -169,11 +169,11 @@ CORS response headers are not authentication. Protect the MCP endpoint with OAut The handler does not infer a Host allowlist from `request.url`. If a deployment accepts arbitrary Host values, validate them before calling the handler. Local servers outside Cloudflare Workers should follow the upstream SDK DNS rebinding guidance. -### Legacy compatibility +### Compatibility with legacy clients -The default `legacy: "stateless"` setting accepts ordinary Legacy tools, prompts, and resources. This lane uses the SDK v2 web-standard transport and does not import `WorkerTransport`. +The default `legacy: "stateless"` setting accepts ordinary legacy tools, prompts, and resources. This lane uses the SDK v2 web-standard transport and does not import `WorkerTransport`. -Legacy compatibility does not provide a complete session transport: +This compatibility path does not provide a complete session transport: - Each POST creates a new server and transport. - HTTP GET and DELETE return `405`. @@ -182,7 +182,7 @@ Legacy compatibility does not provide a complete session transport: - Standalone streams, resumability, replay, and session deletion are unavailable. - Published experimental tasks are not supported through this path. -Set `legacy: "reject"` for a Stateless-only endpoint. Use `createLegacyMcpHandler` or `McpAgent` when a Legacy client needs protocol sessions. +Set `legacy: "reject"` for a stateless-only endpoint. Use `createLegacyMcpHandler` or `McpAgent` when a legacy client needs protocol sessions. ### Return value @@ -203,7 +203,7 @@ handler.bus.publish(event); await handler.close(); ``` -`close()` rejects new requests and closes active Stateless and Legacy compatibility work. +`close()` rejects new requests and closes active stateless and legacy compatibility work. ## `createLegacyMcpHandler` @@ -224,11 +224,11 @@ function createLegacyMcpHandler( ): LegacyMcpHandler; ``` -Use this handler for Legacy sessions, transport storage, event replay, and pushed server-to-client requests. +Use this handler for legacy sessions, transport storage, event replay, and pushed server-to-client requests. - + -```ts title="src/index.ts" +```ts import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { createLegacyMcpHandler } from "agents/mcp"; @@ -330,7 +330,7 @@ Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-t ; ``` diff --git a/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx b/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx index b8825d2f558..225aa749b3e 100644 --- a/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx @@ -33,7 +33,7 @@ Code Mode is experimental and may have breaking changes. Use caution in producti You need a Cloudflare Workers project, an OpenAPI 3.x document, and a host-side method for authenticating API requests. -`openApiMcpServer()` currently returns an SDK v1 server. Serve it through the explicit Legacy `createLegacyMcpHandler` API. +`openApiMcpServer()` currently returns an SDK v1 server. Serve it through the explicit legacy `createLegacyMcpHandler` API. ## Publish the service @@ -141,11 +141,11 @@ You need a Cloudflare Workers project, an OpenAPI 3.x document, and a host-side }, }); - return createLegacyMcpHandler(server, { route: "/mcp" })( - request, - env, - ctx, - ); + return createLegacyMcpHandler(server, { route: "/mcp" })( + request, + env, + ctx, + ); }, } satisfies ExportedHandler; ``` diff --git a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx index 7283f54b215..26065c711dc 100644 --- a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx @@ -1,6 +1,6 @@ --- title: Migrate to MCP SDK v2 -description: Migrate Agents SDK MCP servers and clients to the split MCP TypeScript SDK v2 packages while retaining Legacy compatibility. +description: Migrate Agents SDK MCP servers and clients to the split MCP TypeScript SDK v2 packages while retaining legacy compatibility. pcx_content_type: how-to sidebar: order: 13 @@ -10,7 +10,7 @@ products: import { PackageManagers, Steps, TypeScriptExample } from "~/components"; -This guide covers the MCP SDK v2 upgrade in Agents SDK v0.20.0. It explains how to move Stateless servers to `@modelcontextprotocol/server`, keep existing servers on an explicit Legacy handler, and update MCP clients. +This guide covers the [MCP SDK v2](https://github.com/modelcontextprotocol/typescript-sdk) upgrade in Agents SDK v0.20.0. It explains how to move stateless servers to `@modelcontextprotocol/server`, keep existing servers on an explicit legacy handler, and update MCP clients. ## Choose a server path @@ -19,10 +19,10 @@ Use the following table to select a migration path: | Current server | Migration path | | ------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `createMcpHandler` with an SDK v1 server | Rename the call to `createLegacyMcpHandler` to keep the same behavior. | -| Server ready for Stateless request handling | Move to `@modelcontextprotocol/server` and pass a factory to `createMcpHandler`. | -| Server that must retain Legacy behavior | Keep SDK v1 and use `createLegacyMcpHandler`. | -| `McpAgent` without Legacy stateful features | Migrate directly to an SDK v2 factory and `createMcpHandler`. | -| `McpAgent` using Legacy stateful features | Design Stateless equivalents, serve Stateless and Legacy lanes together, then drain the Legacy lane. | +| Server ready for stateless request handling | Move to `@modelcontextprotocol/server` and pass a factory to `createMcpHandler`. | +| Server that must retain legacy behavior | Keep SDK v1 and use `createLegacyMcpHandler`. | +| `McpAgent` without legacy stateful features | Migrate directly to an SDK v2 factory and `createMcpHandler`. | +| `McpAgent` using legacy stateful features | Design stateless equivalents, serve stateless and legacy lanes together, then drain the legacy lane. | The following APIs are deprecated: @@ -34,11 +34,11 @@ The following APIs are deprecated: Install only the MCP package generations that your application imports. Keep the v2 beta version exact. -For a Stateless server: +For a stateless server: -For an explicit Legacy server: +For an explicit legacy server: @@ -48,7 +48,7 @@ For an Agent that connects to MCP servers: Follow peer dependency instructions from your package manager. The exact v2 pin will change with later Agents releases while the MCP SDK remains in beta. -## Keep existing Legacy behavior +## Keep existing legacy behavior Use this path when your server depends on any of these features: @@ -70,9 +70,9 @@ Use this path when your server depends on any of these features: - + -```ts title="src/index.ts" +```ts import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { createLegacyMcpHandler } from "agents/mcp"; @@ -91,11 +91,11 @@ export default { Create a new SDK v1 server for each request unless you provide a persistent transport that is already connected to that server. Reconnecting one server instance to several transports is invalid. -Refer to the [Legacy Elicitation example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) for a sessionful server with Durable Object state and SSE replay. +Refer to the [legacy elicitation example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) for a sessionful server with Durable Object state and SSE replay. -## Move a Stateless server to SDK v2 +## Move a stateless server to SDK v2 -The Stateless `createMcpHandler` accepts a factory. The factory returns `McpServer` or `Server` from `@modelcontextprotocol/server`. +The stateless `createMcpHandler` accepts a factory. The factory returns `McpServer` or `Server` from `@modelcontextprotocol/server`. @@ -109,13 +109,13 @@ The Stateless `createMcpHandler` accepts a factory. The factory returns `McpServ 5. Remove SDK v1 transport and session options. -6. Test the endpoint with Stateless and Legacy clients. +6. Test the endpoint with stateless and legacy clients. - + -```ts title="src/index.ts" +```ts import { McpServer } from "@modelcontextprotocol/server"; import { createMcpHandler } from "agents/mcp/server"; import { z } from "zod"; @@ -147,7 +147,7 @@ export default createMcpHandler(createServer); The handler creates one server for each request. Concurrent Worker requests never share a connected server instance. -### Stateless handler options +### Handler options for stateless servers The Agents wrapper adds `route`, `corsOptions`, `allowedHostnames`, `allowedOriginHostnames`, and `authContext`. It also passes supported SDK v2 options through to the upstream handler. @@ -156,7 +156,7 @@ Common options include: | Option | Behavior | | ---------------------------------------- | ----------------------------------------------------------------------------------- | | `route` | Sets the exact request path. The default is `/mcp`. | -| `legacy` | Uses Legacy compatibility by default. Set `"reject"` for a Stateless-only endpoint. | +| `legacy` | Uses legacy compatibility by default. Set `"reject"` for a stateless-only endpoint. | | `responseMode` | Selects automatic, JSON, or SSE response handling. | | `allowedHostnames` | Restricts Host headers to specific hostnames. | | `allowedOriginHostnames` | Restricts browser Origins, or accepts `"*"` when trusted middleware validates them. | @@ -164,7 +164,7 @@ Common options include: | `onerror` | Reports handler errors without changing the response. | | `bus`, `maxSubscriptions`, `keepAliveMs` | Configure `subscriptions/listen` delivery. | -The Stateless handler rejects these SDK v1 options: +The stateless handler rejects these SDK v1 options: - `transport` - `storage` @@ -204,9 +204,9 @@ CORS headers do not authenticate a request. Protect the endpoint with OAuth or a The handler does not infer a trusted Host allowlist from `request.url`. If your deployment accepts arbitrary Host values, validate them before calling the handler. For local servers outside Cloudflare Workers, follow the upstream SDK Host and Origin validation guidance. -### Understand Legacy compatibility +### Understand compatibility with legacy clients -The default `legacy: "stateless"` setting supports ordinary Legacy tools, resources, and prompts. This lane uses the SDK v2 web-standard transport. It does not import `WorkerTransport` and is not a complete sessionful transport. +The default `legacy: "stateless"` setting supports ordinary legacy tools, resources, and prompts. This lane uses the SDK v2 web-standard transport. It does not import `WorkerTransport` and is not a complete sessionful transport. The fallback has these limits: @@ -217,34 +217,34 @@ The fallback has these limits: - Standalone streams, event replay, and session deletion are unavailable. - Published experimental tasks are not supported through this fallback. -Use `createLegacyMcpHandler` or `McpAgent` when a Legacy client needs those features. +Use `createLegacyMcpHandler` or `McpAgent` when a legacy client needs those features. -## Migrate an McpAgent server +## Migrate an `McpAgent` server `McpAgent` remains an SDK v1 server. Do not change its server import to `@modelcontextprotocol/server`. -### Migrate directly without Legacy stateful features +### Migrate directly without legacy stateful features If the server does not depend on MCP session state, RPC, pushed server-to-client requests, standalone streams, or event replay, move its tools to an SDK v2 factory and serve it with `createMcpHandler`. ### Plan stateless equivalents for stateful features -If the server uses Legacy stateful features, keep the existing `McpAgent` route while you design and deploy Stateless equivalents: +If the server uses legacy stateful features, keep the existing `McpAgent` route while you design and deploy stateless equivalents: -| Legacy stateful feature | Stateless design | +| Stateful feature on the legacy path | Design for the stateless path | | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Application data keyed by an MCP session | Store data behind an explicit application boundary such as a Durable Object, D1, KV, or R2. Address it with an authenticated, server-issued handle instead of an MCP session ID. | | Multi-step interaction state | Return integrity-protected `requestState` with `input_required`. Bind it to the authenticated user, original method and parameters, and an expiry. | | Pushed elicitation, sampling, or roots requests | Return `inputRequired(...)`. The client fulfils the embedded requests and retries the original operation. | | Standalone list-change stream | Publish changes through `subscriptions/listen`. Clients reopen the subscription if its stream ends. | -| Session replay or transport recovery | Make each Stateless request independently recoverable. Persist business progress in application storage rather than the MCP transport. | +| Session replay or transport recovery | Make each stateless request independently recoverable. Persist business progress in application storage rather than the MCP transport. | | Agent-to-`McpAgent` RPC | Replace the protocol-session dependency with an explicit application RPC or HTTP boundary, then expose the stateless MCP tools separately. | -Do not remove the Legacy route as soon as the Stateless implementation exists. Serve both lanes while clients migrate and existing sessions drain. +Do not remove the legacy route as soon as the stateless implementation exists. Serve both lanes while clients migrate and existing sessions drain. -### Run Stateless and Legacy lanes together +### Run stateless and legacy lanes together -A single URL can route Stateless requests to SDK v2 and Legacy requests to the existing sessionful server. +A single URL can route stateless requests to SDK v2 and legacy requests to the existing sessionful server. @@ -271,29 +271,29 @@ export default { -Keep `legacy: "reject"` on the Stateless handler. Otherwise, its Legacy compatibility lane consumes requests before the sessionful route receives them. +Keep `legacy: "reject"` on the stateless handler. Otherwise, its legacy compatibility lane consumes requests before the sessionful route receives them. -Deploy both routes before moving clients. Monitor the Legacy lane and let existing sessions drain. Remove the Legacy route and its protocol-only Durable Object binding only after no clients depend on them. Handle Durable Object migration configuration as a separate deployment step. +Deploy both routes before moving clients. Monitor the legacy lane and let existing sessions drain. Remove the legacy route and its protocol-only Durable Object binding only after no clients depend on them. Handle Durable Object migration configuration as a separate deployment step. ## Update MCP clients Agents now uses `@modelcontextprotocol/client` internally. Existing `addMcpServer` calls negotiate the protocol era automatically. -Stateless servers use `server/discover`. Agents falls back to `initialize` for Legacy Streamable HTTP, SSE, and RPC servers. +Servers on the stateless path use `server/discover`. Agents falls back to `initialize` for legacy Streamable HTTP, SSE, and RPC servers. The client API includes these changes: - `callTool(params, options)` is the preferred signature. - `callTool(params, resultSchema, options)` remains available but is deprecated. - MCP client types now come from `@modelcontextprotocol/client`. -- Required Stateless HTTP headers are handled by the SDK. -- List changes use Stateless subscriptions or Legacy notifications based on the negotiated lane. +- Required stateless HTTP headers are handled by the SDK. +- List changes use stateless subscriptions or legacy notifications based on the negotiated lane. -### Configure Stateless Elicitation +### Configure elicitation for stateless requests -Stateless tools, prompts, and resources can return `input_required` through multi-round-trip requests (MRTR). The SDK calls the configured elicitation handler and retries the original operation. Your original `callTool`, `getPrompt`, or `readResource` promise remains pending. +Tools, prompts, and resources on the stateless path can return `input_required` through multi-round-trip requests (MRTR). The SDK calls the configured elicitation handler and retries the original operation. Your original `callTool`, `getPrompt`, or `readResource` promise remains pending. -Refer to the [Stateless Elicitation example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) for a two-round tool flow. +Refer to the [stateless elicitation example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) for a two-round tool flow. @@ -333,16 +333,16 @@ SDK v2 validates OAuth metadata issuers by default. A trusted legacy server with ## Review protocol differences -Stateless MCP changes the transport and lifecycle model: +MCP's stateless model changes the transport and lifecycle: -| Area | Legacy behavior | Stateless behavior | -| --------------------- | --------------------------------------------------- | ---------------------------------------------------------------------- | -| Startup | `initialize` handshake | No handshake; `server/discover` is optional for clients | -| Request metadata | Connection-scoped negotiation | Version, client capabilities, and identity metadata on each request | -| Sessions | Optional `Mcp-Session-Id` | No protocol session | -| Server input requests | Server sends JSON-RPC requests | Server returns `input_required`; client retries the original operation | -| Change notifications | Standalone GET stream and list-change notifications | `subscriptions/listen` POST with an SSE response | -| Stream recovery | `Last-Event-ID` can resume configured streams | Listen streams reopen after failure; no `Last-Event-ID` replay | +| Area | Previous behavior | New behavior | +| --------------------- | --------------------------------------------------- | ------------------------------------------------------------------------ | +| Startup | `initialize` handshake | No handshake. `server/discover` is optional for clients | +| Request metadata | Connection-scoped negotiation | Version, client capabilities, and identity metadata on each request | +| Sessions | Optional `Mcp-Session-Id` | No protocol session | +| Server input requests | Server sends JSON-RPC requests | Server returns `input_required`. Client retries the original operation | +| Change notifications | Standalone GET stream and list-change notifications | `subscriptions/listen` POST with an SSE response | +| Stream recovery | `Last-Event-ID` can resume configured streams | Listen streams reopen after failure. There is no `Last-Event-ID` replay. | Custom transports, proxies, and gateways must preserve the draft request headers: @@ -351,9 +351,9 @@ Custom transports, proxies, and gateways must preserve the draft request headers - `Mcp-Name` for tool, prompt, and resource operations - Declared `Mcp-Param-*` tool headers -The exact beta used by Agents is a draft snapshot. Beta.5 makes `clientInfo` optional and places server identity in result `_meta`. Use high-level SDK APIs and update MCP packages with the Agents release that supports each snapshot. Raw Stateless results must include `resultType`. +The exact beta used by Agents is a draft snapshot. Beta.5 makes `clientInfo` optional and places server identity in result `_meta`. Use high-level SDK APIs and update MCP packages with the Agents release that supports each snapshot. Raw stateless results must include `resultType`. -The draft deprecates Roots, Sampling, Logging, the old HTTP+SSE transport, and Dynamic Client Registration. The types remain available during the deprecation window for Legacy compatibility. The published experimental task methods become the `io.modelcontextprotocol/tasks` extension. Agents SDK v0.20.0 does not add that extension. +The draft deprecates Roots, Sampling, Logging, the old HTTP+SSE transport, and Dynamic Client Registration. The types remain available during the deprecation window for legacy compatibility. The published experimental task methods become the `io.modelcontextprotocol/tasks` extension. Agents SDK v0.20.0 does not add that extension. ### Integration compatibility @@ -369,15 +369,15 @@ Serve these results with `createLegacyMcpHandler`. The Code Mode MCP connector a -1. Classify each endpoint as Stateless, explicit Legacy, or `McpAgent`. +1. Classify each endpoint as stateless, explicit legacy, or `McpAgent`. 2. Pin the MCP SDK versions required by the Agents release. 3. Rename SDK v1 handler calls before changing their behavior. -4. Add Stateless handlers beside existing sessionful routes. +4. Add stateless handlers beside existing sessionful routes. -5. Test Stateless and Legacy clients independently. +5. Test stateless and legacy clients independently. 6. Test required HTTP headers through every proxy and gateway. @@ -387,7 +387,7 @@ Serve these results with `createLegacyMcpHandler`. The Code Mode MCP connector a 9. Verify valid Origins and reject invalid Origins with `403`. -10. Remove Legacy routes only after existing sessions drain. +10. Remove legacy routes only after existing sessions drain. diff --git a/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx b/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx index 9733ec419a1..e0721e82d9b 100644 --- a/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx @@ -23,12 +23,12 @@ The Agents SDK provides multiple ways to create MCP servers. Choose the approach | Approach | Stateful? | Protocol path | Best for | | ----------------------------------------------------------------------------------------------------- | -------------------- | ----------------------------------- | --------------------------------------- | -| [`createMcpHandler()`](/agents/model-context-protocol/apis/handler-api/) | No | Stateless with Legacy compatibility | New Stateless tools | -| [`createLegacyMcpHandler()`](/agents/model-context-protocol/apis/handler-api/#createlegacymcphandler) | Optional | Legacy | Existing `WorkerTransport` servers | -| [`McpAgent`](/agents/model-context-protocol/apis/agent-api/) | Yes | Legacy | Existing Durable Object and RPC servers | +| [`createMcpHandler()`](/agents/model-context-protocol/apis/handler-api/) | No | stateless with legacy compatibility | New stateless tools | +| [`createLegacyMcpHandler()`](/agents/model-context-protocol/apis/handler-api/#createlegacymcphandler) | Optional | legacy | Existing `WorkerTransport` servers | +| [`McpAgent`](/agents/model-context-protocol/apis/agent-api/) | Yes | legacy | Existing Durable Object and RPC servers | | Raw SDK transport | Depends on transport | Depends on SDK package | Custom transport ownership | -Use `createMcpHandler` for a new stateless server. An existing `McpAgent` without legacy stateful dependencies can migrate directly. If it uses MCP session state, RPC, pushed requests, streams, or replay, plan the Stateless equivalents and serve Stateless and Legacy lanes during the transition. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the staged rollout. +Use `createMcpHandler` for a new stateless server. An existing `McpAgent` without legacy stateful dependencies can migrate directly. If it uses MCP session state, RPC, pushed requests, streams, or replay, plan the stateless equivalents and serve stateless and legacy lanes during the transition. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the staged rollout. ## Deploy your first MCP server diff --git a/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx b/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx index 36af057713b..834f58b6a7d 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx @@ -202,7 +202,7 @@ export class MyMCP extends McpAgent { } ``` -### With Stateless createMcpHandler +### With stateless `createMcpHandler` A compatible Workers OAuth Provider supplies standard token metadata at `context.http.authInfo`. Use `getMcpAuthContext()` for existing application props. diff --git a/src/content/docs/agents/model-context-protocol/protocol/tools.mdx b/src/content/docs/agents/model-context-protocol/protocol/tools.mdx index 6859740e1d9..cc09b4d5a0b 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/tools.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/tools.mdx @@ -14,7 +14,7 @@ import { TypeScriptExample, LinkCard } from "~/components"; MCP tools are functions that an [MCP server](/agents/model-context-protocol/) exposes for clients to call. An LLM can invoke a tool to look up data, run a calculation, or call an API. The MCP server executes the tool and returns its result. -Use `@modelcontextprotocol/server` for a Stateless `createMcpHandler` server. Existing Legacy `McpAgent` servers must keep using `@modelcontextprotocol/sdk`. +Use `@modelcontextprotocol/server` for a stateless `createMcpHandler` server. Existing legacy `McpAgent` servers must keep using `@modelcontextprotocol/sdk`. :::note[Experimental WebMCP adapter] @@ -30,7 +30,7 @@ The Agents SDK also includes the experimental `agents/experimental/webmcp` adapt ## Defining tools -Use `server.registerTool()` to register a tool on a Stateless `McpServer` instance. Each tool has a name, a description, an input schema defined with [Zod](https://zod.dev), and a handler function. +Use `server.registerTool()` to register a tool on a stateless `McpServer` instance. Each tool has a name, a description, an input schema defined with [Zod](https://zod.dev), and a handler function. @@ -170,7 +170,7 @@ export default createMcpHandler(createServer); ## Using tools with `McpAgent` -For Legacy stateful MCP servers, define tools in the `init()` method of an [`McpAgent`](/agents/model-context-protocol/apis/agent-api/). Tools have access to the agent instance via `this`, which means they can read and write state. +For legacy stateful MCP servers, define tools in the `init()` method of an [`McpAgent`](/agents/model-context-protocol/apis/agent-api/). Tools have access to the agent instance via `this`, which means they can read and write state. diff --git a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx index d255477ef2b..f7099e5c381 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx @@ -87,13 +87,13 @@ export default new OAuthProvider({ -### Legacy servers +### Servers that need protocol sessions -Stateless MCP has no protocol-level session. Applications can store durable business data behind a separate storage boundary. +MCP has no protocol-level session on the stateless path. Applications can store durable business data behind a separate storage boundary. -Servers that require Legacy sessions can use `createLegacyMcpHandler` with `WorkerTransport`, or keep `McpAgent` while their Stateless equivalents are built. These APIs support transport state, event replay, pushed elicitation, sampling, and roots requests. `McpAgent` is deprecated and feature-frozen. +Servers that require legacy sessions can use `createLegacyMcpHandler` with `WorkerTransport`, or keep `McpAgent` while their stateless equivalents are built. These APIs support transport state, event replay, pushed elicitation, sampling, and roots requests. `McpAgent` is deprecated and feature-frozen. -Add the Stateless route before moving clients, and keep both lanes until existing sessions drain. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the staged migration. Refer to [`McpAgent`: Stream resumability](/agents/model-context-protocol/apis/agent-api/#stream-resumability) for existing stream behavior. +Add the stateless route before moving clients, and keep both lanes until existing sessions drain. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the staged migration. Refer to [`McpAgent`: Stream resumability](/agents/model-context-protocol/apis/agent-api/#stream-resumability) for existing stream behavior. ## RPC transport @@ -310,13 +310,13 @@ export class MyMCP extends McpAgent { | ------------------- | ------------------------------------- | ---------------------------------------- | ------------------------------------- | | **Streamable HTTP** | External MCP servers, production apps | Standard protocol, secure, supports auth | Slight network overhead | | **RPC** | Internal agents on Cloudflare | Fastest, simplest setup | No auth, Durable Object bindings only | -| **SSE** | Legacy compatibility | Backwards compatible | Deprecated, use Streamable HTTP | +| **SSE** | Compatibility with older clients | Backwards compatible | Deprecated, use Streamable HTTP | ### Migrate from McpAgent -If the endpoint does not use Legacy stateful features, migrate directly to a Stateless server factory from `@modelcontextprotocol/server` and pass it to `createMcpHandler`. +If the endpoint does not use legacy stateful features, migrate directly to a stateless server factory from `@modelcontextprotocol/server` and pass it to `createMcpHandler`. -If it depends on MCP session state, RPC, pushed server-to-client requests, standalone streams, or replay, first design Stateless equivalents. For example, move business state behind explicit application storage and replace pushed input requests with Stateless Elicitation. Serve Stateless and Legacy lanes together while clients migrate and existing sessions drain. +If it depends on MCP session state, RPC, pushed server-to-client requests, standalone streams, or replay, first design stateless equivalents. For example, move business state behind explicit application storage and replace pushed input requests with stateless elicitation. Serve stateless and legacy lanes together while clients migrate and existing sessions drain. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the feature mapping, dual-era routing, and rollout steps. From 770d8abd49e31c8ea2975493ad67dbb21cee410e Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Mon, 27 Jul 2026 12:39:00 +0100 Subject: [PATCH 08/16] fix: address PR #32175 automated review - create fresh SDK v1 servers per request in the changelog - validate API responses before parsing their bodies - clarify the protocol path used by quick-deploy templates --- ...26-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx | 12 ++++++++--- .../guides/build-codemode-mcp-server.mdx | 10 +++++----- .../build-codemode-openapi-mcp-server.mdx | 20 +++++++++---------- .../guides/remote-mcp-server.mdx | 4 ++++ 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx index 3696c43db3d..2c53648521f 100644 --- a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx +++ b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx @@ -46,9 +46,15 @@ Existing SDK v1 servers can rename `createMcpHandler` to `createLegacyMcpHandler import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { createLegacyMcpHandler } from "agents/mcp"; -export default createLegacyMcpHandler( - new McpServer({ name: "legacy", version: "1.0.0" }), -); +function createServer() { + return new McpServer({ name: "legacy", version: "1.0.0" }); +} + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext) { + return createLegacyMcpHandler(createServer())(request, env, ctx); + }, +} satisfies ExportedHandler; ``` diff --git a/src/content/docs/agents/model-context-protocol/guides/build-codemode-mcp-server.mdx b/src/content/docs/agents/model-context-protocol/guides/build-codemode-mcp-server.mdx index 55c676f1e13..72c604db238 100644 --- a/src/content/docs/agents/model-context-protocol/guides/build-codemode-mcp-server.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/build-codemode-mcp-server.mdx @@ -107,11 +107,11 @@ You need a Cloudflare Workers project and an existing `McpServer`. executor, }); - return createLegacyMcpHandler(server, { route: "/mcp" })( - request, - env, - ctx, - ); + return createLegacyMcpHandler(server, { route: "/mcp" })( + request, + env, + ctx, + ); }, } satisfies ExportedHandler; ``` diff --git a/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx b/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx index 225aa749b3e..e5fd57bf884 100644 --- a/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server.mdx @@ -127,25 +127,23 @@ You need a Cloudflare Workers project, an OpenAPI 3.x document, and a host-side : JSON.stringify(options.body), }); + if (!response.ok) { + throw new Error(`API request failed: ${response.status}`); + } if (response.status === 204) return null; const responseType = response.headers.get("Content-Type") ?? ""; - const result = responseType.includes("application/json") + return responseType.includes("application/json") ? await response.json() : await response.text(); - - if (!response.ok) { - throw new Error(`API request failed: ${response.status}`); - } - return result; }, }); - return createLegacyMcpHandler(server, { route: "/mcp" })( - request, - env, - ctx, - ); + return createLegacyMcpHandler(server, { route: "/mcp" })( + request, + env, + ctx, + ); }, } satisfies ExportedHandler; ``` diff --git a/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx b/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx index e0721e82d9b..f27677bc8d9 100644 --- a/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx @@ -32,6 +32,10 @@ Use `createMcpHandler` for a new stateless server. An existing `McpAgent` withou ## Deploy your first MCP server +:::note[Template protocol path] +The quick-deploy templates in this section currently use the legacy `McpAgent` path. It remains supported for sessionful deployments. For a new stateless server, start with the [`mcp-worker` example](https://github.com/cloudflare/agents/tree/main/examples/mcp-worker). +::: + You can start by deploying a [public MCP server](https://github.com/cloudflare/ai/tree/main/demos/remote-mcp-authless) without authentication, then add user authentication and scoped authorization later. If you already know your server will require authentication, you can skip ahead to the [next section](/agents/model-context-protocol/guides/remote-mcp-server/#add-authentication). ### Via the dashboard From 33275aa9a056437d456414e40aca79421b7aa90f Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Mon, 27 Jul 2026 12:46:16 +0100 Subject: [PATCH 09/16] docs(agents): explain why stateless MCP matters --- .../agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx index 2c53648521f..a9fc78bbcff 100644 --- a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx +++ b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx @@ -9,7 +9,7 @@ date: 2026-07-27 import { PackageManagers, TypeScriptExample } from "~/components"; -The [MCP 2026-07-28 release candidate](https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/) makes the protocol stateless by default. Agents SDK v0.20.0 adopts this model through the split MCP TypeScript SDK v2 packages. New MCP servers can run in a Worker without keeping transport state in a Durable Object, and clients can call tools without an `initialize` handshake. Applications can still use Durable Objects or other storage for their own state. +With support for the [MCP 2026-07-28 release candidate](https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/), Agents SDK v0.20.0 lets you run MCP tools, prompts, resources, and elicitation statelessly in a Worker, so MCP transport no longer needs a Durable Object. You can still use Durable Objects or other storage for application state. Agents clients negotiate stateless or legacy behavior automatically. Existing legacy server deployments remain supported through `createLegacyMcpHandler`, `WorkerTransport`, and `McpAgent`. From fd0b83f3daf70fcd3d2fff895f3cd03a33df99d2 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Mon, 27 Jul 2026 14:34:28 +0100 Subject: [PATCH 10/16] docs(agents): clarify sessionful template support --- .../agents/model-context-protocol/guides/remote-mcp-server.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx b/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx index f27677bc8d9..5b95c594460 100644 --- a/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx @@ -33,7 +33,7 @@ Use `createMcpHandler` for a new stateless server. An existing `McpAgent` withou ## Deploy your first MCP server :::note[Template protocol path] -The quick-deploy templates in this section currently use the legacy `McpAgent` path. It remains supported for sessionful deployments. For a new stateless server, start with the [`mcp-worker` example](https://github.com/cloudflare/agents/tree/main/examples/mcp-worker). +The quick-deploy templates in this section currently use the legacy `McpAgent` path. You can still use it for sessionful deployments. For a new stateless server, start with the [`mcp-worker` example](https://github.com/cloudflare/agents/tree/main/examples/mcp-worker). ::: You can start by deploying a [public MCP server](https://github.com/cloudflare/ai/tree/main/demos/remote-mcp-authless) without authentication, then add user authentication and scoped authorization later. If you already know your server will require authentication, you can skip ahead to the [next section](/agents/model-context-protocol/guides/remote-mcp-server/#add-authentication). From d9cd79f16e46a797becb4d7168a88dbf09a3787d Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Mon, 27 Jul 2026 15:50:53 +0100 Subject: [PATCH 11/16] docs(agents): preserve Worker handler export during MCP migration Document the SDK v1 instance-to-v2 factory change without replacing the Worker object export with a callable default. Clarify that MRTR input responses are per-round and intermediate values belong in protected requestState. --- ...26-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx | 6 ++- .../apis/handler-api.mdx | 34 ++++++++------ .../guides/migrate-to-mcp-sdk-v2.mdx | 44 ++++++++++++++----- .../protocol/authorization.mdx | 6 ++- .../model-context-protocol/protocol/tools.mdx | 6 ++- .../protocol/transport.mdx | 6 ++- 6 files changed, 75 insertions(+), 27 deletions(-) diff --git a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx index a9fc78bbcff..e11c4c8f045 100644 --- a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx +++ b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx @@ -27,7 +27,11 @@ function createServer() { return new McpServer({ name: "example", version: "1.0.0" }); } -export default createMcpHandler(createServer); +export default { + fetch(request, env, ctx) { + return createMcpHandler(createServer).fetch(request, env, ctx); + }, +} satisfies ExportedHandler; ``` diff --git a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx index f0ac3b0a8f4..9cd3c5f848a 100644 --- a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx @@ -35,7 +35,7 @@ Use the exact MCP versions required by your installed Agents release while the v ## `createMcpHandler` -`createMcpHandler` creates a stateless Worker handler from an MCP SDK v2 server factory. +`createMcpHandler` creates a callable stateless MCP request handler from an MCP SDK v2 server factory. Invoke it from a Worker's object `fetch()` export or compose it inside another handler. ```ts import { @@ -97,7 +97,11 @@ function createServer() { return server; } -export default createMcpHandler(createServer); +export default { + fetch(request, env, ctx) { + return createMcpHandler(createServer).fetch(request, env, ctx); + }, +} satisfies ExportedHandler; ``` @@ -134,9 +138,9 @@ Application data can still be durable. Store cross-request data behind an authen ### Elicitation with a stateless handler -Elicitation through a stateless handler returns `input_required` and completes through multi-round-trip requests (MRTR). The SDK carries `requestState` and `inputResponses` between requests. The Worker does not remain suspended while a user responds. +Elicitation through a stateless handler returns `input_required` and completes through multi-round-trip requests (MRTR). On each retry, the SDK echoes the latest `requestState` and sends responses for the immediately preceding input round. It does not accumulate earlier `inputResponses`. The Worker does not remain suspended while a user responds. -Use `inputRequired(...)` to request input. Read accepted form content from `context.mcpReq.inputResponses` with `acceptedContent(...)`. +Use `inputRequired(...)` to request input. Read that round's accepted form content from `context.mcpReq.inputResponses` with `acceptedContent(...)`. Seal trusted intermediate values needed by later rounds into integrity-protected `requestState`. Refer to the [stateless elicitation example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) for a two-round tool flow. For stateful pushed requests, refer to [Elicitation on legacy servers](/agents/model-context-protocol/apis/agent-api/#elicitation-on-legacy-servers). @@ -151,12 +155,16 @@ For a custom domain with wildcard CORS, set `allowedHostnames` and `allowedOrigi ```ts -export default createMcpHandler(createServer, { - allowedHostnames: ["mcp.example.com"], - corsOptions: { - origin: "https://app.example.com", +export default { + fetch(request, env, ctx) { + return createMcpHandler(createServer, { + allowedHostnames: ["mcp.example.com"], + corsOptions: { + origin: "https://app.example.com", + }, + }).fetch(request, env, ctx); }, -}); +} satisfies ExportedHandler; ``` @@ -186,14 +194,14 @@ Set `legacy: "reject"` for a stateless-only endpoint. Use `createLegacyMcpHandle ### Return value -The returned handler is callable as a Worker fetch handler: +The returned handler is callable when composing MCP routing inside another Worker handler: ```ts -const handler = createMcpHandler(createServer); - -const response = await handler(request, env, ctx); +const response = await createMcpHandler(createServer)(request, env, ctx); ``` +Do not export the callable directly as a Worker's default export. Wrangler treats function default exports as `WorkerEntrypoint` classes. Invoke it from an object handler's `fetch()` method, as shown above. + It also exposes the upstream handler controls: ```ts diff --git a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx index 26065c711dc..80ff1fcde92 100644 --- a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx @@ -105,11 +105,13 @@ The stateless `createMcpHandler` accepts a factory. The factory returns `McpServ 3. Move server construction and registration into a factory. -4. Pass the factory itself to `createMcpHandler`. +4. Keep the Worker object default export. Inside its `fetch()` method, pass the factory to `createMcpHandler` and call the returned handler's `.fetch()` method. -5. Remove SDK v1 transport and session options. +5. Do not default-export the callable returned by the Agents handler. Wrangler interprets function default exports as `WorkerEntrypoint` classes. -6. Test the endpoint with stateless and legacy clients. +6. Remove SDK v1 transport and session options. + +7. Test the endpoint with stateless and legacy clients. @@ -140,12 +142,28 @@ function createServer() { return server; } -export default createMcpHandler(createServer); +export default { + fetch(request, env, ctx) { + return createMcpHandler(createServer).fetch(request, env, ctx); + }, +} satisfies ExportedHandler; ``` -The handler creates one server for each request. Concurrent Worker requests never share a connected server instance. +The Worker entrypoint remains an object. Only the handler call changes: + +```ts +// SDK v1: pass a fresh constructed server. +return createMcpHandler(createServer())(request, env, ctx); + +// SDK v2: pass the factory itself. +return createMcpHandler(createServer).fetch(request, env, ctx); +``` + +Do not simplify this to `export default createMcpHandler(createServer)`. The Agents handler is callable for composition inside another handler, but Wrangler treats any function default export as a `WorkerEntrypoint` class. + +The SDK v2 handler creates one server for each MCP request. Concurrent Worker requests never share a connected server instance. ### Handler options for stateless servers @@ -189,11 +207,15 @@ For a custom domain with wildcard CORS, configure both Host and Origin restricti ```ts -export default createMcpHandler(createServer, { - allowedHostnames: ["mcp.example.com"], - allowedOriginHostnames: ["app.example.com"], - corsOptions: { origin: "https://app.example.com" }, -}); +export default { + fetch(request, env, ctx) { + return createMcpHandler(createServer, { + allowedHostnames: ["mcp.example.com"], + allowedOriginHostnames: ["app.example.com"], + corsOptions: { origin: "https://app.example.com" }, + }).fetch(request, env, ctx); + }, +} satisfies ExportedHandler; ``` @@ -293,6 +315,8 @@ The client API includes these changes: Tools, prompts, and resources on the stateless path can return `input_required` through multi-round-trip requests (MRTR). The SDK calls the configured elicitation handler and retries the original operation. Your original `callTool`, `getPrompt`, or `readResource` promise remains pending. +Each retry contains responses for the immediately preceding input round, not every earlier response. The client also echoes the latest opaque `requestState`. Seal trusted intermediate values needed by later rounds into integrity-protected `requestState`; do not expect `inputResponses` to accumulate across rounds. + Refer to the [stateless elicitation example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) for a two-round tool flow. diff --git a/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx b/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx index 834f58b6a7d..f612205e316 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx @@ -233,7 +233,11 @@ function createServer() { return server; } -export default createMcpHandler(createServer); +export default { + fetch(request, env, ctx) { + return createMcpHandler(createServer).fetch(request, env, ctx); + }, +} satisfies ExportedHandler; ``` Do not log or return the raw access token. diff --git a/src/content/docs/agents/model-context-protocol/protocol/tools.mdx b/src/content/docs/agents/model-context-protocol/protocol/tools.mdx index cc09b4d5a0b..242836dae9f 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/tools.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/tools.mdx @@ -163,7 +163,11 @@ function createServer() { return server; } -export default createMcpHandler(createServer); +export default { + fetch(request, env, ctx) { + return createMcpHandler(createServer).fetch(request, env, ctx); + }, +} satisfies ExportedHandler; ``` diff --git a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx index f7099e5c381..fab12872022 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx @@ -66,7 +66,11 @@ function createServer() { return server; } -export default createMcpHandler(createServer); +export default { + fetch(request, env, ctx) { + return createMcpHandler(createServer).fetch(request, env, ctx); + }, +} satisfies ExportedHandler; ``` From f40a0fa5de56729994edb2e44451def5c1145a66 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Mon, 27 Jul 2026 16:13:54 +0100 Subject: [PATCH 12/16] docs(agents): clarify callable MCP handler migration Keep the established object Worker export while changing only the createMcpHandler argument from an SDK v1 server instance to an SDK v2 factory. Document that direct invocation remains supported and .fetch is optional. --- .../agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx | 2 +- .../agents/model-context-protocol/apis/handler-api.mdx | 4 ++-- .../guides/migrate-to-mcp-sdk-v2.mdx | 8 ++++---- .../model-context-protocol/protocol/authorization.mdx | 2 +- .../docs/agents/model-context-protocol/protocol/tools.mdx | 2 +- .../agents/model-context-protocol/protocol/transport.mdx | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx index e11c4c8f045..1188e6b4c5a 100644 --- a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx +++ b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx @@ -29,7 +29,7 @@ function createServer() { export default { fetch(request, env, ctx) { - return createMcpHandler(createServer).fetch(request, env, ctx); + return createMcpHandler(createServer)(request, env, ctx); }, } satisfies ExportedHandler; ``` diff --git a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx index 9cd3c5f848a..a0e4f74bd96 100644 --- a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx @@ -99,7 +99,7 @@ function createServer() { export default { fetch(request, env, ctx) { - return createMcpHandler(createServer).fetch(request, env, ctx); + return createMcpHandler(createServer)(request, env, ctx); }, } satisfies ExportedHandler; ``` @@ -162,7 +162,7 @@ export default { corsOptions: { origin: "https://app.example.com", }, - }).fetch(request, env, ctx); + })(request, env, ctx); }, } satisfies ExportedHandler; ``` diff --git a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx index 80ff1fcde92..5fa2c3f835c 100644 --- a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx @@ -105,7 +105,7 @@ The stateless `createMcpHandler` accepts a factory. The factory returns `McpServ 3. Move server construction and registration into a factory. -4. Keep the Worker object default export. Inside its `fetch()` method, pass the factory to `createMcpHandler` and call the returned handler's `.fetch()` method. +4. Keep the Worker object default export. Inside its `fetch()` method, pass the factory to `createMcpHandler` and invoke the returned callable as before. Calling its `.fetch()` method is equivalent. 5. Do not default-export the callable returned by the Agents handler. Wrangler interprets function default exports as `WorkerEntrypoint` classes. @@ -144,7 +144,7 @@ function createServer() { export default { fetch(request, env, ctx) { - return createMcpHandler(createServer).fetch(request, env, ctx); + return createMcpHandler(createServer)(request, env, ctx); }, } satisfies ExportedHandler; ``` @@ -158,7 +158,7 @@ The Worker entrypoint remains an object. Only the handler call changes: return createMcpHandler(createServer())(request, env, ctx); // SDK v2: pass the factory itself. -return createMcpHandler(createServer).fetch(request, env, ctx); +return createMcpHandler(createServer)(request, env, ctx); ``` Do not simplify this to `export default createMcpHandler(createServer)`. The Agents handler is callable for composition inside another handler, but Wrangler treats any function default export as a `WorkerEntrypoint` class. @@ -213,7 +213,7 @@ export default { allowedHostnames: ["mcp.example.com"], allowedOriginHostnames: ["app.example.com"], corsOptions: { origin: "https://app.example.com" }, - }).fetch(request, env, ctx); + })(request, env, ctx); }, } satisfies ExportedHandler; ``` diff --git a/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx b/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx index f612205e316..2f1e2d472a9 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx @@ -235,7 +235,7 @@ function createServer() { export default { fetch(request, env, ctx) { - return createMcpHandler(createServer).fetch(request, env, ctx); + return createMcpHandler(createServer)(request, env, ctx); }, } satisfies ExportedHandler; ``` diff --git a/src/content/docs/agents/model-context-protocol/protocol/tools.mdx b/src/content/docs/agents/model-context-protocol/protocol/tools.mdx index 242836dae9f..aeabe6a9468 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/tools.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/tools.mdx @@ -165,7 +165,7 @@ function createServer() { export default { fetch(request, env, ctx) { - return createMcpHandler(createServer).fetch(request, env, ctx); + return createMcpHandler(createServer)(request, env, ctx); }, } satisfies ExportedHandler; ``` diff --git a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx index fab12872022..1301ce211b9 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx @@ -68,7 +68,7 @@ function createServer() { export default { fetch(request, env, ctx) { - return createMcpHandler(createServer).fetch(request, env, ctx); + return createMcpHandler(createServer)(request, env, ctx); }, } satisfies ExportedHandler; ``` From b0d64ff2eabc3c3655a4786c30504139b87f2222 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Mon, 27 Jul 2026 16:22:50 +0100 Subject: [PATCH 13/16] docs(agents): document stateless handler controls Reference every public StatelessMcpHandler member, including both fetch forms, lifecycle shutdown, typed notifications, event bus events, and the handler lifetime required for subscriptions. --- .../apis/handler-api.mdx | 103 ++++++++++++++++-- 1 file changed, 95 insertions(+), 8 deletions(-) diff --git a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx index a0e4f74bd96..4353b2b7448 100644 --- a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx @@ -194,24 +194,111 @@ Set `legacy: "reject"` for a stateless-only endpoint. Use `createLegacyMcpHandle ### Return value -The returned handler is callable when composing MCP routing inside another Worker handler: +`createMcpHandler` returns a `StatelessMcpHandler`. It is callable and exposes request, lifecycle, notification, and event-bus controls: ```ts -const response = await createMcpHandler(createServer)(request, env, ctx); +interface StatelessMcpHandler { + (request: Request, env: unknown, ctx: ExecutionContext): Promise; + + fetch: { + (request: Request, options?: McpHandlerRequestOptions): Promise; + (request: Request, env: unknown, ctx: ExecutionContext): Promise; + }; + + close(): Promise; + + notify: { + toolsChanged(): void; + promptsChanged(): void; + resourcesChanged(): void; + resourceUpdated(uri: string): void; + }; + + bus: { + publish(event: ServerEvent): void; + subscribe(listener: (event: ServerEvent) => void): () => void; + }; +} + +type McpHandlerRequestOptions = { + authInfo?: AuthInfo; + parsedBody?: unknown; +}; + +type ServerEvent = + | { kind: "tools_list_changed" } + | { kind: "prompts_list_changed" } + | { kind: "resources_list_changed" } + | { kind: "resource_updated"; uri: string }; +``` + +#### Invoke the handler + +Call the handler from a Worker's object `fetch()` export: + +```ts +export default { + fetch(request, env, ctx) { + return createMcpHandler(createServer)(request, env, ctx); + }, +} satisfies ExportedHandler; +``` + +The three-argument `handler.fetch(request, env, ctx)` overload is equivalent. Do not export the callable directly as a Worker's default export. Wrangler treats function default exports as `WorkerEntrypoint` classes. + +Use the request-options overload when another framework or authentication layer has already parsed or validated request data: + +```ts +const response = await handler.fetch(request, { + authInfo, + parsedBody, +}); ``` -Do not export the callable directly as a Worker's default export. Wrangler treats function default exports as `WorkerEntrypoint` classes. Invoke it from an object handler's `fetch()` method, as shown above. +`authInfo` is passed to the server factory and request handlers. The handler does not derive it from request headers or verify access tokens. `parsedBody` avoids reparsing a JSON body that upstream middleware already consumed. -It also exposes the upstream handler controls: +#### Close the handler ```ts -handler.fetch(request, { authInfo }); -handler.notify.toolsChanged(); -handler.bus.publish(event); await handler.close(); ``` -`close()` rejects new requests and closes active stateless and legacy compatibility work. +`close()` rejects new requests, closes active modern exchanges, and closes active work in the stateless legacy compatibility handler. + +#### Publish list and resource changes + +The `notify` methods publish typed change events to matching open `subscriptions/listen` streams: + +| Method | MCP notification | +| ----------------------------- | -------------------------------------- | +| `notify.toolsChanged()` | `notifications/tools/list_changed` | +| `notify.promptsChanged()` | `notifications/prompts/list_changed` | +| `notify.resourcesChanged()` | `notifications/resources/list_changed` | +| `notify.resourceUpdated(uri)` | `notifications/resources/updated` | + +Calling a notifier when no matching subscription is open is a no-op. + +The lower-level `bus` carries the same events. `publish(event)` synchronously publishes to registered listeners. `subscribe(listener)` registers a listener and returns an idempotent unsubscribe function. Prefer the typed `notify` methods when publishing standard MCP changes. + +By default, each handler owns an in-process event bus. You can provide a `ServerEventBus` through the `bus` option when events must cross processes or isolates. + +#### Keep one handler for subscription controls + +The controls belong to the handler instance. Constructing a new handler inside every Worker `fetch()` call is suitable for ordinary tools, prompts, resources, and MRTR elicitation. It creates a new in-memory event bus for every request, however, so later requests cannot notify a `subscriptions/listen` stream owned by an earlier handler. + +Create the handler once at module scope when using `notify`, `bus`, `close`, or in-process subscriptions, then invoke it from the Worker object export: + +```ts +const handler = createMcpHandler(createServer); + +export default { + fetch(request, env, ctx) { + return handler(request, env, ctx); + }, +} satisfies ExportedHandler; +``` + +For events spanning multiple Worker isolates, supply a shared `ServerEventBus` implementation instead of relying on the default in-memory bus. ## `createLegacyMcpHandler` From 594860207dde699e2bfe12bcd476c1e52161ce25 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Mon, 27 Jul 2026 16:48:40 +0100 Subject: [PATCH 14/16] docs(agents): narrow stateless handler surface Document only callable/fetch request handling and typed notifications. Remove upstream close and event-bus internals from the Agents API reference and options. --- ...26-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx | 2 +- .../apis/handler-api.mdx | 36 +++---------------- .../guides/migrate-to-mcp-sdk-v2.mdx | 20 +++++------ 3 files changed, 16 insertions(+), 42 deletions(-) diff --git a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx index 1188e6b4c5a..33be2737e83 100644 --- a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx +++ b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx @@ -38,7 +38,7 @@ export default { The isolated `agents/mcp/server` entry keeps `McpAgent`, `WorkerTransport`, MCP client transports, and SDK v1 modules out of stateless server bundles. Its legacy compatibility lane supports ordinary tools, resources, and prompts. Session streams, replay, deletion, and pushed server-to-client requests still require a legacy sessionful server. -The Workers wrapper validates present browser Origins, supports explicit delegation to trusted Origin middleware, and exposes the upstream handler's `close`, `notify`, and `bus` controls. +The Workers wrapper validates present browser Origins, supports explicit delegation to trusted Origin middleware, and exposes request handling plus typed change notifications. ## Keep legacy support diff --git a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx index 4353b2b7448..23f7b5b3653 100644 --- a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx @@ -122,7 +122,6 @@ The following options are available: | `legacy` | `"stateless" \| "reject"` | `"stateless"` | legacy compatibility or stateless-only rejection | | `responseMode` | `"auto" \| "json" \| "sse"` | `"auto"` | stateless request response shaping | | `onerror` | `(error: Error) => void` | None | Out-of-band error reporting | -| `bus` | `ServerEventBus` | In-memory bus | Event bus for stateless subscriptions | | `maxSubscriptions` | `number` | `1,024` | Maximum concurrent listen streams | | `keepAliveMs` | `number` | `15,000` | Keepalive interval for listen streams | @@ -194,7 +193,7 @@ Set `legacy: "reject"` for a stateless-only endpoint. Use `createLegacyMcpHandle ### Return value -`createMcpHandler` returns a `StatelessMcpHandler`. It is callable and exposes request, lifecycle, notification, and event-bus controls: +`createMcpHandler` returns a `StatelessMcpHandler`. It is callable and exposes request and notification controls: ```ts interface StatelessMcpHandler { @@ -205,31 +204,18 @@ interface StatelessMcpHandler { (request: Request, env: unknown, ctx: ExecutionContext): Promise; }; - close(): Promise; - notify: { toolsChanged(): void; promptsChanged(): void; resourcesChanged(): void; resourceUpdated(uri: string): void; }; - - bus: { - publish(event: ServerEvent): void; - subscribe(listener: (event: ServerEvent) => void): () => void; - }; } type McpHandlerRequestOptions = { authInfo?: AuthInfo; parsedBody?: unknown; }; - -type ServerEvent = - | { kind: "tools_list_changed" } - | { kind: "prompts_list_changed" } - | { kind: "resources_list_changed" } - | { kind: "resource_updated"; uri: string }; ``` #### Invoke the handler @@ -257,14 +243,6 @@ const response = await handler.fetch(request, { `authInfo` is passed to the server factory and request handlers. The handler does not derive it from request headers or verify access tokens. `parsedBody` avoids reparsing a JSON body that upstream middleware already consumed. -#### Close the handler - -```ts -await handler.close(); -``` - -`close()` rejects new requests, closes active modern exchanges, and closes active work in the stateless legacy compatibility handler. - #### Publish list and resource changes The `notify` methods publish typed change events to matching open `subscriptions/listen` streams: @@ -278,15 +256,11 @@ The `notify` methods publish typed change events to matching open `subscriptions Calling a notifier when no matching subscription is open is a no-op. -The lower-level `bus` carries the same events. `publish(event)` synchronously publishes to registered listeners. `subscribe(listener)` registers a listener and returns an idempotent unsubscribe function. Prefer the typed `notify` methods when publishing standard MCP changes. - -By default, each handler owns an in-process event bus. You can provide a `ServerEventBus` through the `bus` option when events must cross processes or isolates. - -#### Keep one handler for subscription controls +#### Keep one handler for notifications -The controls belong to the handler instance. Constructing a new handler inside every Worker `fetch()` call is suitable for ordinary tools, prompts, resources, and MRTR elicitation. It creates a new in-memory event bus for every request, however, so later requests cannot notify a `subscriptions/listen` stream owned by an earlier handler. +Notification routing belongs to the handler instance. Constructing a new handler inside every Worker `fetch()` call is suitable for ordinary tools, prompts, resources, and MRTR elicitation. It cannot notify a `subscriptions/listen` stream owned by an earlier handler instance. -Create the handler once at module scope when using `notify`, `bus`, `close`, or in-process subscriptions, then invoke it from the Worker object export: +Create the handler once at module scope when using `notify` or `subscriptions/listen`, then invoke it from the Worker object export: ```ts const handler = createMcpHandler(createServer); @@ -298,7 +272,7 @@ export default { } satisfies ExportedHandler; ``` -For events spanning multiple Worker isolates, supply a shared `ServerEventBus` implementation instead of relying on the default in-memory bus. +Notifications are isolate-local. A notification published in one Worker isolate does not reach a subscription stream running in another isolate. ## `createLegacyMcpHandler` diff --git a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx index 5fa2c3f835c..3019bc543dc 100644 --- a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx @@ -171,16 +171,16 @@ The Agents wrapper adds `route`, `corsOptions`, `allowedHostnames`, `allowedOrig Common options include: -| Option | Behavior | -| ---------------------------------------- | ----------------------------------------------------------------------------------- | -| `route` | Sets the exact request path. The default is `/mcp`. | -| `legacy` | Uses legacy compatibility by default. Set `"reject"` for a stateless-only endpoint. | -| `responseMode` | Selects automatic, JSON, or SSE response handling. | -| `allowedHostnames` | Restricts Host headers to specific hostnames. | -| `allowedOriginHostnames` | Restricts browser Origins, or accepts `"*"` when trusted middleware validates them. | -| `corsOptions` | Controls CORS response headers. Set `false` to remove them. | -| `onerror` | Reports handler errors without changing the response. | -| `bus`, `maxSubscriptions`, `keepAliveMs` | Configure `subscriptions/listen` delivery. | +| Option | Behavior | +| --------------------------------- | ----------------------------------------------------------------------------------- | +| `route` | Sets the exact request path. The default is `/mcp`. | +| `legacy` | Uses legacy compatibility by default. Set `"reject"` for a stateless-only endpoint. | +| `responseMode` | Selects automatic, JSON, or SSE response handling. | +| `allowedHostnames` | Restricts Host headers to specific hostnames. | +| `allowedOriginHostnames` | Restricts browser Origins, or accepts `"*"` when trusted middleware validates them. | +| `corsOptions` | Controls CORS response headers. Set `false` to remove them. | +| `onerror` | Reports handler errors without changing the response. | +| `maxSubscriptions`, `keepAliveMs` | Configure `subscriptions/listen` delivery. | The stateless handler rejects these SDK v1 options: From 0fdfa260a8c188818b458d28dd58fe7246aeafb9 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Mon, 27 Jul 2026 16:54:41 +0100 Subject: [PATCH 15/16] docs(agents): align handler fetch with lower-level SDK Document Worker dispatch on the callable and reserve fetch(request, options?) for lower-level request integration. Remove the redundant three-argument fetch overload. --- .../model-context-protocol/apis/handler-api.mdx | 12 ++++++------ .../guides/migrate-to-mcp-sdk-v2.mdx | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx index 23f7b5b3653..0d345f1fdc3 100644 --- a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx @@ -199,10 +199,10 @@ Set `legacy: "reject"` for a stateless-only endpoint. Use `createLegacyMcpHandle interface StatelessMcpHandler { (request: Request, env: unknown, ctx: ExecutionContext): Promise; - fetch: { - (request: Request, options?: McpHandlerRequestOptions): Promise; - (request: Request, env: unknown, ctx: ExecutionContext): Promise; - }; + fetch( + request: Request, + options?: McpHandlerRequestOptions, + ): Promise; notify: { toolsChanged(): void; @@ -230,9 +230,9 @@ export default { } satisfies ExportedHandler; ``` -The three-argument `handler.fetch(request, env, ctx)` overload is equivalent. Do not export the callable directly as a Worker's default export. Wrangler treats function default exports as `WorkerEntrypoint` classes. +Do not export the callable directly as a Worker's default export. Wrangler treats function default exports as `WorkerEntrypoint` classes. -Use the request-options overload when another framework or authentication layer has already parsed or validated request data: +Use `fetch()` when another framework or authentication layer has already parsed or validated request data: ```ts const response = await handler.fetch(request, { diff --git a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx index 3019bc543dc..e721809989a 100644 --- a/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2.mdx @@ -105,7 +105,7 @@ The stateless `createMcpHandler` accepts a factory. The factory returns `McpServ 3. Move server construction and registration into a factory. -4. Keep the Worker object default export. Inside its `fetch()` method, pass the factory to `createMcpHandler` and invoke the returned callable as before. Calling its `.fetch()` method is equivalent. +4. Keep the Worker object default export. Inside its `fetch()` method, pass the factory to `createMcpHandler` and invoke the returned callable as before. Use the handler's `fetch(request, options?)` method only for lower-level request integration. 5. Do not default-export the callable returned by the Agents handler. Wrangler interprets function default exports as `WorkerEntrypoint` classes. From a36380d88676788ec7ed71f69715d9622b5896d5 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Mon, 27 Jul 2026 17:57:04 +0100 Subject: [PATCH 16/16] docs(agents): lead MCP migration with stateless path Present MCP 2026-07-28 client and server improvements first, explain one-handler legacy stateless compatibility, and reserve explicit legacy APIs for temporary sessionful migration lanes. End the changelog with the complete v0.20.0 deprecation inventory. --- ...26-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx | 71 +++++++++----- .../model-context-protocol/apis/agent-api.mdx | 4 +- .../apis/handler-api.mdx | 12 +-- .../guides/migrate-to-mcp-sdk-v2.mdx | 92 +++++++------------ .../guides/remote-mcp-server.mdx | 14 +-- .../protocol/authorization.mdx | 4 +- .../model-context-protocol/protocol/tools.mdx | 4 +- .../protocol/transport.mdx | 12 ++- 8 files changed, 106 insertions(+), 107 deletions(-) diff --git a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx index 33be2737e83..a545550616e 100644 --- a/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx +++ b/src/content/changelog/agents/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2.mdx @@ -1,6 +1,6 @@ --- title: "Agents SDK adds MCP Specification 2026-07-28 support" -description: "Agents SDK v0.20.0 adds stateless-by-default MCP 2026-07-28 clients and servers while retaining explicit legacy support." +description: "Agents SDK v0.20.0 adds client and server support for MCP 2026-07-28, including stateless Workers and compatibility with legacy MCP servers." products: - agents - workers @@ -9,9 +9,15 @@ date: 2026-07-27 import { PackageManagers, TypeScriptExample } from "~/components"; -With support for the [MCP 2026-07-28 release candidate](https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/), Agents SDK v0.20.0 lets you run MCP tools, prompts, resources, and elicitation statelessly in a Worker, so MCP transport no longer needs a Durable Object. You can still use Durable Objects or other storage for application state. +Agents SDK v0.20.0 adds client and server support for the [MCP 2026-07-28 release candidate](https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/). Workers can serve tools, prompts, resources, and elicitation without an MCP transport session or Durable Object. Agents can connect to both MCP 2026-07-28 servers and existing legacy servers. -Agents clients negotiate stateless or legacy behavior automatically. Existing legacy server deployments remain supported through `createLegacyMcpHandler`, `WorkerTransport`, and `McpAgent`. +## Client support + +The MCP client manager now uses `@modelcontextprotocol/client`. For each connection, it probes for MCP 2026-07-28 support with `server/discover`. If the server does not support the stateless protocol, the client continues with the legacy `initialize` handshake on the same connection. Existing `addMcpServer` calls do not need a protocol-version setting or separate clients for each protocol generation. + +For stateless requests, elicitation uses `input_required` through multi-round-trip requests (MRTR). The legacy path uses the same form and URL handlers for pushed requests. The SDK collects input, retries the original operation, and resolves the original `callTool`, `getPrompt`, or `readResource` promise with its final result. + +OAuth callbacks now validate issuer metadata through the v2 SDK. Discovery state and issuer-bound credentials persist across browser redirects and Durable Object hibernation. ## Run stateless servers @@ -36,47 +42,62 @@ export default { -The isolated `agents/mcp/server` entry keeps `McpAgent`, `WorkerTransport`, MCP client transports, and SDK v1 modules out of stateless server bundles. Its legacy compatibility lane supports ordinary tools, resources, and prompts. Session streams, replay, deletion, and pushed server-to-client requests still require a legacy sessionful server. +The isolated `agents/mcp/server` entry keeps `McpAgent`, `WorkerTransport`, MCP client transports, and SDK v1 modules out of stateless server bundles. The Workers wrapper validates present browser Origins, supports explicit delegation to trusted Origin middleware, and exposes request handling plus typed change notifications. -## Keep legacy support +### Backward compatibility + +The same `createMcpHandler(createServer)(request, env, ctx)` route serves MCP 2026-07-28 clients and legacy clients that use stateless requests. You do not need separate routes or tool definitions for ordinary tools, prompts, and resources. + +`McpAgent` is deprecated and feature-frozen. Migrate existing `McpAgent` servers to the stateless handler at your earliest convenience. If a server depends on protocol sessions, RPC, pushed server-to-client requests, standalone streams, or replay, use the [migration guide](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) to design stateless equivalents and run both routes while clients transition. + +## Migrate existing SDK v1 servers + +Upgrade the Agents SDK: + + + +Move ordinary SDK v1 server definitions into an SDK v2 factory and serve them with `createMcpHandler`. The handler's default legacy compatibility means most stateless deployments need only one route. -Existing SDK v1 servers can rename `createMcpHandler` to `createLegacyMcpHandler` without changing their transport behavior: +If an existing `McpAgent` server still needs sessionful features, add the stateless path beside it. Use `isLegacyRequest()` to send only legacy traffic to the existing route: ```ts -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { createLegacyMcpHandler } from "agents/mcp"; +import { isLegacyRequest } from "@modelcontextprotocol/server"; +import { createMcpHandler } from "agents/mcp/server"; +import { MyMcpAgent } from "./legacy-server"; +import { createServer } from "./server"; -function createServer() { - return new McpServer({ name: "legacy", version: "1.0.0" }); -} +const stateless = createMcpHandler(createServer, { + route: "/mcp", + legacy: "reject", +}); +const legacy = MyMcpAgent.serve("/mcp"); export default { async fetch(request: Request, env: Env, ctx: ExecutionContext) { - return createLegacyMcpHandler(createServer())(request, env, ctx); + if (await isLegacyRequest(request)) { + return legacy.fetch(request, env, ctx); + } + return stateless(request, env, ctx); }, } satisfies ExportedHandler; ``` -Passing an SDK v1 server directly to `createMcpHandler` is deprecated. `McpAgent` remains available for stateful legacy deployments, but it is deprecated and feature-frozen. - -## Client negotiation and input requests +Migrate the remaining sessionful features, allow existing sessions to drain, then remove the legacy route. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for package changes, compatibility limits, and rollout steps. -The MCP client manager now uses `@modelcontextprotocol/client`. It probes stateless servers with `server/discover` and falls back to the legacy `initialize` handshake. +## Deprecations in v0.20.0 -For stateless requests, elicitation uses `input_required` through multi-round-trip requests (MRTR). The legacy path uses the same form and URL handlers for pushed requests. The SDK collects input, retries the original operation, and resolves the original `callTool`, `getPrompt`, or `readResource` promise with its final result. +This release deprecates the following Agents SDK APIs: -OAuth callbacks now validate issuer metadata through the v2 SDK. Discovery state and issuer-bound credentials persist across browser redirects and Durable Object hibernation. - -### Upgrade - -To update the Agents SDK: - - +| Deprecated API | Replacement | Status | +| ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | +| `McpAgent` | Use an SDK v2 factory with `createMcpHandler` for stateless servers. Use the migration guide to replace stateful features before removing a legacy route. | Feature-frozen. No removal version is announced. | +| `createMcpHandler(v1Server, options)` | Move the server to an SDK v2 factory and call `createMcpHandler(factory, options)`. Use `createLegacyMcpHandler` only as a temporary bridge for sessionful features. | Scheduled for removal in the next major version. | +| `MCPClientManager.callTool(params, resultSchema, options)` and the equivalent `withX402Client` overload | Use `callTool(params, options)` or `callTool(confirm, params, options)`. | Compatibility overload. No removal version is announced. | -Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for package changes, compatibility limits, and rollout steps. +The MCP 2026-07-28 draft separately deprecates Roots, Sampling, Logging, the old HTTP+SSE transport, and Dynamic Client Registration. diff --git a/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx b/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx index 3e3caa1ad81..9f140b05109 100644 --- a/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx @@ -1,7 +1,7 @@ --- pcx_content_type: reference title: McpAgent -description: Build stateful MCP servers on Cloudflare by extending the McpAgent class with persistent storage and agent capabilities. +description: Reference the deprecated, feature-frozen McpAgent class while migrating existing stateful MCP servers to stateless handlers. tags: - MCP sidebar: @@ -16,7 +16,7 @@ import { TypeScriptExample, LinkCard } from "~/components"; :::caution[Deprecated] -`McpAgent` remains available for legacy servers that use stateful features. It is deprecated and feature-frozen. A server without those dependencies can migrate directly to [`createMcpHandler`](/agents/model-context-protocol/apis/handler-api/). +`McpAgent` remains available only for existing legacy servers while they migrate. It is deprecated and feature-frozen. Migrate to [`createMcpHandler`](/agents/model-context-protocol/apis/handler-api/) at your earliest convenience. A server that depends on MCP session state, RPC, pushed server-to-client requests, standalone streams, or replay needs a staged migration. Design stateless equivalents, add a stateless route, and serve both lanes until clients migrate and existing sessions drain. diff --git a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx index 0d345f1fdc3..e4664e7a455 100644 --- a/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/handler-api.mdx @@ -19,7 +19,7 @@ The Agents SDK provides two server handler paths: | `createMcpHandler` | `agents/mcp/server` | `@modelcontextprotocol/server` | stateless with legacy compatibility by default | | `createLegacyMcpHandler` | `agents/mcp` | `@modelcontextprotocol/sdk` | legacy sessions through `WorkerTransport` | -`McpAgent` remains available for legacy servers while stateless equivalents are built. It is deprecated and feature-frozen. Refer to the [migration guide](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for staged rollout guidance. +`McpAgent` is deprecated and feature-frozen. Migrate existing `McpAgent` servers to a stateless handler. Refer to the [migration guide](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) when sessionful features require a staged rollout. ## Install dependencies @@ -189,7 +189,7 @@ This compatibility path does not provide a complete session transport: - Standalone streams, resumability, replay, and session deletion are unavailable. - Published experimental tasks are not supported through this path. -Set `legacy: "reject"` for a stateless-only endpoint. Use `createLegacyMcpHandler` or `McpAgent` when a legacy client needs protocol sessions. +Set `legacy: "reject"` for a stateless-only endpoint. During migration, route legacy clients that still require protocol sessions to a temporary `createLegacyMcpHandler` or `McpAgent` lane. ### Return value @@ -293,7 +293,7 @@ function createLegacyMcpHandler( ): LegacyMcpHandler; ``` -Use this handler for legacy sessions, transport storage, event replay, and pushed server-to-client requests. +Use this handler only as a temporary migration bridge when an existing SDK v1 endpoint still requires legacy sessions, transport storage, event replay, or pushed server-to-client requests. @@ -314,9 +314,9 @@ export default { -Passing an SDK v1 server to `createMcpHandler` still works but emits a deprecation warning. Change the call to `createLegacyMcpHandler` to keep the same behavior without the warning. +Passing an SDK v1 server to `createMcpHandler` still works but emits a deprecation warning. Move the server to an SDK v2 factory and pass the factory to `createMcpHandler`. If sessionful behavior prevents an immediate migration, use `createLegacyMcpHandler` only on the temporary legacy lane. -`experimental_createMcpHandler` is also deprecated. Replace it with `createLegacyMcpHandler`. +`experimental_createMcpHandler` is also deprecated. Move its SDK v1 server to an SDK v2 factory. Use `createLegacyMcpHandler` only as a temporary bridge for sessionful behavior. ### `CreateLegacyMcpHandlerOptions` @@ -399,7 +399,7 @@ Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-t -For an explicit legacy server: +For a temporary legacy lane: @@ -48,9 +49,9 @@ For an Agent that connects to MCP servers: Follow peer dependency instructions from your package manager. The exact v2 pin will change with later Agents releases while the MCP SDK remains in beta. -## Keep existing legacy behavior +## Decide whether a temporary legacy lane is required -Use this path when your server depends on any of these features: +Do not keep SDK v1 only because the server currently imports it. Move directly to an SDK v2 factory unless the endpoint depends on one of these sessionful features: - Protocol sessions or a supplied `WorkerTransport` - Transport storage or event replay @@ -58,40 +59,9 @@ Use this path when your server depends on any of these features: - Pushed elicitation, sampling, or roots requests - Session deletion with HTTP `DELETE` - - -1. Keep importing `McpServer` from `@modelcontextprotocol/sdk`. - -2. Replace `createMcpHandler` with `createLegacyMcpHandler`. +If the endpoint uses one of these features, deploy the stateless route first. Keep the SDK v1 route only while you replace the sessionful dependency. Route requests with `isLegacyRequest()` as shown in [Run stateless and legacy lanes together](#run-stateless-and-legacy-lanes-together). -3. Keep the existing `WorkerTransport` options. - -4. Test initialization, tool calls, reconnects, session deletion, OAuth, and server-to-client requests. - - - - - -```ts -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { createLegacyMcpHandler } from "agents/mcp"; - -function createServer() { - return new McpServer({ name: "legacy-server", version: "1.0.0" }); -} - -export default { - async fetch(request: Request, env: Env, ctx: ExecutionContext) { - return createLegacyMcpHandler(createServer())(request, env, ctx); - }, -} satisfies ExportedHandler; -``` - - - -Create a new SDK v1 server for each request unless you provide a persistent transport that is already connected to that server. Reconnecting one server instance to several transports is invalid. - -Refer to the [legacy elicitation example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) for a sessionful server with Durable Object state and SSE replay. +For an SDK v1 endpoint that does not use `McpAgent`, use `createLegacyMcpHandler` only on that temporary legacy branch. Remove it after clients migrate and existing sessions drain. ## Move a stateless server to SDK v2 @@ -239,11 +209,11 @@ The fallback has these limits: - Standalone streams, event replay, and session deletion are unavailable. - Published experimental tasks are not supported through this fallback. -Use `createLegacyMcpHandler` or `McpAgent` when a legacy client needs those features. +While migrating these features, route affected legacy clients to a temporary `createLegacyMcpHandler` or `McpAgent` lane. ## Migrate an `McpAgent` server -`McpAgent` remains an SDK v1 server. Do not change its server import to `@modelcontextprotocol/server`. +During migration, `McpAgent` remains an SDK v1 server. Do not change the server import inside the legacy route to `@modelcontextprotocol/server`. ### Migrate directly without legacy stateful features @@ -387,31 +357,33 @@ The following integrations retain SDK v1 server output in this release: - The server-side `withX402` helper - Existing OpenAI Apps examples that import an SDK v1 `McpServer` -Serve these results with `createLegacyMcpHandler`. The Code Mode MCP connector and `withX402Client` accept either client generation. +Until these integrations produce SDK v2 servers, isolate their output behind a temporary `createLegacyMcpHandler` route. The Code Mode MCP connector and `withX402Client` accept either client generation. ## Plan the rollout -1. Classify each endpoint as stateless, explicit legacy, or `McpAgent`. +1. Classify each endpoint by its sessionful dependencies. 2. Pin the MCP SDK versions required by the Agents release. -3. Rename SDK v1 handler calls before changing their behavior. +3. Move each server definition that can be stateless to an SDK v2 factory. + +4. Add stateless handlers beside endpoints that still need a temporary legacy lane. -4. Add stateless handlers beside existing sessionful routes. +5. Route stateless and legacy requests with `isLegacyRequest()`. -5. Test stateless and legacy clients independently. +6. Test stateless and legacy clients independently. -6. Test required HTTP headers through every proxy and gateway. +7. Test required HTTP headers through every proxy and gateway. -7. Test OAuth after a clean login and after Durable Object hibernation. +8. Test OAuth after a clean login and after Durable Object hibernation. -8. Test cancellation, multiple input rounds, and transport loss. +9. Test cancellation, multiple input rounds, and transport loss. -9. Verify valid Origins and reject invalid Origins with `403`. +10. Verify valid Origins and reject invalid Origins with `403`. -10. Remove legacy routes only after existing sessions drain. +11. Remove legacy routes only after existing sessions drain. diff --git a/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx b/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx index 5b95c594460..f18148b2611 100644 --- a/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/remote-mcp-server.mdx @@ -21,19 +21,19 @@ This guide shows how to deploy a remote MCP server on Cloudflare using [Streamab The Agents SDK provides multiple ways to create MCP servers. Choose the approach that fits your use case: -| Approach | Stateful? | Protocol path | Best for | -| ----------------------------------------------------------------------------------------------------- | -------------------- | ----------------------------------- | --------------------------------------- | -| [`createMcpHandler()`](/agents/model-context-protocol/apis/handler-api/) | No | stateless with legacy compatibility | New stateless tools | -| [`createLegacyMcpHandler()`](/agents/model-context-protocol/apis/handler-api/#createlegacymcphandler) | Optional | legacy | Existing `WorkerTransport` servers | -| [`McpAgent`](/agents/model-context-protocol/apis/agent-api/) | Yes | legacy | Existing Durable Object and RPC servers | -| Raw SDK transport | Depends on transport | Depends on SDK package | Custom transport ownership | +| Approach | Stateful? | Protocol path | Best for | +| ----------------------------------------------------------------------------------------------------- | -------------------- | ----------------------------------- | ------------------------------------------- | +| [`createMcpHandler()`](/agents/model-context-protocol/apis/handler-api/) | No | stateless with legacy compatibility | New stateless tools | +| [`createLegacyMcpHandler()`](/agents/model-context-protocol/apis/handler-api/#createlegacymcphandler) | Optional | legacy | Temporary existing `WorkerTransport` routes | +| [`McpAgent`](/agents/model-context-protocol/apis/agent-api/) | Yes | legacy | Deprecated Durable Object and RPC servers | +| Raw SDK transport | Depends on transport | Depends on SDK package | Custom transport ownership | Use `createMcpHandler` for a new stateless server. An existing `McpAgent` without legacy stateful dependencies can migrate directly. If it uses MCP session state, RPC, pushed requests, streams, or replay, plan the stateless equivalents and serve stateless and legacy lanes during the transition. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the staged rollout. ## Deploy your first MCP server :::note[Template protocol path] -The quick-deploy templates in this section currently use the legacy `McpAgent` path. You can still use it for sessionful deployments. For a new stateless server, start with the [`mcp-worker` example](https://github.com/cloudflare/agents/tree/main/examples/mcp-worker). +The quick-deploy templates in this section still use the deprecated `McpAgent` path. Do not use that path for a new server. Start with the [`mcp-worker` example](https://github.com/cloudflare/agents/tree/main/examples/mcp-worker). If an existing template deployment needs sessionful behavior, add a stateless route and follow the [migration guide](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/). ::: You can start by deploying a [public MCP server](https://github.com/cloudflare/ai/tree/main/demos/remote-mcp-authless) without authentication, then add user authentication and scoped authorization later. If you already know your server will require authentication, you can skip ahead to the [next section](/agents/model-context-protocol/guides/remote-mcp-server/#add-authentication). diff --git a/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx b/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx index 2f1e2d472a9..34023d9cda3 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/authorization.mdx @@ -178,9 +178,9 @@ Remember — [authentication is different from authorization](https://www.cloud When a user authenticates through the OAuth Provider, their identity information is available inside your tools. How you access it depends on whether you use `McpAgent` or `createMcpHandler`. -### With McpAgent +### With `McpAgent` during migration -The third type parameter on `McpAgent` defines the shape of the authentication context. Access it via `this.props` inside `init()` and tool handlers. +This pattern applies only to existing deprecated `McpAgent` routes. The third type parameter defines the authentication context shape. Access it through `this.props` inside `init()` and tool handlers. ```ts import { McpAgent } from "agents/mcp"; diff --git a/src/content/docs/agents/model-context-protocol/protocol/tools.mdx b/src/content/docs/agents/model-context-protocol/protocol/tools.mdx index aeabe6a9468..3d6942b7a97 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/tools.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/tools.mdx @@ -14,7 +14,7 @@ import { TypeScriptExample, LinkCard } from "~/components"; MCP tools are functions that an [MCP server](/agents/model-context-protocol/) exposes for clients to call. An LLM can invoke a tool to look up data, run a calculation, or call an API. The MCP server executes the tool and returns its result. -Use `@modelcontextprotocol/server` for a stateless `createMcpHandler` server. Existing legacy `McpAgent` servers must keep using `@modelcontextprotocol/sdk`. +Use `@modelcontextprotocol/server` for a stateless `createMcpHandler` server. `McpAgent` is deprecated and feature-frozen. Existing `McpAgent` routes must keep using `@modelcontextprotocol/sdk` only while they migrate. :::note[Experimental WebMCP adapter] @@ -174,7 +174,7 @@ export default { ## Using tools with `McpAgent` -For legacy stateful MCP servers, define tools in the `init()` method of an [`McpAgent`](/agents/model-context-protocol/apis/agent-api/). Tools have access to the agent instance via `this`, which means they can read and write state. +This section applies only to existing legacy routes during migration. Define their tools in the `init()` method of an [`McpAgent`](/agents/model-context-protocol/apis/agent-api/). Tools have access to the agent instance through `this`, so they can read and write state. diff --git a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx index 1301ce211b9..05ea1fc93a0 100644 --- a/src/content/docs/agents/model-context-protocol/protocol/transport.mdx +++ b/src/content/docs/agents/model-context-protocol/protocol/transport.mdx @@ -18,7 +18,7 @@ The Model Context Protocol (MCP) specification defines two standard [transport m 2. **Streamable HTTP** — The standard transport method for remote MCP connections, [introduced](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) in March 2025. It uses a single HTTP endpoint for bidirectional messaging. :::note -Server-Sent Events (SSE) was previously used for remote MCP connections but has been deprecated in favor of Streamable HTTP. If you need SSE support for legacy clients, use the [`McpAgent`](/agents/model-context-protocol/apis/agent-api/) class. +Server-Sent Events (SSE) was previously used for remote MCP connections but has been deprecated in favor of Streamable HTTP. Existing `McpAgent` deployments can retain SSE temporarily while they migrate, but new servers should use the stateless Streamable HTTP handler. ::: MCP servers built with the [Agents SDK](/agents) use [`createMcpHandler`](/agents/model-context-protocol/apis/handler-api/) to handle Streamable HTTP transport. @@ -95,7 +95,7 @@ export default new OAuthProvider({ MCP has no protocol-level session on the stateless path. Applications can store durable business data behind a separate storage boundary. -Servers that require legacy sessions can use `createLegacyMcpHandler` with `WorkerTransport`, or keep `McpAgent` while their stateless equivalents are built. These APIs support transport state, event replay, pushed elicitation, sampling, and roots requests. `McpAgent` is deprecated and feature-frozen. +While migrating legacy sessions, existing servers can keep a temporary `createLegacyMcpHandler` with `WorkerTransport` or `McpAgent` route beside the new stateless route. These APIs support transport state, event replay, pushed elicitation, sampling, and roots requests. `McpAgent` is deprecated and feature-frozen. Add the stateless route before moving clients, and keep both lanes until existing sessions drain. Refer to [Migrate to MCP SDK v2](/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) for the staged migration. Refer to [`McpAgent`: Stream resumability](/agents/model-context-protocol/apis/agent-api/#stream-resumability) for existing stream behavior. @@ -109,7 +109,13 @@ The **RPC transport** is designed for internal applications where your MCP serve RPC transport does not support authentication. Use Streamable HTTP for external connections that require OAuth. -### Connecting an Agent to an McpAgent via RPC +### Connect an Agent to an existing `McpAgent` through RPC + +:::caution[Deprecated server path] + +This section applies only to existing `McpAgent` deployments during migration. Do not create a new `McpAgent` server. Use a stateless `createMcpHandler` server instead. + +::: #### 1. Define your MCP server