Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
title: "Agents SDK adds MCP Specification 2026-07-28 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
date: 2026-07-27
---

import { PackageManagers, TypeScriptExample } from "~/components";

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.

## 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

`createMcpHandler` now accepts a factory that returns a server from `@modelcontextprotocol/server`. The factory creates an isolated server for each request.

<TypeScriptExample>

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

function createServer() {
return new McpServer({ name: "example", version: "1.0.0" });
}

export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer)(request, env, ctx);
},
} satisfies ExportedHandler;
```

</TypeScriptExample>

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.

### 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:

<PackageManagers pkg="agents@latest" />

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.

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:

<TypeScriptExample>

```ts
import { isLegacyRequest } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";
import { MyMcpAgent } from "./legacy-server";
import { createServer } from "./server";

const stateless = createMcpHandler(createServer, {
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 stateless(request, env, ctx);
},
} satisfies ExportedHandler<Env>;
```

</TypeScriptExample>

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.

## Deprecations in v0.20.0

This release deprecates the following Agents SDK APIs:

| 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. |

The MCP 2026-07-28 draft separately deprecates Roots, Sampling, Logging, the old HTTP+SSE transport, and Dynamic Client Registration.
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -12,7 +12,19 @@ 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 legacy MCP server backed by a Durable Object.

:::caution[Deprecated]

`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.

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.

:::

<TypeScriptExample>

Expand Down Expand Up @@ -40,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/).

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 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:

Expand Down Expand Up @@ -257,9 +269,9 @@ export class MyMCP extends McpAgent<Env, State, {}> {

</TypeScriptExample>

## 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. 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. 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.
Expand Down Expand Up @@ -371,7 +383,7 @@ switch (result.action) {
</TypeScriptExample>

:::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/).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.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.

## Overview

Expand Down Expand Up @@ -393,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. 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()`:
Register a handler for each mode your Agent supports in `onStart()`. The same handlers serve both lanes:

<TypeScriptExample>

```ts
import { Agent } from "agents";
import type { ElicitRequest, ElicitResult } from "agents/mcp";
import type { ElicitRequest, ElicitResult } from "agents/mcp/client";

class MyAgent extends Agent<Env> {
onStart() {
Expand Down Expand Up @@ -432,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. 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.

Expand Down Expand Up @@ -510,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;
Expand Down Expand Up @@ -570,9 +572,9 @@ class MyAgent extends Agent<Env> {

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 [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

Expand Down Expand Up @@ -686,7 +688,7 @@ async addMcpServer(
callbackHost?: string;
callbackPath?: string;
agentsPrefix?: string;
client?: ClientOptions;
client?: McpClientOptions;
transport?: {
headers?: HeadersInit;
type?: "sse" | "streamable-http" | "auto";
Expand All @@ -705,7 +707,7 @@ async addMcpServer(
options?: {
id?: string;
props?: Record<string, unknown>;
client?: ClientOptions;
client?: McpClientOptions;
retry?: RetryOptions;
}
): Promise<{ id: string; state: "ready" }>
Expand All @@ -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"`
Expand Down Expand Up @@ -870,17 +872,19 @@ 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?: {
form?: (
request: ElicitRequest,
serverId: string,
signal?: AbortSignal,
) => Promise<ElicitResult>;
url?: (
request: ElicitRequest,
serverId: string,
signal?: AbortSignal,
) => Promise<ElicitResult>;
}): void
```
Expand All @@ -892,14 +896,15 @@ 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`.

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.

Expand All @@ -911,7 +916,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<Env> {
onStart() {
Expand Down
Loading