Skip to content
Open
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
107 changes: 107 additions & 0 deletions src/content/changelog/agents/2026-01-12-mcp-sdk-upgrade.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
title: MCP SDK upgraded to v1.25.2 with enhanced error handling and new transport features
description: MCP SDK upgrade brings improved error handling for transport detection, new WorkerTransport options, and a simpler way to build MCP servers
products:
- agents
- workers
date: 2026-01-12
---

The Agents SDK has been updated with [@modelcontextprotocol/sdk v1.25.2](https://github.com/modelcontextprotocol/typescript-sdk), bringing several improvements to MCP server development and transport handling.

### Enhanced error handling

The MCP SDK now uses `StreamableHTTPError` with structured error codes instead of message-only errors. The Agents SDK has been updated to handle these new error structures correctly:

- **404/405 errors**: Properly detected for transport fallback
- **401 errors**: Correctly identified for authentication failures
- **Improved detection**: Error handling now checks both error codes and messages for better reliability

This change improves the auto-transport selection feature, allowing the SDK to more reliably detect when a transport is not supported and fall back to alternatives.

### New WorkerTransport options

The `WorkerTransport` class now supports additional options for advanced use cases:

#### Session lifecycle callback

Track when sessions are closed:

```ts
const transport = new WorkerTransport({
onsessionclosed: (sessionId) => {
console.log(`Session ${sessionId} was closed`);
// Clean up session-specific resources
},
});
```

#### Event store for resumability

Enable clients to reconnect and resume from their last received message:

```ts
const transport = new WorkerTransport({
eventStore: {
storeEvent: async (streamId, message) => {
const eventId = crypto.randomUUID();
await env.KV.put(`event:${eventId}`, JSON.stringify({ streamId, message }));
return eventId;
},
replayEventsAfter: async (lastEventId, { send }) => {
// Retrieve and replay events after lastEventId
const events = await getEventsAfter(lastEventId);
for (const event of events) {
await send(event.id, event.message);
}
return events[0]?.streamId ?? "_GET_stream";
},
},
});
```

#### Polling behavior support

Control client reconnection timing and implement polling patterns:

```ts
const transport = new WorkerTransport({
retryInterval: 5000, // Client reconnects after 5 seconds
});

// Close stream to trigger reconnection
transport.closeSSEStream(requestId);
```

The new `closeSSEStream` method allows servers to close individual SSE streams, triggering client reconnection after the specified retry interval. This is useful for implementing polling patterns during long-running operations.

### Simplified MCP server creation

A new example demonstrates using `WebStandardStreamableHTTPServerTransport` from the MCP SDK directly, without the `agents` package. This provides the simplest way to create a stateless MCP server on Cloudflare Workers:

```ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";

const server = new McpServer({
name: "Hello MCP Server",
version: "1.0.0",
});

const transport = new WebStandardStreamableHTTPServerTransport();
server.connect(transport);

export default {
fetch: async (request: Request) => {
return transport.handleRequest(request);
},
};
```

This approach requires no additional dependencies beyond the MCP SDK and is ideal when you do not need state management or authentication. View the [complete example](https://github.com/cloudflare/agents/tree/main/examples/mcp-server) on GitHub.

For more advanced use cases with state management and authentication, continue using [`createMcpHandler`](/agents/model-context-protocol/mcp-handler-api/) from the Agents SDK.

### API documentation updates

The [MCP Handler API reference](/agents/model-context-protocol/mcp-handler-api/) has been updated with documentation for all new WorkerTransport options and methods. The [Transport guide](/agents/model-context-protocol/transport/) now includes both approaches for building MCP servers.
146 changes: 146 additions & 0 deletions src/content/docs/agents/model-context-protocol/mcp-handler-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,11 @@ interface CreateMcpHandlerOptions extends WorkerTransportOptions {
sessionIdGenerator?: () => string;
enableJsonResponse?: boolean;
onsessioninitialized?: (sessionId: string) => void;
onsessionclosed?: (sessionId: string) => void;
corsOptions?: CORSOptions;
storage?: MCPStorageApi;
eventStore?: EventStore;
retryInterval?: number;
}
```

Expand Down Expand Up @@ -259,6 +262,7 @@ class WorkerTransport implements Transport {
): Promise<void>;
async start(): Promise<void>;
async close(): Promise<void>;
closeSSEStream(requestId: RequestId): void;
}
```

Expand All @@ -285,6 +289,12 @@ interface WorkerTransportOptions {
*/
onsessioninitialized?: (sessionId: string) => void;

/**
* Callback invoked when a session is closed via DELETE request.
* Receives the session ID that was closed.
*/
onsessionclosed?: (sessionId: string) => void;

/**
* CORS configuration for cross-origin requests.
* Configures Access-Control-* headers.
Expand All @@ -297,6 +307,18 @@ interface WorkerTransportOptions {
* so it survives hibernation/restart.
*/
storage?: MCPStorageApi;

/**
* Event store for resumability support.
* If provided, enables clients to reconnect and resume messages using Last-Event-ID.
*/
eventStore?: EventStore;

/**
* Retry interval in milliseconds to suggest to clients in SSE retry field.
* Controls client reconnection timing for polling behavior.
*/
retryInterval?: number;
}
```

Expand Down Expand Up @@ -344,6 +366,23 @@ const transport = new WorkerTransport({

</TypeScriptExample>

#### onsessionclosed

A callback that fires when a session is closed via a DELETE request. Use this to clean up resources or perform logging when a client explicitly terminates a session.

<TypeScriptExample>

```ts
const transport = new WorkerTransport({
onsessionclosed: (sessionId) => {
console.log(`MCP session closed: ${sessionId}`);
// Clean up any session-specific resources
},
});
```

</TypeScriptExample>

#### corsOptions

Configure CORS headers for cross-origin requests.
Expand Down Expand Up @@ -409,6 +448,113 @@ const transport = new WorkerTransport({

</TypeScriptExample>

#### eventStore

An optional event store for resumability support. When provided, the transport stores each message with a unique event ID, allowing clients to reconnect and resume from their last received message using the `Last-Event-ID` header.

```ts
interface EventStore {
/**
* Stores an event for later retrieval.
* Returns the generated event ID.
*/
storeEvent(streamId: string, message: JSONRPCMessage): Promise<string>;

/**
* Replays events after the given event ID.
* Returns the stream ID that was replayed.
*/
replayEventsAfter(
lastEventId: string,
options: {
send: (eventId: string, message: JSONRPCMessage) => Promise<void>;
},
): Promise<string>;

/**
* Optional: Get stream ID for a given event ID.
*/
getStreamIdForEventId?(eventId: string): Promise<string | undefined>;
}
```

<TypeScriptExample>

```ts
const transport = new WorkerTransport({
eventStore: {
storeEvent: async (streamId, message) => {
const eventId = crypto.randomUUID();
await env.KV.put(`event:${eventId}`, JSON.stringify({ streamId, message }));
return eventId;
},
replayEventsAfter: async (lastEventId, { send }) => {
// Retrieve and replay events after lastEventId
const events = await getEventsAfter(lastEventId);
for (const event of events) {
await send(event.id, event.message);
}
return events[0]?.streamId ?? "_GET_stream";
},
},
});
```

</TypeScriptExample>

#### retryInterval

The retry interval in milliseconds to suggest to clients in the SSE `retry` field. This controls how quickly clients reconnect when the connection is closed, enabling polling behavior during long-running operations.

<TypeScriptExample>

```ts
const transport = new WorkerTransport({
retryInterval: 5000, // Suggest clients reconnect after 5 seconds
});
```

</TypeScriptExample>

When combined with the `closeSSEStream` method, this enables implementing polling patterns where the server closes the connection and the client automatically reconnects after the specified interval.

### Methods

#### closeSSEStream

Close an SSE stream for a specific request, triggering client reconnection. Use this to implement polling behavior during long-running operations where the client will automatically reconnect after the retry interval specified in the priming event.

```ts
closeSSEStream(requestId: RequestId): void;
```

<TypeScriptExample>

```ts
// In an MCP tool handler
server.tool("longRunningTask", "Start a long-running task", {}, async () => {
// Start the task asynchronously
startBackgroundTask();

// Close the SSE stream to trigger client reconnection
// The client will reconnect based on the retryInterval
transport.closeSSEStream(currentRequestId);

return {
content: [
{
type: "text",
text: "Task started. Client will reconnect to check status.",
},
],
};
});
```

</TypeScriptExample>

This method is useful when implementing polling patterns where the server needs to perform work between client connections rather than keeping a long-lived connection open.

## Authentication Context

When using [OAuth authentication](/agents/model-context-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.
Expand Down
78 changes: 77 additions & 1 deletion src/content/docs/agents/model-context-protocol/transport.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
The Model Context Protocol (MCP) specification defines two standard [transport mechanisms](https://spec.modelcontextprotocol.io/specification/draft/basic/transports/) for communication between clients and servers:

1. **stdio** — Communication over standard in and standard out, designed for local MCP connections.
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.

Check warning on line 15 in src/content/docs/agents/model-context-protocol/transport.mdx

View workflow job for this annotation

GitHub Actions / Semgrep

semgrep.style-guide-potential-date-year

Potential year found. Documentation should strive to represent universal truth, not something time-bound. (add [skip style guide checks] to commit message to skip)

Check warning on line 15 in src/content/docs/agents/model-context-protocol/transport.mdx

View workflow job for this annotation

GitHub Actions / Semgrep

semgrep.style-guide-potential-date-month

Potential month found. Documentation should strive to represent universal truth, not something time-bound. (add [skip style guide checks] to commit message to skip)

:::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/mcp-agent-api/) class.
Expand All @@ -22,7 +22,83 @@

## Implementing remote MCP transport

Use [`createMcpHandler`](/agents/model-context-protocol/mcp-handler-api/) to create an MCP server that handles Streamable HTTP transport. This is the recommended approach for new MCP servers.
The Agents SDK provides two approaches for creating MCP servers:

1. **Using the MCP SDK directly** — Use `WebStandardStreamableHTTPServerTransport` from `@modelcontextprotocol/sdk` for the simplest zero-configuration MCP server
2. **Using `createMcpHandler`** — Use the Agents SDK helper for additional features like session management and authentication

### Using the MCP SDK directly

For the simplest MCP server, use `WebStandardStreamableHTTPServerTransport` from the `@modelcontextprotocol/sdk` package directly. This requires no additional dependencies beyond the MCP SDK and runs on Cloudflare Workers without any configuration.

View the [complete example on GitHub](https://github.com/cloudflare/agents/tree/main/examples/mcp-server).

<TypeScriptExample>

```ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";

const server = new McpServer({
name: "Hello MCP Server",
version: "1.0.0",
});

server.registerTool(
"hello",
{
description: "Returns a greeting message",
inputSchema: { name: z.string().optional() },
},
async ({ name }) => {
return {
content: [
{
text: `Hello, ${name ?? "World"}!`,
type: "text",
},
],
};
},
);

const transport = new WebStandardStreamableHTTPServerTransport();
server.connect(transport);

const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
"Access-Control-Allow-Headers":
"Content-Type, Accept, mcp-session-id, mcp-protocol-version",
"Access-Control-Expose-Headers": "mcp-session-id",
"Access-Control-Max-Age": "86400",
};

function withCors(response: Response): Response {
for (const [key, value] of Object.entries(corsHeaders)) {
response.headers.set(key, value);
}
return response;
}

export default {
fetch: async (request: Request, _env: Env, _ctx: ExecutionContext) => {
if (request.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
return withCors(await transport.handleRequest(request));
},
};
```

</TypeScriptExample>

This approach is ideal when you do not need state management or authentication.

### Using createMcpHandler

Use [`createMcpHandler`](/agents/model-context-protocol/mcp-handler-api/) to create an MCP server with additional features like session management, state persistence, and authentication support.

#### Get started quickly

Expand Down
Loading