Skip to content

Commit b09c50e

Browse files
Sync docs for PR #752: Upgrade MCP SDK to v1.25.1
This sync updates the documentation to reflect the following changes from cloudflare/agents PR #752: ## API Changes ### WorkerTransportOptions (mcp-handler-api.mdx) - Added `onsessionclosed` callback that fires when a session is closed via DELETE request - Added `eventStore` option for SSE resumability support, enabling clients to reconnect and resume using Last-Event-ID header - Added `retryInterval` option to control client reconnection timing for polling behavior - Added `closeSSEStream()` method to WorkerTransport class for implementing polling behavior during long-running operations - Updated `sessionIdGenerator` description to clarify it can return undefined for stateless mode ### MCPTransportOptions (mcp-client-api.mdx) - Added `connectionTimeoutMs` option (default: 15000ms) to prevent infinite hangs when connecting to MCP servers. Particularly useful when proxies strip SSE newline terminators. ### New Example (transport.mdx) - Added documentation for the new `mcp-server` example showing how to use `WebStandardStreamableHTTPServerTransport` from the MCP SDK directly without the agents package. This is the simplest way to create stateless MCP servers on Cloudflare Workers. Related PR: cloudflare/agents#752 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
1 parent 9349794 commit b09c50e

File tree

3 files changed

+174
-2
lines changed

3 files changed

+174
-2
lines changed

src/content/docs/agents/model-context-protocol/mcp-client-api.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ async addMcpServer(
6363
transport?: {
6464
headers?: HeadersInit;
6565
type?: "sse" | "streamable-http" | "auto";
66+
connectionTimeoutMs?: number;
6667
};
6768
}
6869
): Promise<
@@ -82,6 +83,7 @@ async addMcpServer(
8283
- **`transport`** — Transport layer configuration:
8384
- **`headers`** — Custom HTTP headers for authentication
8485
- **`type`** — Transport type: `"sse"`, `"streamable-http"`, or `"auto"` (tries streamable-http first, falls back to sse)
86+
- **`connectionTimeoutMs`** — Connection timeout in milliseconds. Default: 15000 (15 seconds). If the connection does not complete within this time, it will fail. Timeouts are often caused by proxies that strip SSE newline terminators, causing the parser to hang.
8587

8688
#### Returns
8789

src/content/docs/agents/model-context-protocol/mcp-handler-api.mdx

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,11 @@ interface CreateMcpHandlerOptions extends WorkerTransportOptions {
6161
sessionIdGenerator?: () => string;
6262
enableJsonResponse?: boolean;
6363
onsessioninitialized?: (sessionId: string) => void;
64+
onsessionclosed?: (sessionId: string) => void;
6465
corsOptions?: CORSOptions;
6566
storage?: MCPStorageApi;
67+
eventStore?: EventStore;
68+
retryInterval?: number;
6669
}
6770
```
6871

@@ -259,6 +262,7 @@ class WorkerTransport implements Transport {
259262
): Promise<void>;
260263
async start(): Promise<void>;
261264
async close(): Promise<void>;
265+
closeSSEStream(requestId: RequestId): void;
262266
}
263267
```
264268

@@ -269,6 +273,7 @@ interface WorkerTransportOptions {
269273
/**
270274
* Function that generates a unique session ID.
271275
* Called when a new session is initialized.
276+
* Return undefined to disable session management (stateless mode).
272277
*/
273278
sessionIdGenerator?: () => string;
274279

@@ -285,6 +290,12 @@ interface WorkerTransportOptions {
285290
*/
286291
onsessioninitialized?: (sessionId: string) => void;
287292

293+
/**
294+
* Callback invoked when a session is closed via DELETE request.
295+
* Receives the session ID that was closed.
296+
*/
297+
onsessionclosed?: (sessionId: string) => void;
298+
288299
/**
289300
* CORS configuration for cross-origin requests.
290301
* Configures Access-Control-* headers.
@@ -297,6 +308,18 @@ interface WorkerTransportOptions {
297308
* so it survives hibernation/restart.
298309
*/
299310
storage?: MCPStorageApi;
311+
312+
/**
313+
* Event store for resumability support.
314+
* If provided, enables clients to reconnect and resume messages using Last-Event-ID.
315+
*/
316+
eventStore?: EventStore;
317+
318+
/**
319+
* Retry interval in milliseconds to suggest to clients in SSE retry field.
320+
* Controls client reconnection timing for polling behavior.
321+
*/
322+
retryInterval?: number;
300323
}
301324
```
302325

@@ -344,6 +367,23 @@ const transport = new WorkerTransport({
344367

345368
</TypeScriptExample>
346369

370+
#### onsessionclosed
371+
372+
A callback that fires when a session is explicitly closed via DELETE request. This allows you to perform cleanup operations when a client terminates the session.
373+
374+
<TypeScriptExample>
375+
376+
```ts
377+
const transport = new WorkerTransport({
378+
onsessionclosed: (sessionId) => {
379+
console.log(`MCP session closed: ${sessionId}`);
380+
// Perform cleanup operations
381+
},
382+
});
383+
```
384+
385+
</TypeScriptExample>
386+
347387
#### corsOptions
348388

349389
Configure CORS headers for cross-origin requests.
@@ -409,6 +449,88 @@ const transport = new WorkerTransport({
409449

410450
</TypeScriptExample>
411451

452+
#### eventStore
453+
454+
An event store for SSE resumability support. When provided, clients can reconnect and resume from where they left off using the `Last-Event-ID` header. This is useful for handling temporary network interruptions without losing messages.
455+
456+
```ts
457+
interface EventStore {
458+
storeEvent(streamId: string, message: JSONRPCMessage): Promise<EventId>;
459+
replayEventsAfter(
460+
lastEventId: EventId,
461+
handler: { send: (eventId: EventId, message: JSONRPCMessage) => Promise<void> }
462+
): Promise<string>;
463+
getStreamIdForEventId?(eventId: EventId): Promise<string | undefined>;
464+
}
465+
```
466+
467+
<TypeScriptExample>
468+
469+
```ts
470+
// Example: In-memory event store (use persistent storage in production)
471+
class InMemoryEventStore implements EventStore {
472+
private events: Map<string, Array<{ id: string; message: JSONRPCMessage }>> = new Map();
473+
private eventCounter = 0;
474+
475+
async storeEvent(streamId: string, message: JSONRPCMessage): Promise<string> {
476+
const eventId = `event-${++this.eventCounter}`;
477+
const streamEvents = this.events.get(streamId) || [];
478+
streamEvents.push({ id: eventId, message });
479+
this.events.set(streamId, streamEvents);
480+
return eventId;
481+
}
482+
483+
async replayEventsAfter(
484+
lastEventId: string,
485+
handler: { send: (eventId: string, message: JSONRPCMessage) => Promise<void> }
486+
): Promise<string> {
487+
// Implementation details omitted for brevity
488+
return streamId;
489+
}
490+
}
491+
492+
const transport = new WorkerTransport({
493+
eventStore: new InMemoryEventStore(),
494+
});
495+
```
496+
497+
</TypeScriptExample>
498+
499+
#### retryInterval
500+
501+
Suggests to MCP clients how long to wait (in milliseconds) before reconnecting after a connection is closed. This is sent in the SSE retry field and enables polling behavior for long-running operations.
502+
503+
<TypeScriptExample>
504+
505+
```ts
506+
const transport = new WorkerTransport({
507+
retryInterval: 5000, // Suggest clients reconnect after 5 seconds
508+
});
509+
```
510+
511+
</TypeScriptExample>
512+
513+
#### closeSSEStream
514+
515+
Close an SSE stream for a specific request, triggering client reconnection. Use this to implement polling behavior during long-running operations - the client will reconnect after the retry interval specified in the priming event.
516+
517+
This method is useful when you want to control when clients poll for updates rather than keeping connections open continuously.
518+
519+
<TypeScriptExample>
520+
521+
```ts
522+
import { WorkerTransport } from "agents/mcp";
523+
524+
const transport = new WorkerTransport({
525+
retryInterval: 3000, // Client will reconnect after 3 seconds
526+
});
527+
528+
// Close the stream to trigger reconnection and polling
529+
transport.closeSSEStream(requestId);
530+
```
531+
532+
</TypeScriptExample>
533+
412534
## Authentication Context
413535

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

src/content/docs/agents/model-context-protocol/transport.mdx

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,57 @@ You can use the "Deploy to Cloudflare" button to create a remote MCP server.
3030

3131
[![Deploy to Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/agents/tree/main/examples/mcp-worker)
3232

33-
#### Remote MCP server (without authentication)
33+
#### Remote MCP server (using MCP SDK directly)
3434

35-
Create an MCP server using `createMcpHandler`. View the [complete example on GitHub](https://github.com/cloudflare/agents/tree/main/examples/mcp-worker).
35+
The simplest way to create a stateless MCP server on Cloudflare Workers is to use `WebStandardStreamableHTTPServerTransport` from the `@modelcontextprotocol/sdk` directly, without the agents package. View the [complete example on GitHub](https://github.com/cloudflare/agents/tree/main/examples/mcp-server).
36+
37+
<TypeScriptExample>
38+
39+
```ts
40+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
41+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
42+
import { z } from "zod";
43+
44+
const server = new McpServer({
45+
name: "Hello MCP Server",
46+
version: "1.0.0",
47+
});
48+
49+
server.registerTool(
50+
"hello",
51+
{
52+
description: "Returns a greeting message",
53+
inputSchema: { name: z.string().optional() },
54+
},
55+
async ({ name }) => {
56+
return {
57+
content: [{ text: `Hello, ${name ?? "World"}!`, type: "text" }],
58+
};
59+
},
60+
);
61+
62+
const transport = new WebStandardStreamableHTTPServerTransport();
63+
server.connect(transport);
64+
65+
export default {
66+
fetch: async (request: Request) => {
67+
if (request.method === "OPTIONS") {
68+
return new Response(null, { headers: corsHeaders });
69+
}
70+
return await transport.handleRequest(request);
71+
},
72+
};
73+
```
74+
75+
</TypeScriptExample>
76+
77+
:::note
78+
This approach is ideal for stateless MCP servers. If you need session state, authentication, or integration with the Agents SDK, use `createMcpHandler` instead.
79+
:::
80+
81+
#### Remote MCP server (using createMcpHandler)
82+
83+
Create an MCP server using `createMcpHandler`. This approach provides integration with the Agents SDK and supports both stateless and stateful servers. View the [complete example on GitHub](https://github.com/cloudflare/agents/tree/main/examples/mcp-worker).
3684

3785
<TypeScriptExample>
3886

0 commit comments

Comments
 (0)