agents 0.16.2 → 0.17.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.
Files changed (126) hide show
  1. package/README.md +11 -8
  2. package/dist/{agent-tool-types-CTw3UJUP.d.ts → agent-tool-types-CNyE1iz_.d.ts} +671 -138
  3. package/dist/agent-tool-types.d.ts +34 -18
  4. package/dist/agent-tool-types.js +20 -1
  5. package/dist/agent-tool-types.js.map +1 -0
  6. package/dist/agent-tools-BXlsuX0d.js +304 -0
  7. package/dist/agent-tools-BXlsuX0d.js.map +1 -0
  8. package/dist/agent-tools-CSnyGvJ2.d.ts +119 -0
  9. package/dist/agent-tools.d.ts +24 -18
  10. package/dist/agent-tools.js.map +1 -1
  11. package/dist/ai-chat-agent.d.ts +1 -1
  12. package/dist/ai-chat-agent.js +1 -2
  13. package/dist/ai-chat-agent.js.map +1 -1
  14. package/dist/ai-chat-v5-migration.d.ts +1 -1
  15. package/dist/ai-chat-v5-migration.js +1 -2
  16. package/dist/ai-chat-v5-migration.js.map +1 -1
  17. package/dist/ai-react.d.ts +1 -1
  18. package/dist/ai-react.js +1 -2
  19. package/dist/ai-react.js.map +1 -1
  20. package/dist/ai-types.d.ts +1 -7
  21. package/dist/ai-types.js +2 -8
  22. package/dist/ai-types.js.map +1 -1
  23. package/dist/browser/ai.js +1 -1
  24. package/dist/browser/index.js +1 -1
  25. package/dist/chat/index.d.ts +2033 -77
  26. package/dist/chat/index.js +1828 -245
  27. package/dist/chat/index.js.map +1 -1
  28. package/dist/chat/react.d.ts +602 -0
  29. package/dist/chat/react.js +1506 -0
  30. package/dist/chat/react.js.map +1 -0
  31. package/dist/chat-sdk/index.d.ts +4 -4
  32. package/dist/{classPrivateFieldGet2-CZ7QjTXN.js → classPrivateFieldGet2-DZBYAB34.js} +5 -5
  33. package/dist/{classPrivateMethodInitSpec-D-0__zd9.js → classPrivateMethodInitSpec-qMjJ6sHQ.js} +2 -2
  34. package/dist/{client-BXJ9n2f7.js → client-BZ-B3NhC.js} +2 -2
  35. package/dist/client-BZ-B3NhC.js.map +1 -0
  36. package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
  37. package/dist/client.d.ts +76 -57
  38. package/dist/client.js +33 -5
  39. package/dist/client.js.map +1 -1
  40. package/dist/{connector-CrKhowfD.js → connector-CdldGF3h.js} +5 -5
  41. package/dist/{connector-CrKhowfD.js.map → connector-CdldGF3h.js.map} +1 -1
  42. package/dist/email.js +1 -1
  43. package/dist/email.js.map +1 -1
  44. package/dist/{index-B7IbEeze.d.ts → index-BRnybD6X.d.ts} +197 -14
  45. package/dist/index.d.ts +95 -73
  46. package/dist/index.js +694 -25
  47. package/dist/index.js.map +1 -1
  48. package/dist/mcp/client.d.ts +18 -14
  49. package/dist/mcp/client.js +1 -1
  50. package/dist/mcp/index.d.ts +30 -30
  51. package/dist/mcp/index.js +7 -7
  52. package/dist/mcp/index.js.map +1 -1
  53. package/dist/{agent-tools-3zLG7MgA.js → message-builder-BymO4N_D.js} +45 -126
  54. package/dist/message-builder-BymO4N_D.js.map +1 -0
  55. package/dist/observability/index.d.ts +1 -1
  56. package/dist/observability/index.js +4 -2
  57. package/dist/observability/index.js.map +1 -1
  58. package/dist/react.d.ts +123 -110
  59. package/dist/react.js +44 -11
  60. package/dist/react.js.map +1 -1
  61. package/dist/{retries-CwlpAGet.d.ts → retries-CvHJwSuh.d.ts} +15 -6
  62. package/dist/retries.d.ts +8 -6
  63. package/dist/retries.js +44 -1
  64. package/dist/retries.js.map +1 -1
  65. package/dist/serializable.d.ts +1 -1
  66. package/dist/skills/compile.js +1 -1
  67. package/dist/skills/compile.js.map +1 -1
  68. package/dist/skills/index.js +5 -5
  69. package/dist/skills/index.js.map +1 -1
  70. package/dist/sub-routing.d.ts +6 -6
  71. package/dist/utils.js +1 -1
  72. package/dist/utils.js.map +1 -1
  73. package/dist/vite.js +5 -5
  74. package/dist/vite.js.map +1 -1
  75. package/dist/wire-types-nflOzNuU.js +240 -0
  76. package/dist/wire-types-nflOzNuU.js.map +1 -0
  77. package/dist/{workflow-types-SrZK_o9p.d.ts → workflow-types-Baz_PO5v.d.ts} +42 -22
  78. package/dist/workflow-types.d.ts +25 -21
  79. package/dist/workflow-types.js.map +1 -1
  80. package/dist/workflows.d.ts +22 -21
  81. package/dist/workflows.js +31 -7
  82. package/dist/workflows.js.map +1 -1
  83. package/docs/adding-to-existing-project.md +450 -0
  84. package/docs/agent-class.md +503 -0
  85. package/docs/agent-tools.md +552 -0
  86. package/docs/browse-the-web.md +430 -0
  87. package/docs/callable-methods.md +627 -0
  88. package/docs/chat-agents.md +1696 -0
  89. package/docs/chat-sdk.md +181 -0
  90. package/docs/client-sdk.md +520 -0
  91. package/docs/client-tools-continuation.md +177 -0
  92. package/docs/codemode.md +440 -0
  93. package/docs/configuration.md +775 -0
  94. package/docs/cross-domain-authentication.md +171 -0
  95. package/docs/durable-execution.md +537 -0
  96. package/docs/email.md +663 -0
  97. package/docs/get-current-agent.md +204 -0
  98. package/docs/getting-started.md +305 -0
  99. package/docs/http-websockets.md +668 -0
  100. package/docs/human-in-the-loop.md +661 -0
  101. package/docs/index.md +151 -0
  102. package/docs/long-running-agents.md +730 -0
  103. package/docs/mcp-client.md +620 -0
  104. package/docs/mcp-servers.md +526 -0
  105. package/docs/mcp-transports.md +308 -0
  106. package/docs/migration-to-ai-sdk-v5.md +96 -0
  107. package/docs/migration-to-ai-sdk-v6.md +163 -0
  108. package/docs/observability.md +261 -0
  109. package/docs/push-notifications.md +367 -0
  110. package/docs/queue.md +329 -0
  111. package/docs/readonly-connections.md +278 -0
  112. package/docs/resumable-streaming.md +127 -0
  113. package/docs/retries.md +444 -0
  114. package/docs/routing.md +749 -0
  115. package/docs/scheduling.md +898 -0
  116. package/docs/securing-mcp-servers.md +359 -0
  117. package/docs/server-driven-messages.md +477 -0
  118. package/docs/sessions.md +1024 -0
  119. package/docs/state.md +512 -0
  120. package/docs/sub-agents.md +389 -0
  121. package/docs/webhooks.md +604 -0
  122. package/docs/workflows.md +877 -0
  123. package/package.json +41 -14
  124. package/dist/agent-tools-3zLG7MgA.js.map +0 -1
  125. package/dist/agent-tools-DZhI5F6Q.d.ts +0 -14
  126. package/dist/client-BXJ9n2f7.js.map +0 -1
@@ -0,0 +1,620 @@
1
+ # Connecting to MCP Servers
2
+
3
+ Connect your agent to external MCP (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.
4
+
5
+ ## Overview
6
+
7
+ The MCP client capability lets your agent:
8
+
9
+ - **Connect to external MCP servers** - GitHub, Slack, databases, AI services
10
+ - **Use their tools** - Call functions exposed by MCP servers
11
+ - **Access resources** - Read data from MCP servers
12
+ - **Use prompts** - Leverage pre-built prompt templates
13
+
14
+ > **Note:** This page covers connecting to MCP servers as a client. To create your own MCP server, see [Creating MCP Servers](./mcp-servers.md).
15
+
16
+ ## Quick Start
17
+
18
+ ```typescript
19
+ import { Agent } from "agents";
20
+
21
+ export class MyAgent extends Agent {
22
+ async onRequest(request: Request) {
23
+ // Add an MCP server
24
+ const result = await this.addMcpServer(
25
+ "github",
26
+ "https://mcp.github.com/mcp"
27
+ );
28
+
29
+ if (result.state === "authenticating") {
30
+ // Server requires OAuth - redirect user to authorize
31
+ return Response.redirect(result.authUrl);
32
+ }
33
+
34
+ // Server is ready - tools are now available
35
+ const state = this.getMcpServers();
36
+ console.log(`Connected! ${state.tools.length} tools available`);
37
+
38
+ return new Response("MCP server connected");
39
+ }
40
+ }
41
+ ```
42
+
43
+ ## Adding MCP Servers
44
+
45
+ Use `addMcpServer()` to connect to an MCP server:
46
+
47
+ ```typescript
48
+ const result = await this.addMcpServer(name, url, options?);
49
+ ```
50
+
51
+ ### Basic Usage
52
+
53
+ ```typescript
54
+ // Simple connection
55
+ await this.addMcpServer("notion", "https://mcp.notion.so/mcp");
56
+
57
+ // With explicit callback host (rarely needed — auto-derived from request or WebSocket URI)
58
+ await this.addMcpServer("github", "https://mcp.github.com/mcp", {
59
+ callbackHost: "https://my-worker.workers.dev"
60
+ });
61
+ ```
62
+
63
+ ### Transport Options
64
+
65
+ MCP supports multiple transport types:
66
+
67
+ ```typescript
68
+ await this.addMcpServer("server", "https://mcp.example.com/mcp", {
69
+ transport: {
70
+ // Transport type: "streamable-http" (default), "sse", or "auto"
71
+ type: "streamable-http"
72
+ }
73
+ });
74
+ ```
75
+
76
+ | Transport | Description |
77
+ | ------------------- | ----------------------------------------------------- |
78
+ | `"streamable-http"` | HTTP with streaming - recommended default |
79
+ | `"sse"` | Server-Sent Events - legacy / compatibility transport |
80
+ | `"auto"` | Auto-detect based on server response |
81
+
82
+ ### Custom Headers
83
+
84
+ For servers behind authentication (like Cloudflare Access) or using bearer tokens:
85
+
86
+ ```typescript
87
+ await this.addMcpServer("internal", "https://internal-mcp.example.com/mcp", {
88
+ transport: {
89
+ headers: {
90
+ Authorization: "Bearer my-token",
91
+ "CF-Access-Client-Id": "...",
92
+ "CF-Access-Client-Secret": "..."
93
+ }
94
+ }
95
+ });
96
+ ```
97
+
98
+ ### Retry Options
99
+
100
+ Configure retry behavior for connection and reconnection attempts:
101
+
102
+ ```typescript
103
+ await this.addMcpServer("github", "https://mcp.github.com/mcp", {
104
+ retry: {
105
+ maxAttempts: 5,
106
+ baseDelayMs: 1000,
107
+ maxDelayMs: 10000
108
+ }
109
+ });
110
+ ```
111
+
112
+ These options are persisted and used when reconnecting after hibernation or after OAuth completion. Default: 3 attempts, 500ms base delay, 5s max delay. See [Retries](./retries.md) for more details.
113
+
114
+ ### URL Security
115
+
116
+ MCP server URLs are validated before connection to prevent Server-Side Request Forgery (SSRF). The following URL targets are blocked:
117
+
118
+ - Private/internal IP ranges (RFC 1918: `10.x`, `172.16-31.x`, `192.168.x`)
119
+ - Unspecified addresses (`0.0.0.0`, `::`)
120
+ - Link-local addresses (`169.254.x`, `fe80::`)
121
+ - Cloud metadata endpoints (`169.254.169.254`)
122
+ - IPv6 unique-local addresses (`fc00::/7`)
123
+
124
+ Loopback development URLs such as `localhost`, `127.0.0.1`, and `::1` are allowed.
125
+
126
+ If you need to connect to another internal MCP server, use the [RPC transport](./mcp-transports.md) with a Durable Object binding instead of HTTP.
127
+
128
+ ### Return Value
129
+
130
+ `addMcpServer()` returns the connection state:
131
+
132
+ ```typescript
133
+ type AddMcpServerResult =
134
+ | { id: string; state: "ready" }
135
+ | { id: string; state: "authenticating"; authUrl: string };
136
+ ```
137
+
138
+ - **`ready`** - Server connected and tools discovered
139
+ - **`authenticating`** - Server requires OAuth; redirect user to `authUrl`
140
+
141
+ ## OAuth Authentication
142
+
143
+ Many MCP servers require OAuth authentication. The agent handles the OAuth flow automatically.
144
+
145
+ ### How It Works
146
+
147
+ ```mermaid
148
+ sequenceDiagram
149
+ participant Client
150
+ participant Agent
151
+ participant MCPServer
152
+
153
+ Client->>Agent: addMcpServer(name, url)
154
+ Agent->>MCPServer: Connect
155
+ MCPServer-->>Agent: Requires OAuth
156
+ Agent-->>Client: state: authenticating, authUrl
157
+ Client->>MCPServer: User authorizes
158
+ MCPServer->>Agent: Callback with code
159
+ Agent->>MCPServer: Exchange for token
160
+ Agent-->>Client: onMcpUpdate (ready)
161
+ ```
162
+
163
+ ### Handling OAuth in Your Agent
164
+
165
+ ```typescript
166
+ async onRequest(request: Request) {
167
+ const result = await this.addMcpServer("github", "https://mcp.github.com/mcp");
168
+
169
+ if (result.state === "authenticating") {
170
+ // Option 1: Redirect the user
171
+ return Response.redirect(result.authUrl);
172
+
173
+ // Option 2: Return the URL for client-side redirect
174
+ return Response.json({
175
+ status: "needs_auth",
176
+ authUrl: result.authUrl
177
+ });
178
+ }
179
+
180
+ return Response.json({ status: "connected", id: result.id });
181
+ }
182
+ ```
183
+
184
+ ### OAuth Callback
185
+
186
+ The callback URL is automatically constructed:
187
+
188
+ ```
189
+ https://{host}/{agentsPrefix}/{agent-name}/{instance-name}/callback
190
+ ```
191
+
192
+ For example: `https://my-worker.workers.dev/agents/my-agent/default/callback`
193
+
194
+ OAuth tokens are securely stored in SQLite and persist across agent restarts.
195
+
196
+ ### Custom Callback Handling
197
+
198
+ For custom OAuth completion behavior:
199
+
200
+ ```typescript
201
+ // In your agent constructor or onStart
202
+ this.mcp.configureOAuthCallback({
203
+ // Redirect after successful auth
204
+ successRedirect: "https://myapp.com/success",
205
+
206
+ // Redirect on error
207
+ errorRedirect: "https://myapp.com/error",
208
+
209
+ // Or use a custom handler
210
+ customHandler: (result) => {
211
+ return new Response(
212
+ JSON.stringify({
213
+ success: result.authSuccess,
214
+ serverId: result.serverId,
215
+ error: result.authError
216
+ }),
217
+ {
218
+ headers: { "Content-Type": "application/json" }
219
+ }
220
+ );
221
+ }
222
+ });
223
+ ```
224
+
225
+ ### Custom OAuth Provider
226
+
227
+ By default, agents use dynamic client registration to authenticate with MCP servers. If you need to use a different OAuth strategy — such as pre-registered client credentials, mTLS-based authentication, or other mechanisms — override the `createMcpOAuthProvider` method in your agent subclass:
228
+
229
+ ```typescript
230
+ import { Agent } from "agents";
231
+ import type { AgentMcpOAuthProvider } from "agents";
232
+
233
+ class MyAgent extends Agent {
234
+ createMcpOAuthProvider(callbackUrl: string): AgentMcpOAuthProvider {
235
+ return new MyCustomOAuthProvider(this.ctx.storage, this.name, callbackUrl);
236
+ }
237
+ }
238
+ ```
239
+
240
+ Your custom class must implement the `AgentMcpOAuthProvider` interface, which extends the MCP SDK's `OAuthClientProvider` with additional properties (`authUrl`, `clientId`, `serverId`) and methods (`checkState`, `consumeState`, `deleteCodeVerifier`) used by the agent's MCP connection lifecycle.
241
+
242
+ The override is used for both new connections (`addMcpServer`) and restored connections after a Durable Object restart, so your custom provider is always used consistently.
243
+
244
+ #### Custom storage backend
245
+
246
+ The most common customization is using a different storage backend while keeping the built-in OAuth logic (CSRF state, PKCE, nonce generation, token management). Import `DurableObjectOAuthClientProvider` and pass your own storage adapter:
247
+
248
+ ```typescript
249
+ import { Agent, DurableObjectOAuthClientProvider } from "agents";
250
+ import type { AgentMcpOAuthProvider } from "agents";
251
+
252
+ class MyAgent extends Agent {
253
+ createMcpOAuthProvider(callbackUrl: string): AgentMcpOAuthProvider {
254
+ return new DurableObjectOAuthClientProvider(
255
+ myCustomStorage, // any DurableObjectStorage-compatible adapter
256
+ this.name,
257
+ callbackUrl
258
+ );
259
+ }
260
+ }
261
+ ```
262
+
263
+ ## Using MCP Capabilities
264
+
265
+ Once connected, access the server's capabilities:
266
+
267
+ ### Getting Available Tools
268
+
269
+ ```typescript
270
+ const state = this.getMcpServers();
271
+
272
+ // All tools from all connected servers
273
+ for (const tool of state.tools) {
274
+ console.log(`Tool: ${tool.name}`);
275
+ console.log(` From server: ${tool.serverId}`);
276
+ console.log(` Description: ${tool.description}`);
277
+ }
278
+ ```
279
+
280
+ ### Resources and Prompts
281
+
282
+ ```typescript
283
+ const state = this.getMcpServers();
284
+
285
+ // Available resources
286
+ for (const resource of state.resources) {
287
+ console.log(`Resource: ${resource.name} (${resource.uri})`);
288
+ }
289
+
290
+ // Available prompts
291
+ for (const prompt of state.prompts) {
292
+ console.log(`Prompt: ${prompt.name}`);
293
+ }
294
+ ```
295
+
296
+ ### Server Status
297
+
298
+ ```typescript
299
+ const state = this.getMcpServers();
300
+
301
+ for (const [id, server] of Object.entries(state.servers)) {
302
+ console.log(`${server.name}: ${server.state}`);
303
+ // state: "ready" | "authenticating" | "connecting" | "connected" | "discovering" | "failed"
304
+ }
305
+ ```
306
+
307
+ ### Integration with AI SDK
308
+
309
+ To use MCP tools with the Vercel AI SDK, use `this.mcp.getAITools()` which converts MCP tools to AI SDK format:
310
+
311
+ ```typescript
312
+ import { generateText } from "ai";
313
+
314
+ async function chat(prompt: string) {
315
+ const response = await generateText({
316
+ model: openai("gpt-4"),
317
+ prompt,
318
+ tools: this.mcp.getAITools() // Converts MCP tools to AI SDK format
319
+ });
320
+
321
+ return response;
322
+ }
323
+ ```
324
+
325
+ > **Note:** `getMcpServers().tools` returns raw MCP `Tool` objects for inspection. Use `this.mcp.getAITools()` when passing tools to the AI SDK.
326
+
327
+ ## Managing Servers
328
+
329
+ ### Removing a Server
330
+
331
+ ```typescript
332
+ await this.removeMcpServer(serverId);
333
+ ```
334
+
335
+ This disconnects from the server and removes it from storage.
336
+
337
+ ### Persistence
338
+
339
+ MCP servers persist across agent restarts:
340
+
341
+ - Server configuration stored in SQLite
342
+ - OAuth tokens stored securely
343
+ - Connections restored automatically when agent wakes
344
+
345
+ ### Listing All Servers
346
+
347
+ ```typescript
348
+ const state = this.getMcpServers();
349
+
350
+ for (const [id, server] of Object.entries(state.servers)) {
351
+ console.log(`${id}: ${server.name} (${server.server_url})`);
352
+ }
353
+ ```
354
+
355
+ ## Client-Side Integration
356
+
357
+ Connected clients receive real-time MCP updates via WebSocket:
358
+
359
+ ```typescript
360
+ import { useAgent } from "agents/react";
361
+
362
+ function Dashboard() {
363
+ const [tools, setTools] = useState([]);
364
+ const [servers, setServers] = useState({});
365
+
366
+ const agent = useAgent({
367
+ agent: "MyAgent",
368
+ onMcpUpdate: (mcpState) => {
369
+ setTools(mcpState.tools);
370
+ setServers(mcpState.servers);
371
+ }
372
+ });
373
+
374
+ return (
375
+ <div>
376
+ <h2>Connected Servers</h2>
377
+ {Object.entries(servers).map(([id, server]) => (
378
+ <div key={id}>
379
+ {server.name}: {server.connectionState}
380
+ </div>
381
+ ))}
382
+
383
+ <h2>Available Tools ({tools.length})</h2>
384
+ {tools.map(tool => (
385
+ <div key={`${tool.serverId}-${tool.name}`}>
386
+ {tool.name}
387
+ </div>
388
+ ))}
389
+ </div>
390
+ );
391
+ }
392
+ ```
393
+
394
+ ## Advanced: MCPClientManager
395
+
396
+ For fine-grained control, use `this.mcp` directly:
397
+
398
+ ### Step-by-Step Connection
399
+
400
+ ```typescript
401
+ // 1. Register the server (saves to storage and creates in-memory connection)
402
+ const id = "my-server";
403
+ await this.mcp.registerServer(id, {
404
+ url: "https://mcp.example.com/mcp",
405
+ name: "My Server",
406
+ callbackUrl: "https://my-worker.workers.dev/agents/my-agent/default/callback",
407
+ transport: { type: "auto" }
408
+ });
409
+
410
+ // 2. Connect (initializes transport, handles OAuth if needed)
411
+ const connectResult = await this.mcp.connectToServer(id);
412
+
413
+ if (connectResult.state === "failed") {
414
+ console.error("Connection failed:", connectResult.error);
415
+ return;
416
+ }
417
+
418
+ if (connectResult.state === "authenticating") {
419
+ console.log("OAuth required:", connectResult.authUrl);
420
+ return;
421
+ }
422
+
423
+ // 3. Discover capabilities (transitions from "connected" to "ready")
424
+ if (connectResult.state === "connected") {
425
+ const discoverResult = await this.mcp.discoverIfConnected(id);
426
+
427
+ if (!discoverResult?.success) {
428
+ console.error("Discovery failed:", discoverResult?.error);
429
+ }
430
+ }
431
+ ```
432
+
433
+ ### Event Subscription
434
+
435
+ ```typescript
436
+ // Listen for state changes (onServerStateChanged is an Event<void>)
437
+ const disposable = this.mcp.onServerStateChanged(() => {
438
+ console.log("MCP server state changed");
439
+ this.broadcastMcpServers(); // Notify connected clients
440
+ });
441
+
442
+ // Clean up the subscription when no longer needed
443
+ // disposable.dispose();
444
+ ```
445
+
446
+ ### Waiting for Connections
447
+
448
+ After hibernation or when connections are being restored in the background, MCP tools may not be immediately available. Use `waitForConnections()` to wait until all in-flight connection and discovery operations have settled:
449
+
450
+ ```typescript
451
+ // Wait indefinitely for all connections to be ready
452
+ await this.mcp.waitForConnections();
453
+
454
+ // Wait with a timeout (in milliseconds)
455
+ await this.mcp.waitForConnections({ timeout: 10_000 });
456
+ ```
457
+
458
+ This is useful when you need to call `this.mcp.getAITools()` immediately after the agent wakes from hibernation. Without waiting, tools from servers that are still reconnecting will be missing.
459
+
460
+ > **Note:** `AIChatAgent` handles this automatically via the `waitForMcpConnections` property (defaults to `{ timeout: 10_000 }`). You only need `waitForConnections()` directly when using `Agent` with MCP, or when you want finer control inside `onChatMessage`.
461
+
462
+ ### Error Recovery
463
+
464
+ ```typescript
465
+ async retryConnection(serverId: string) {
466
+ const result = await this.mcp.connectToServer(serverId);
467
+
468
+ if (result.state === "connected") {
469
+ await this.mcp.discoverIfConnected(serverId);
470
+ } else if (result.state === "failed") {
471
+ console.error("Reconnection failed:", result.error);
472
+ }
473
+ }
474
+ ```
475
+
476
+ ## Examples
477
+
478
+ ### MCP Client Demo
479
+
480
+ The [`examples/mcp-client`](https://github.com/cloudflare/agents/tree/main/examples/mcp-client) example demonstrates:
481
+
482
+ - Adding and removing MCP servers dynamically
483
+ - Custom OAuth callback handling (popup-closing behavior)
484
+ - Listing tools from connected servers
485
+ - Real-time state updates to the frontend
486
+
487
+ ```typescript
488
+ // From examples/mcp-client/src/server.ts
489
+ export class MyAgent extends Agent {
490
+ onStart() {
491
+ // Custom OAuth callback that closes the popup window
492
+ this.mcp.configureOAuthCallback({
493
+ customHandler: (result) => {
494
+ if (result.authSuccess) {
495
+ return new Response("<script>window.close();</script>", {
496
+ headers: { "content-type": "text/html" }
497
+ });
498
+ }
499
+ // Handle error...
500
+ }
501
+ });
502
+ }
503
+
504
+ async onRequest(request: Request) {
505
+ const url = new URL(request.url);
506
+
507
+ if (url.pathname.endsWith("add-mcp")) {
508
+ const { name, url } = await request.json();
509
+ await this.addMcpServer(name, url);
510
+ return new Response("Ok");
511
+ }
512
+ // ...
513
+ }
514
+ }
515
+ ```
516
+
517
+ ## API Reference
518
+
519
+ ### addMcpServer()
520
+
521
+ ```typescript
522
+ // HTTP transport (Streamable HTTP, SSE)
523
+ async addMcpServer(
524
+ name: string,
525
+ url: string,
526
+ options?: {
527
+ callbackHost?: string; // auto-derived from request or WebSocket connection URI; only set to override
528
+ callbackPath?: string; // custom callback URL path (bypasses default /agents/{class}/{name}/callback)
529
+ agentsPrefix?: string;
530
+ client?: ClientOptions;
531
+ transport?: {
532
+ headers?: HeadersInit;
533
+ type?: "sse" | "streamable-http" | "auto"; // default: "auto"
534
+ };
535
+ retry?: RetryOptions; // retry options for connection/reconnection
536
+ }
537
+ ): Promise<
538
+ | { id: string; state: "ready" }
539
+ | { id: string; state: "authenticating"; authUrl: string }
540
+ >
541
+
542
+ // RPC transport (Durable Object binding — no HTTP overhead)
543
+ async addMcpServer(
544
+ name: string,
545
+ binding: DurableObjectNamespace,
546
+ options?: {
547
+ props?: Record<string, unknown>; // passed to the McpAgent's onStart(props)
548
+ client?: ClientOptions;
549
+ retry?: RetryOptions;
550
+ }
551
+ ): Promise<{ id: string; state: "ready" }>
552
+
553
+ // Legacy signature (still supported)
554
+ async addMcpServer(
555
+ name: string,
556
+ url: string,
557
+ callbackHost?: string,
558
+ agentsPrefix?: string,
559
+ options?: { ... }
560
+ ): Promise<...>
561
+ ```
562
+
563
+ Add and connect to an MCP server. Throws if connection or discovery fails.
564
+
565
+ `callbackHost` is automatically derived from the incoming HTTP request or WebSocket connection URI — you almost never need to set it explicitly. It is only needed when the auto-detected host does not match your desired OAuth callback origin (for example, behind a reverse proxy). For RPC transport, pass a `DurableObjectNamespace` binding instead of a URL. See [MCP Transports](./mcp-transports.md) for details.
566
+
567
+ Calling `addMcpServer` is idempotent when both the server name **and** URL match an existing active connection — the existing connection is returned without creating a duplicate. This makes it safe to call in `onStart()` without worrying about duplicate connections on restart.
568
+
569
+ If you call `addMcpServer` with the same name but a **different** URL, a new connection is created. Both connections remain active and their tools are merged in `getAITools()`. To replace a server, call `removeMcpServer(oldId)` first.
570
+
571
+ > **Note:** URLs are normalized before comparison (trailing slashes, default ports, and hostname case are handled), so `https://MCP.Example.com` and `https://mcp.example.com/` are treated as the same URL.
572
+
573
+ ### removeMcpServer()
574
+
575
+ ```typescript
576
+ async removeMcpServer(id: string): Promise<void>
577
+ ```
578
+
579
+ Disconnect from and remove an MCP server.
580
+
581
+ ### getMcpServers()
582
+
583
+ ```typescript
584
+ getMcpServers(): MCPServersState
585
+ ```
586
+
587
+ Get the current state of all MCP servers and their capabilities.
588
+
589
+ ### MCPServersState
590
+
591
+ ```typescript
592
+ type MCPServersState = {
593
+ servers: {
594
+ [id: string]: MCPServer;
595
+ };
596
+ tools: (Tool & { serverId: string })[];
597
+ prompts: (Prompt & { serverId: string })[];
598
+ resources: (Resource & { serverId: string })[];
599
+ };
600
+ ```
601
+
602
+ ### MCPServer
603
+
604
+ ```typescript
605
+ type MCPServer = {
606
+ name: string;
607
+ server_url: string;
608
+ auth_url: string | null;
609
+ state:
610
+ | "ready"
611
+ | "authenticating"
612
+ | "connecting"
613
+ | "connected"
614
+ | "discovering"
615
+ | "failed";
616
+ error: string | null;
617
+ instructions: string | null;
618
+ capabilities: ServerCapabilities | null;
619
+ };
620
+ ```