agents 0.30.0 → 0.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,314 +1,55 @@
1
1
  # MCP Transports
2
2
 
3
- This guide explains the different transport options for connecting to MCP servers with the Agents SDK.
3
+ This page covers the transport your MCP server uses and the transports an Agent can use to connect to other MCP servers.
4
4
 
5
- For a primer on MCP servers, see [Creating MCP Servers](./mcp-servers.md).
5
+ ## Server: Streamable HTTP
6
6
 
7
- ## Streamable HTTP Transport (Recommended)
8
-
9
- The Agents SDK has two server paths:
10
-
11
- - Current SDK v2 servers use `createMcpHandler`, a per-request stateless Worker handler with no Durable Object or WebSocket bridge.
12
- - Retained SDK v1 servers use the deprecated, feature-frozen `McpAgent`, which provides sessionful transports through a Durable Object and internal WebSocket bridge.
13
-
14
- The current stateless handler also accepts stateless 2025-era Streamable HTTP requests by default. It does not expose standalone legacy SSE.
15
-
16
- ### Legacy `McpAgent` architecture
17
-
18
- When a client connects to your MCP server:
19
-
20
- 1. The client makes an HTTP request to your Worker with a JSON-RPC message in the body
21
- 2. Your Worker upgrades the connection to a WebSocket
22
- 3. The WebSocket connects to your `McpAgent` Durable Object which manages connection state
23
- 4. JSON-RPC messages flow bidirectionally over the WebSocket
24
- 5. Your Worker streams responses back to the client using Server-Sent Events (SSE)
25
-
26
- This is all handled automatically by the `McpAgent.serve()` method:
7
+ [`createMcpHandler`](./mcp-servers.md) serves Streamable HTTP. Each request is handled on its own, with a fresh `McpServer` from your factory, so there are no sessions, Durable Objects, or WebSockets behind the endpoint.
27
8
 
28
9
  ```typescript
29
- import { McpAgent } from "agents/mcp";
30
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
31
-
32
- export class MyMCP extends McpAgent {
33
- server = new McpServer({ name: "Demo", version: "1.0.0" });
34
-
35
- async init() {
36
- // Define your tools, resources, prompts
37
- }
38
- }
39
-
40
- // Serve with Streamable HTTP transport
41
- export default MyMCP.serve("/mcp");
42
- ```
10
+ import { createMcpHandler } from "agents/mcp/server";
43
11
 
44
- The `serve()` method returns a Worker with a `fetch` handler that:
45
-
46
- - Handles CORS preflight requests
47
- - Manages WebSocket upgrades
48
- - Routes messages to your Durable Object
49
-
50
- ### Connection from clients
51
-
52
- The Agents MCP client uses `"auto"` by default. It tries Streamable HTTP first and falls back to legacy SSE only when the endpoint reports that Streamable HTTP is unsupported:
53
-
54
- ```typescript
55
- await agent.addMcpServer("my-server", "https://your-worker.workers.dev/mcp");
12
+ const handler = createMcpHandler(createServer, { route: "/mcp" });
56
13
  ```
57
14
 
58
- ## Legacy `McpAgent` Server Auto Transport
59
-
60
- The **auto** transport serves both Streamable HTTP and legacy SSE on the same endpoint. Capable clients use Streamable HTTP automatically, while older SSE-only clients continue to work.
15
+ The same endpoint serves:
61
16
 
62
- ```typescript
63
- export default MyMCP.serve("/mcp", { transport: "auto" });
64
- ```
65
-
66
- The handler distinguishes between the two protocols based on the request shape — no configuration or content negotiation is required from clients. This is useful when migrating from SSE to Streamable HTTP without breaking existing clients.
17
+ - Stateless clients on draft MCP `2026-07-28`
18
+ - 2025-era Streamable HTTP clients, for tools, resources, and prompts (the default `legacy: "stateless"` lane)
67
19
 
68
- ## Legacy `McpAgent` SSE Transport (Deprecated)
20
+ It does not serve the standalone SSE transport. Set `legacy: "reject"` to accept only Stateless clients. See [2025-era clients](./mcp-servers.md#2025-era-clients) for what that lane does not support.
69
21
 
70
- We also support the legacy **SSE (Server-Sent Events)** transport, but it is deprecated in favor of Streamable HTTP.
22
+ ## Client: connecting to MCP servers
71
23
 
72
- If you need SSE transport for compatibility:
24
+ An Agent connects to remote MCP servers with `addMcpServer()`. The `transport.type` option picks the transport:
73
25
 
74
26
  ```typescript
75
- // Server
76
- export default MyMCP.serveSSE("/sse");
77
-
78
- // Client
79
- await agent.addMcpServer("my-server", url);
80
- ```
81
-
82
- ## RPC Transport (Experimental)
83
-
84
- The **RPC transport** is a custom transport designed for internal applications where your MCP server and agent are both running on Cloudflare. They can even run in the same Worker! It sends JSON-RPC messages directly over Cloudflare's RPC bindings without going over the public internet.
85
-
86
- ### Why use RPC transport?
87
-
88
- - **Faster**: No network overhead - direct function calls
89
- - **Simpler**: No HTTP endpoints, no connection management
90
- - **Internal only**: Perfect for agents calling MCP servers within the same Worker
91
-
92
- **Note**: RPC transport does not support authentication. Use HTTP/SSE for external connections that require OAuth.
93
-
94
- ### Connecting an Agent to an McpAgent via RPC
95
-
96
- The RPC transport uses Durable Object bindings to connect your `Agent` (MCP client) directly to your `McpAgent` (MCP server).
97
-
98
- #### Step 1: Define your MCP server
99
-
100
- Create your `McpAgent` with the tools you want to expose:
101
-
102
- ```typescript
103
- import { McpAgent } from "agents/mcp";
104
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
105
- import { z } from "zod";
106
-
107
- type State = { counter: number };
108
-
109
- export class MyMCP extends McpAgent<Env, State> {
110
- server = new McpServer({ name: "MyMCP", version: "1.0.0" });
111
- initialState: State = { counter: 0 };
112
-
113
- async init() {
114
- this.server.tool(
115
- "add",
116
- "Add to the counter",
117
- { amount: z.number() },
118
- async ({ amount }) => {
119
- this.setState({ counter: this.state.counter + amount });
120
- return {
121
- content: [
122
- {
123
- type: "text",
124
- text: `Added ${amount}, total is now ${this.state.counter}`
125
- }
126
- ]
127
- };
128
- }
129
- );
130
- }
131
- }
132
- ```
133
-
134
- #### Step 2: Connect your Agent to the MCP server
135
-
136
- In your `Agent`, call `addMcpServer()` with the Durable Object binding in `onStart()`:
137
-
138
- ```typescript
139
- import { AIChatAgent } from "agents/ai-chat-agent";
140
-
141
- export class Chat extends AIChatAgent<Env> {
142
- async onStart(): Promise<void> {
143
- // Pass the DO namespace binding directly
144
- await this.addMcpServer("my-mcp", this.env.MyMCP);
145
- }
146
-
147
- async onChatMessage(onFinish) {
148
- const allTools = this.mcp.getAITools();
149
-
150
- const result = streamText({
151
- model,
152
- tools: allTools
153
- // ...
154
- });
27
+ // Default: try Streamable HTTP, fall back to SSE
28
+ await this.addMcpServer("my-server", "https://example.com/mcp");
155
29
 
156
- return createUIMessageStreamResponse({ stream: result });
157
- }
158
- }
159
- ```
160
-
161
- RPC connections are automatically restored after Durable Object hibernation, just like HTTP connections. The binding name and props are persisted to storage so the connection can be re-established without any extra code.
162
-
163
- **Deduplication:** For RPC transport, if `addMcpServer` is called with a name that already has an active connection, the existing connection is returned instead of creating a duplicate. For HTTP transport, deduplication matches on both server name and URL (see [MCP Client API](./mcp-client.md) for details). This makes it safe to call `addMcpServer` in `onStart()` without worrying about creating multiple connections on restart.
164
-
165
- #### Step 3: Configure Durable Object bindings
166
-
167
- In your `wrangler.jsonc`, define bindings for both Durable Objects:
168
-
169
- ```jsonc
170
- {
171
- "durable_objects": {
172
- "bindings": [
173
- { "name": "Chat", "class_name": "Chat" },
174
- { "name": "MyMCP", "class_name": "MyMCP" }
175
- ]
176
- },
177
- "migrations": [
178
- {
179
- "new_sqlite_classes": ["MyMCP", "Chat"],
180
- "tag": "v1"
181
- }
182
- ]
183
- }
184
- ```
185
-
186
- #### Step 4: Set up your Worker fetch handler
187
-
188
- Route requests to your Chat agent:
189
-
190
- ```typescript
191
- import { routeAgentRequest } from "agents";
192
-
193
- export default {
194
- async fetch(request: Request, env: Env, ctx: ExecutionContext) {
195
- const url = new URL(request.url);
196
-
197
- // Optionally expose the MCP server via HTTP as well
198
- if (url.pathname.startsWith("/mcp")) {
199
- return MyMCP.serve("/mcp").fetch(request, env, ctx);
200
- }
201
-
202
- const response = await routeAgentRequest(request, env);
203
- if (response) return response;
204
-
205
- return new Response("Not found", { status: 404 });
206
- }
207
- };
208
- ```
209
-
210
- ### Passing props from client to server
211
-
212
- Since RPC transport does not have an OAuth flow, you can pass user context (like userId, role, etc.) directly as props:
213
-
214
- ```typescript
215
- await this.addMcpServer("my-mcp", this.env.MyMCP, {
216
- props: { userId: "user-123", role: "admin" }
30
+ // Force a transport
31
+ await this.addMcpServer("my-server", "https://example.com/mcp", {
32
+ transport: { type: "streamable-http" }
217
33
  });
218
- ```
219
34
 
220
- Your `McpAgent` can then access these props:
221
-
222
- ```typescript
223
- export class MyMCP extends McpAgent<
224
- Env,
225
- State,
226
- { userId?: string; role?: string }
227
- > {
228
- async init() {
229
- this.server.tool("whoami", "Get current user info", {}, async () => {
230
- const userId = this.props?.userId || "anonymous";
231
- const role = this.props?.role || "guest";
232
-
233
- return {
234
- content: [{ type: "text", text: `User ID: ${userId}, Role: ${role}` }]
235
- };
236
- });
35
+ // Send extra headers, for example an API key
36
+ await this.addMcpServer("my-server", "https://example.com/mcp", {
37
+ transport: {
38
+ type: "streamable-http",
39
+ headers: { Authorization: `Bearer ${this.env.API_KEY}` }
237
40
  }
238
- }
239
- ```
240
-
241
- The props are:
242
-
243
- - **Type-safe**: TypeScript extracts the Props type from your McpAgent generic
244
- - **Persistent**: Stored in Durable Object storage via `updateProps()`
245
- - **Available immediately**: Set before any tool calls are made
246
-
247
- This is useful for:
248
-
249
- - User authentication context
250
- - Tenant/organization IDs
251
- - Feature flags or permissions
252
- - Any per-connection configuration
253
-
254
- ### How RPC transport works under the hood
255
-
256
- When you call `addMcpServer()` with a Durable Object binding, the SDK:
257
-
258
- 1. Creates an `RPCClientTransport` that wraps the DO stub
259
- 2. Calls `handleMcpMessage()` on the `McpAgent` for each JSON-RPC message
260
- 3. The `McpAgent` routes messages through its `RPCServerTransport` to the MCP server
261
- 4. Responses flow back synchronously through the RPC call
262
-
263
- This happens entirely within your Worker's execution context using Cloudflare's RPC mechanism - no HTTP, no WebSockets, no public internet.
264
-
265
- The RPC transport fully supports:
266
-
267
- - JSON-RPC 2.0 validation (via the MCP SDK's schema)
268
- - Batch requests
269
- - Notifications (messages without `id` field)
270
- - Automatic reconnection after Durable Object hibernation (when called from `onStart()`)
271
-
272
- ### Configuring RPC Transport Server Timeout
273
-
274
- The RPC transport has a configurable timeout for waiting for tool responses. By default, the server will wait **60 seconds** for a tool handler to respond. You can customize this by overriding `getRpcTransportOptions()` in your `McpAgent`:
275
-
276
- ```typescript
277
- export class MyMCP extends McpAgent<Env, State> {
278
- server = new McpServer({ name: "MyMCP", version: "1.0.0" });
279
-
280
- protected getRpcTransportOptions() {
281
- return { timeout: 120000 }; // 2 minutes
282
- }
283
-
284
- async init() {
285
- this.server.tool(
286
- "long-running-task",
287
- "A tool that takes a while",
288
- { input: z.string() },
289
- async ({ input }) => {
290
- await longRunningOperation(input);
291
- return {
292
- content: [{ type: "text", text: "Task completed" }]
293
- };
294
- }
295
- );
296
- }
297
- }
41
+ });
298
42
  ```
299
43
 
300
- ## Choosing a transport
44
+ | `transport.type` | Use when |
45
+ | ------------------- | ------------------------------------------------- |
46
+ | `"auto"` (default) | You do not know which transport the server speaks |
47
+ | `"streamable-http"` | The server supports Streamable HTTP |
48
+ | `"sse"` | The server only supports the older SSE transport |
301
49
 
302
- | Transport | Use when | Pros | Cons |
303
- | --------------------------------- | --------------------------------------------------- | ------------------------------------------ | -------------------------------------- |
304
- | **Streamable HTTP** | New servers and external MCP connections | Current standard transport; supports OAuth | Slight network overhead |
305
- | **Client `auto`** | The remote server's HTTP transport is not known | Tries Streamable HTTP, then legacy SSE | May make a fallback connection attempt |
306
- | **Legacy `McpAgent` server auto** | An existing server must accept both HTTP transports | Serves both protocols on one endpoint | Uses the deprecated server path |
307
- | **RPC** | Internal agents | Fastest, simplest setup | No auth, Service Bindings only |
308
- | **SSE** | Legacy compatibility | Backwards compatible | Deprecated, use Streamable HTTP |
50
+ `"auto"` tries Streamable HTTP first and falls back to SSE only when the server reports that Streamable HTTP is unsupported. The client negotiates the protocol version on every connection, so it works with both Stateless and 2025-era servers. See [Connecting to MCP Servers](./mcp-client.md) for OAuth, retries, and the rest of the client API.
309
51
 
310
52
  ## Examples
311
53
 
312
- - **Streamable HTTP**: See [`examples/mcp`](https://github.com/cloudflare/agents/tree/main/examples/mcp)
313
- - **RPC Transport**: See [`examples/mcp-rpc-transport`](https://github.com/cloudflare/agents/tree/main/examples/mcp-rpc-transport)
314
- - **MCP Client**: See [`examples/mcp-client`](https://github.com/cloudflare/agents/tree/main/examples/mcp-client)
54
+ - [`examples/mcp-worker`](https://github.com/cloudflare/agents/tree/main/examples/mcp-worker): a server built with `createMcpHandler`
55
+ - [`examples/mcp-client`](https://github.com/cloudflare/agents/tree/main/examples/mcp-client): an Agent that connects to MCP servers
package/docs/routing.md CHANGED
@@ -300,8 +300,6 @@ export default {
300
300
 
301
301
  For agent-specific initialization, use `getAgentByName` instead where you control exactly which agent receives the props.
302
302
 
303
- > **Note:** For `McpAgent`, props are automatically stored and accessible via `this.props`. See [MCP Servers](/mcp-servers) for details.
304
-
305
303
  ### Hooks
306
304
 
307
305
  `routeAgentRequest` supports hooks for intercepting requests before they reach agents:
@@ -14,7 +14,7 @@ function createServer() {
14
14
  return new McpServer({ name: "authenticated-server", version: "1.0.0" });
15
15
  }
16
16
 
17
- const apiHandler = createMcpHandler(createServer);
17
+ const mcpHandler = createMcpHandler(createServer);
18
18
 
19
19
  // Wrap with OAuth protection
20
20
  export default new OAuthProvider({
@@ -23,7 +23,12 @@ export default new OAuthProvider({
23
23
  clientRegistrationEndpoint: "/oauth/register",
24
24
 
25
25
  apiRoute: "/mcp", // Protected MCP endpoint
26
- apiHandler: apiHandler, // Your MCP server
26
+ apiHandler: {
27
+ // OAuthProvider needs an object with a fetch method
28
+ fetch(request: Request, env: Env, ctx: ExecutionContext) {
29
+ return mcpHandler(request, env, ctx);
30
+ }
31
+ },
27
32
 
28
33
  defaultHandler: AuthHandler // Handles consent flow
29
34
  });
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "bin": {
13
13
  "agents": "./dist/cli.js"
14
14
  },
15
- "version": "0.30.0",
15
+ "version": "0.30.1",
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "directory": "packages/agents",