agents 0.19.0 → 0.20.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 (57) hide show
  1. package/README.md +24 -19
  2. package/dist/{agent-tool-types-BNUGGBzQ.d.ts → agent-tool-types-BC-WFlsz.d.ts} +1026 -400
  3. package/dist/agent-tool-types.d.ts +1 -1
  4. package/dist/{agent-tools-BFbzVLFc.d.ts → agent-tools-DeHe9Xov.d.ts} +2 -2
  5. package/dist/agent-tools.d.ts +1 -1
  6. package/dist/browser/ai.js +1 -1
  7. package/dist/browser/index.js +1 -1
  8. package/dist/chat/index.d.ts +2 -2
  9. package/dist/chat-sdk/index.d.ts +1 -1
  10. package/dist/client-invoker-BNSZxAkv.d.ts +20 -0
  11. package/dist/client-invoker-VNZ7X0nn.js +57 -0
  12. package/dist/client-invoker-VNZ7X0nn.js.map +1 -0
  13. package/dist/{client-CcjiFpTf.js → client-zqKcsyFa.js} +434 -168
  14. package/dist/client-zqKcsyFa.js.map +1 -0
  15. package/dist/client.d.ts +1 -1
  16. package/dist/cloudflare-BduZwmYK.js +204 -0
  17. package/dist/cloudflare-BduZwmYK.js.map +1 -0
  18. package/dist/{connector-CdldGF3h.js → connector-KEJnl6e5.js} +2 -2
  19. package/dist/connector-KEJnl6e5.js.map +1 -0
  20. package/dist/{do-oauth-client-provider-D4ZwyBDu.d.ts → do-oauth-client-provider-VTZj2VtM.d.ts} +23 -11
  21. package/dist/experimental/webmcp.js +1 -1
  22. package/dist/handler-stateless-CIkKPETH.js +318 -0
  23. package/dist/handler-stateless-CIkKPETH.js.map +1 -0
  24. package/dist/handler-stateless-C_bo-Ytq.d.ts +107 -0
  25. package/dist/index.d.ts +14 -12
  26. package/dist/index.js +30 -20
  27. package/dist/index.js.map +1 -1
  28. package/dist/mcp/client.d.ts +22 -18
  29. package/dist/mcp/client.js +1 -1
  30. package/dist/mcp/do-oauth-client-provider.d.ts +1 -1
  31. package/dist/mcp/do-oauth-client-provider.js +25 -12
  32. package/dist/mcp/do-oauth-client-provider.js.map +1 -1
  33. package/dist/mcp/index.d.ts +48 -34
  34. package/dist/mcp/index.js +95 -156
  35. package/dist/mcp/index.js.map +1 -1
  36. package/dist/mcp/server.d.ts +17 -0
  37. package/dist/mcp/server.js +2 -0
  38. package/dist/mcp/x402.d.ts +21 -9
  39. package/dist/mcp/x402.js +8 -7
  40. package/dist/mcp/x402.js.map +1 -1
  41. package/dist/observability/ai/index.d.ts +9 -1
  42. package/dist/observability/ai/index.js +188 -63
  43. package/dist/observability/ai/index.js.map +1 -1
  44. package/dist/react.d.ts +1 -1
  45. package/dist/serializable.d.ts +1 -1
  46. package/dist/sub-routing.d.ts +6 -6
  47. package/dist/workflows.d.ts +1 -1
  48. package/docs/human-in-the-loop.md +63 -82
  49. package/docs/mcp-client.md +31 -7
  50. package/docs/mcp-servers.md +125 -88
  51. package/docs/observability.md +2 -1
  52. package/docs/securing-mcp-servers.md +9 -6
  53. package/package.json +28 -7
  54. package/dist/client-CcjiFpTf.js.map +0 -1
  55. package/dist/cloudflare-BldFV0Pa.js +0 -117
  56. package/dist/cloudflare-BldFV0Pa.js.map +0 -1
  57. package/dist/connector-CdldGF3h.js.map +0 -1
package/dist/react.d.ts CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  Y as MCPServersState,
3
3
  _ as AgentToolRunState,
4
4
  g as AgentToolRunPart
5
- } from "./agent-tool-types-BNUGGBzQ.js";
5
+ } from "./agent-tool-types-BC-WFlsz.js";
6
6
  import { ClientParameters } from "./serializable.js";
7
7
  import {
8
8
  AgentConnectionError,
@@ -1,4 +1,4 @@
1
- import { st as StreamingResponse } from "./agent-tool-types-BNUGGBzQ.js";
1
+ import { st as StreamingResponse } from "./agent-tool-types-BC-WFlsz.js";
2
2
 
3
3
  //#region src/serializable.d.ts
4
4
  type SerializablePrimitive = undefined | null | string | number | boolean;
@@ -1,10 +1,10 @@
1
1
  import {
2
- an as SUB_PREFIX,
3
- cn as parseSubAgentPath,
4
- ln as routeSubAgentRequest,
5
- on as SubAgentPathMatch,
6
- sn as getSubAgentByName
7
- } from "./agent-tool-types-BNUGGBzQ.js";
2
+ cn as SubAgentPathMatch,
3
+ dn as routeSubAgentRequest,
4
+ ln as getSubAgentByName,
5
+ sn as SUB_PREFIX,
6
+ un as parseSubAgentPath
7
+ } from "./agent-tool-types-BC-WFlsz.js";
8
8
  export {
9
9
  SUB_PREFIX,
10
10
  SubAgentPathMatch,
@@ -1,4 +1,4 @@
1
- import { O as Agent } from "./agent-tool-types-BNUGGBzQ.js";
1
+ import { O as Agent } from "./agent-tool-types-BC-WFlsz.js";
2
2
  import {
3
3
  C as WorkflowStatus,
4
4
  S as WorkflowRejectedError,
@@ -25,13 +25,14 @@ Human-in-the-loop (HITL) patterns allow agents to pause execution and wait for h
25
25
 
26
26
  Agents SDK supports multiple human-in-the-loop patterns. Choose based on your use case:
27
27
 
28
- | Use Case | Pattern | Best For | Example |
29
- | ---------------------- | ----------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
30
- | Long-running workflows | Workflow Approval | Multi-step processes, durable approval gates | [examples/workflows/](https://github.com/cloudflare/agents/tree/main/examples/workflows) |
31
- | AIChatAgent tools | `needsApproval` | Chat-based tool calls with `@cloudflare/ai-chat` | [guides/human-in-the-loop/](https://github.com/cloudflare/agents/tree/main/guides/human-in-the-loop) |
32
- | OpenAI Agents SDK | `needsApproval` | Using OpenAI's agent SDK with conditional approval | [openai-sdk/human-in-the-loop/](https://github.com/cloudflare/agents/tree/main/openai-sdk/human-in-the-loop) |
33
- | Client-side tools | `onToolCall` | Tools that need browser APIs or user interaction | Pattern below |
34
- | MCP Servers | Elicitation | MCP tools requesting structured user input | [examples/mcp-elicitation/](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) |
28
+ | Use Case | Pattern | Best For | Example |
29
+ | ---------------------- | --------------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
30
+ | Long-running workflows | Workflow Approval | Multi-step processes, durable approval gates | [examples/workflows/](https://github.com/cloudflare/agents/tree/main/examples/workflows) |
31
+ | AIChatAgent tools | `needsApproval` | Chat-based tool calls with `@cloudflare/ai-chat` | [guides/human-in-the-loop/](https://github.com/cloudflare/agents/tree/main/guides/human-in-the-loop) |
32
+ | OpenAI Agents SDK | `needsApproval` | Using OpenAI's agent SDK with conditional approval | [openai-sdk/human-in-the-loop/](https://github.com/cloudflare/agents/tree/main/openai-sdk/human-in-the-loop) |
33
+ | Client-side tools | `onToolCall` | Tools that need browser APIs or user interaction | Pattern below |
34
+ | Stateless servers | Stateless Elicitation | Current MCP tools requesting structured input | [examples/mcp-elicitation-mrtr/](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) |
35
+ | Legacy servers | Legacy Elicitation | Existing sessionful MCP deployments | [examples/mcp-elicitation/](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) |
35
36
 
36
37
  ### Decision Guide
37
38
 
@@ -452,85 +453,64 @@ See the complete example: [openai-sdk/human-in-the-loop/](https://github.com/clo
452
453
 
453
454
  ### MCP Elicitation
454
455
 
455
- When building MCP servers with `McpAgent`, you can request additional user input during tool execution using **elicitation**. The MCP client (like Claude Desktop) renders a form based on your JSON Schema and returns the user's response.
456
+ **Stateless Elicitation** uses multi-round-trip requests (MRTR). A handler returns `inputRequired(...)`; the client gathers the requested input and retries with SDK-managed state. No Worker remains suspended while the user responds.
456
457
 
457
458
  ```typescript
458
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
459
- import { Agent } from "agents";
460
-
461
- export class MyMcpAgent extends Agent<Env, State> {
462
- server = new McpServer({
463
- name: "my-mcp-server",
464
- version: "1.0.0"
465
- });
466
-
467
- onStart() {
468
- this.server.registerTool(
469
- "increase-counter",
470
- {
471
- description: "Increase the counter by a user-specified amount",
472
- inputSchema: {
473
- confirm: z.boolean().describe("Do you want to increase the counter?")
474
- }
475
- },
476
- async ({ confirm }, extra) => {
477
- if (!confirm) {
478
- return { content: [{ type: "text", text: "Cancelled." }] };
479
- }
480
-
481
- // Request additional input from the user
482
- const userInput = await this.server.server.elicitInput(
483
- {
484
- message: "By how much do you want to increase the counter?",
485
- requestedSchema: {
486
- type: "object",
487
- properties: {
488
- amount: {
489
- type: "number",
490
- title: "Amount",
491
- description: "The amount to increase the counter by"
492
- }
493
- },
494
- required: ["amount"]
495
- }
496
- },
497
- { relatedRequestId: extra.requestId }
498
- );
499
-
500
- // Check if user accepted or cancelled
501
- if (userInput.action !== "accept" || !userInput.content) {
502
- return { content: [{ type: "text", text: "Cancelled." }] };
503
- }
459
+ import {
460
+ McpServer,
461
+ acceptedContent,
462
+ inputRequired
463
+ } from "@modelcontextprotocol/server";
464
+ import { createMcpHandler } from "agents/mcp/server";
465
+ import { z } from "zod";
504
466
 
505
- // Use the input
506
- const amount = Number(userInput.content.amount);
507
- this.setState({
508
- ...this.state,
509
- counter: this.state.counter + amount
467
+ function createServer() {
468
+ const server = new McpServer({ name: "my-server", version: "1.0.0" });
469
+ server.registerTool(
470
+ "ask-name",
471
+ { inputSchema: z.object({}) },
472
+ async (_args, context) => {
473
+ const answer = acceptedContent(
474
+ context.mcpReq.inputResponses,
475
+ "name",
476
+ z.object({ name: z.string() })
477
+ );
478
+ if (!answer) {
479
+ return inputRequired({
480
+ inputRequests: {
481
+ name: inputRequired.elicit({
482
+ message: "What is your name?",
483
+ requestedSchema: {
484
+ type: "object",
485
+ properties: { name: { type: "string" } },
486
+ required: ["name"]
487
+ }
488
+ })
489
+ }
510
490
  });
511
-
512
- return {
513
- content: [
514
- {
515
- type: "text",
516
- text: `Counter increased by ${amount}, now at ${this.state.counter}`
517
- }
518
- ]
519
- };
520
491
  }
492
+ return { content: [{ type: "text", text: `Hello ${answer.name}` }] };
493
+ }
494
+ );
495
+ return server;
496
+ }
497
+
498
+ export default {
499
+ fetch(request, env, ctx) {
500
+ return createMcpHandler(createServer, { legacy: "reject" })(
501
+ request,
502
+ env,
503
+ ctx
521
504
  );
522
505
  }
523
- }
506
+ } satisfies ExportedHandler;
524
507
  ```
525
508
 
526
- **Key differences from other patterns:**
509
+ The MCP client renders the JSON Schema form and the original operation remains pending from the application's perspective while the SDK completes the rounds.
527
510
 
528
- - Used by **MCP servers** exposing tools to clients, not agents calling tools
529
- - Uses **JSON Schema** for structured form-based input
530
- - The **MCP client** (Claude Desktop, etc.) handles UI rendering
531
- - Returns `{ action: "accept" | "decline", content: {...} }`
511
+ See the [Stateless Elicitation example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr).
532
512
 
533
- See the complete example: [examples/mcp-elicitation/](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation)
513
+ **Legacy Elicitation** in existing Legacy deployments uses pushed `elicitation/create` requests over a stateful transport. See the explicitly legacy [examples/mcp-elicitation/](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) example when retaining `McpAgent` or `createLegacyMcpHandler` with `WorkerTransport`.
534
514
 
535
515
  ## State Patterns for Approvals
536
516
 
@@ -647,15 +627,16 @@ async submitForApproval(request: ApprovalRequest): Promise<string> {
647
627
 
648
628
  ## Complete Examples
649
629
 
650
- | Pattern | Location | Description |
651
- | ----------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------- |
652
- | Workflow approval | [examples/workflows/](https://github.com/cloudflare/agents/tree/main/examples/workflows) | Multi-step task processing with approval gate |
653
- | AIChatAgent tools | [guides/human-in-the-loop/](https://github.com/cloudflare/agents/tree/main/guides/human-in-the-loop) | Chat tool approval with needsApproval + onToolCall |
654
- | OpenAI Agents SDK | [openai-sdk/human-in-the-loop/](https://github.com/cloudflare/agents/tree/main/openai-sdk/human-in-the-loop) | Conditional tool approval with modal |
655
- | MCP Elicitation | [examples/mcp-elicitation/](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) | MCP server requesting structured user input |
630
+ | Pattern | Location | Description |
631
+ | --------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
632
+ | Workflow approval | [examples/workflows/](https://github.com/cloudflare/agents/tree/main/examples/workflows) | Multi-step task processing with approval gate |
633
+ | AIChatAgent tools | [guides/human-in-the-loop/](https://github.com/cloudflare/agents/tree/main/guides/human-in-the-loop) | Chat tool approval with needsApproval + onToolCall |
634
+ | OpenAI Agents SDK | [openai-sdk/human-in-the-loop/](https://github.com/cloudflare/agents/tree/main/openai-sdk/human-in-the-loop) | Conditional tool approval with modal |
635
+ | Stateless Elicitation | [examples/mcp-elicitation-mrtr/](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) | Stateless multi-round input |
636
+ | Legacy Elicitation | [examples/mcp-elicitation/](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) | Stateful pushed input requests |
656
637
 
657
638
  For detailed API documentation, see:
658
639
 
659
640
  - [Workflows](./workflows.md) - `waitForApproval()`, `approveWorkflow()`, `rejectWorkflow()`
660
- - [MCP Servers](./mcp-servers.md) - `elicitInput()` for MCP elicitation
641
+ - [MCP Servers](./mcp-servers.md) - `inputRequired()` and legacy `elicitInput()`
661
642
  - [Callable Methods](./callable-methods.md) - `@callable()` decorator for approval endpoints
@@ -15,6 +15,12 @@ The MCP client capability lets your agent:
15
15
 
16
16
  ## Quick Start
17
17
 
18
+ Install the exact MCP client peer used by this Agents release:
19
+
20
+ ```sh
21
+ pnpm add agents @modelcontextprotocol/client@2.0.0
22
+ ```
23
+
18
24
  ```typescript
19
25
  import { Agent } from "agents";
20
26
 
@@ -95,6 +101,20 @@ await this.addMcpServer("internal", "https://internal-mcp.example.com/mcp", {
95
101
  });
96
102
  ```
97
103
 
104
+ ### Legacy OAuth metadata compatibility
105
+
106
+ SDK v2 validates authorization-server metadata issuers by default. A trusted Legacy server with known mismatched RFC 8414 metadata can opt out explicitly:
107
+
108
+ ```typescript
109
+ await this.addMcpServer("legacy", "https://legacy.example.com/mcp", {
110
+ transport: {
111
+ skipIssuerMetadataValidation: true
112
+ }
113
+ });
114
+ ```
115
+
116
+ This weakens OAuth mix-up protection. Do not enable it for unknown servers or as a general fallback.
117
+
98
118
  ### Retry Options
99
119
 
100
120
  Configure retry behavior for connection and reconnection attempts:
@@ -111,9 +131,13 @@ await this.addMcpServer("github", "https://mcp.github.com/mcp", {
111
131
 
112
132
  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
133
 
114
- ### Elicitation
134
+ ### Protocol negotiation and elicitation
135
+
136
+ Agents uses the MCP v2 client and automatically negotiates the protocol version for every connection. It uses `server/discover` with Stateless servers and falls back to the `initialize` handshake on the same connection for Legacy Streamable HTTP, SSE, and RPC servers.
115
137
 
116
- MCP servers can request input from the client during a tool call (`elicitation/create`). To respond, configure an elicitation handler before MCP connections are registered or restored:
138
+ MCP servers can request input from the client during `callTool`, `getPrompt`, or `readResource`. Stateless Elicitation returns an `input_required` result and completes through MRTR: the MCP SDK invokes the configured `elicitation/create` handler, sends its response, and continues the original request. Agents does not expose an intermediate continuation; the original call stays pending while human input is collected and eventually resolves to the ordinary tool, prompt, or resource result. Legacy Elicitation on Legacy servers continues to use pushed `elicitation/create` requests. Both generations share the same handlers.
139
+
140
+ Configure elicitation handlers before MCP connections are registered or restored:
117
141
 
118
142
  ```typescript
119
143
  import { Agent } from "agents";
@@ -136,6 +160,8 @@ class MyAgent extends Agent<Env> {
136
160
 
137
161
  The advertised modes are persisted with each MCP server, so a connection restored from storage after hibernation re-advertises the same modes at the handshake; the handlers themselves re-attach when `onStart()` runs. Configuring a handler after an MCP connection is already active updates the in-memory handler, but the server only sees new advertised elicitation modes after that connection reconnects.
138
162
 
163
+ Handlers and pending calls are memory-only. A Durable Object hibernation or isolate restart does not preserve an in-flight interactive call; callers must retry it after the connection is restored. Agents intentionally does not persist in-flight Stateless `requestState` or implement a manual/resumable continuation layer.
164
+
139
165
  Connections advertise only the elicitation modes with configured handlers at the `initialize` handshake: configure `form` to advertise form-mode elicitation, `url` to advertise url-mode elicitation (MCP spec 2025-11-25 — url mode is used for sensitive flows like OAuth URLs), or both to advertise both modes. Without handlers, connections advertise no elicitation capability, so spec-compliant servers use their non-elicitation fallbacks instead of sending requests the agent cannot answer.
140
166
 
141
167
  To override the advertised modes, declare them explicitly — an explicit declaration always wins and is persisted with the server options, surviving hibernation:
@@ -416,7 +442,7 @@ function Dashboard() {
416
442
  onMcpUpdate: (mcpState) => {
417
443
  setTools(mcpState.tools);
418
444
  setServers(mcpState.servers);
419
- }
445
+ },
420
446
  });
421
447
 
422
448
  return (
@@ -429,10 +455,8 @@ function Dashboard() {
429
455
  ))}
430
456
 
431
457
  <h2>Available Tools ({tools.length})</h2>
432
- {tools.map(tool => (
433
- <div key={`${tool.serverId}-${tool.name}`}>
434
- {tool.name}
435
- </div>
458
+ {tools.map((tool) => (
459
+ <div key={`${tool.serverId}-${tool.name}`}>{tool.name}</div>
436
460
  ))}
437
461
  </div>
438
462
  );
@@ -4,23 +4,27 @@ This guide covers the different ways to create MCP servers with the Agents SDK a
4
4
 
5
5
  ## Choosing an Approach
6
6
 
7
- | Approach | Stateful? | Requires Durable Objects? | Best for |
8
- | ---------------------------------------------- | --------- | ------------------------- | ---------------------------------------------- |
9
- | `createMcpHandler()` | No | No | Stateless tools, simplest setup |
10
- | `McpAgent` | Yes | Yes | Stateful tools, per-session state, elicitation |
11
- | Raw `WebStandardStreamableHTTPServerTransport` | No | No | Full control, no SDK dependency |
7
+ | Approach | Stateful? | Requires Durable Objects? | Best for |
8
+ | ---------------------------------------------- | --------- | ------------------------- | ------------------------------------------------- |
9
+ | `createMcpHandler()` | No | No | New servers and the draft MCP protocol |
10
+ | `McpAgent` (deprecated legacy path) | Yes | Yes | Existing stateful SDK v1 deployments |
11
+ | Raw `WebStandardStreamableHTTPServerTransport` | No | No | Low-level control without the Agents HTTP wrapper |
12
12
 
13
- - **`createMcpHandler()`** is the fastest way to get a stateless MCP server running. Use it when your tools do not need per-session state.
14
- - **`McpAgent`** gives you a Durable Object per session with built-in state management, elicitation support, and both SSE and Streamable HTTP transports.
15
- - **Raw transport** gives you full control if you want to use the `@modelcontextprotocol/sdk` directly without the Agents SDK helpers.
13
+ - **`createMcpHandler()`** is the current server-development path. It serves draft MCP `2026-07-28` and supports stateless published 2025 clients by default.
14
+ - **`McpAgent`** is a retained, feature-frozen SDK v1 path for existing stateful deployments. New servers should use `createMcpHandler()`.
15
+ - **Raw transport** gives you low-level control if the standard handler lifecycle is not suitable.
16
16
 
17
17
  ## Stateless MCP Server with `createMcpHandler()`
18
18
 
19
- The simplest way to create an MCP server. No Durable Objects or bindings required:
19
+ The simplest way to create an MCP server. Install the exact SDK v2 server peer, then use the isolated server entry point so legacy Agents transports and MCP clients stay out of your Worker bundle:
20
+
21
+ ```sh
22
+ pnpm add agents @modelcontextprotocol/server@2.0.0 zod
23
+ ```
20
24
 
21
25
  ```typescript
22
- import { createMcpHandler } from "agents/mcp";
23
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
26
+ import { McpServer } from "@modelcontextprotocol/server";
27
+ import { createMcpHandler } from "agents/mcp/server";
24
28
  import { z } from "zod";
25
29
 
26
30
  function createServer() {
@@ -44,45 +48,77 @@ function createServer() {
44
48
  }
45
49
 
46
50
  export default {
47
- fetch: async (request: Request, env: Env, ctx: ExecutionContext) => {
48
- const server = createServer();
49
- return createMcpHandler(server)(request, env, ctx);
51
+ fetch(request, env, ctx) {
52
+ return createMcpHandler(createServer)(request, env, ctx);
50
53
  }
51
- };
54
+ } satisfies ExportedHandler;
52
55
  ```
53
56
 
54
- > **Important:** Create a new `McpServer` instance per request. The MCP SDK does not allow connecting an already-connected server to a new transport.
57
+ `createMcpHandler` requires a factory so concurrent Worker requests receive isolated server instances. A function input is always treated as an SDK v2 factory.
55
58
 
56
- ### `createMcpHandler` Options
59
+ ### `createMcpHandler` options
57
60
 
58
61
  ```typescript
59
- createMcpHandler(server, {
60
- route: "/mcp", // path to handle (default: "/mcp")
61
- enableJsonResponse: true, // use JSON responses instead of SSE streaming
62
- sessionIdGenerator: () => crypto.randomUUID(),
63
- corsOptions: { ... }, // CORS configuration
64
- authContext: { props: {} }, // manually set auth context
65
- transport: workerTransport // provide your own WorkerTransport instance
62
+ createMcpHandler(() => createServer(), {
63
+ route: "/mcp", // exact path to handle (default: "/mcp")
64
+ corsOptions: {
65
+ origin: "https://app.example.com"
66
+ },
67
+ allowedHostnames: ["mcp.example.com"], // custom-domain Host policy
68
+ allowedOriginHostnames: ["app.example.com"], // browser Origin policy
69
+ authContext: { props: {} }, // optional application props override
70
+ legacy: "stateless", // upstream default; use "reject" for Stateless-only
71
+ responseMode: "auto" // upstream SDK response shaping
66
72
  });
67
73
  ```
68
74
 
75
+ All upstream SDK v2 handler options pass through. Use `createLegacyMcpHandler` for WorkerTransport, storage, session, and event-store options.
76
+
77
+ The handler validates every present `Origin` header before serving the request. It rejects malformed, opaque, and non-HTTP origins. Requests without `Origin` remain valid for non-browser MCP clients.
78
+
79
+ Its default allowlist includes localhost-class origins and the endpoint's `workers.dev` hostname. A concrete `corsOptions.origin` adds that hostname automatically. The handler also applies matching Host checks to localhost and `workers.dev` endpoints. For a custom domain with wildcard CORS, configure `allowedHostnames` and `allowedOriginHostnames` explicitly. Values are hostnames without a scheme or port, and matching ignores the Origin's scheme and port.
80
+
81
+ Pass `allowedOriginHostnames: "*"` only when equivalent Origin validation runs in trusted middleware before the handler. This explicitly disables the handler's Origin check, including malformed and opaque Origin rejection. MCP HTTP servers are required to validate browser Origins, so do not use this as an unauthenticated public-server shortcut.
82
+
83
+ ### 2025 compatibility and elicitation
84
+
85
+ The default `legacy: "stateless"` lane supports ordinary 2025 tools, resources, and prompts. It has no session return path for push-style server-to-client requests; attempts to sample, elicit, or list roots fail immediately with guidance to use a sessionful transport.
86
+
87
+ Applications that must serve both generations can route before `createMcpHandler`: send Stateless requests to a strict handler and Legacy requests to an existing session-addressed Agent or transport. See [`examples/mcp-elicitation-mrtr`](../../examples/mcp-elicitation-mrtr/) for Stateless Elicitation and [`examples/mcp-elicitation`](../../examples/mcp-elicitation/) for Legacy Elicitation.
88
+
89
+ For an explicit Legacy handler, use the retained Legacy API:
90
+
91
+ ```typescript
92
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
93
+ import { createLegacyMcpHandler } from "agents/mcp";
94
+
95
+ const server = new McpServer({ name: "legacy", version: "1.0.0" });
96
+ export default createLegacyMcpHandler(server);
97
+ ```
98
+
99
+ Passing an SDK v1 server directly to `createMcpHandler` still forwards to this API for compatibility, but that overload is deprecated, emits a migration warning, and is removed in the next major release. `createLegacyMcpHandler` and `WorkerTransport` themselves are not deprecated.
100
+
69
101
  ### Accessing Authenticated User Context
70
102
 
71
- When your MCP server is wrapped with `OAuthProvider` from `@cloudflare/workers-oauth-provider`, authenticated user information is available inside tools via `getMcpAuthContext()`:
103
+ When your MCP server is wrapped with `OAuthProvider` from `@cloudflare/workers-oauth-provider`, provider-issued tokens are available through standard SDK v2 `AuthInfo`. The existing `getMcpAuthContext()` application-props helper remains supported:
72
104
 
73
105
  ```typescript
74
- import { createMcpHandler, getMcpAuthContext } from "agents/mcp";
106
+ import { createMcpHandler, getMcpAuthContext } from "agents/mcp/server";
75
107
 
76
108
  server.registerTool(
77
109
  "whoami",
78
110
  { description: "Returns the authenticated user" },
79
- async () => {
111
+ async (context) => {
80
112
  const auth = getMcpAuthContext();
81
113
  return {
82
114
  content: [
83
115
  {
84
116
  type: "text",
85
- text: auth ? JSON.stringify(auth.props) : "Not authenticated"
117
+ text: JSON.stringify({
118
+ clientId: context.http?.authInfo?.clientId,
119
+ scopes: context.http?.authInfo?.scopes,
120
+ props: auth?.props
121
+ })
86
122
  }
87
123
  ]
88
124
  };
@@ -90,11 +126,11 @@ server.registerTool(
90
126
  );
91
127
  ```
92
128
 
93
- The `OAuthProvider` sets `ctx.props` on the execution context, which `createMcpHandler` automatically picks up and makes available via `getMcpAuthContext()`.
129
+ Do not log or return `context.http.authInfo.token`. External-token resolvers continue providing `getMcpAuthContext().props` but do not synthesize incomplete standard metadata.
94
130
 
95
- ## Stateful MCP Server with `McpAgent`
131
+ ## Stateful MCP Server with `McpAgent` (legacy)
96
132
 
97
- `McpAgent` gives each client session its own Durable Object with persistent state. Use this when your tools need to track per-session data.
133
+ `McpAgent` gives each client session its own Durable Object with persistent state. It remains available for existing SDK v1 deployments but is deprecated and feature-frozen; new servers should use the stateless handler or explicitly compose a separate legacy route where a session is required.
98
134
 
99
135
  ### Writing TinyMCP
100
136
 
@@ -428,76 +464,77 @@ The `jurisdiction` option accepts any value supported by [Cloudflare's Durable O
428
464
 
429
465
  ## Elicitation (Human-in-the-Loop)
430
466
 
431
- MCP servers can request additional input from the user during a tool call using elicitation. This is useful for confirmation dialogs, requesting amounts, or any interactive tool flow.
467
+ MCP servers can request additional input during a tool call. The implementation differs by protocol generation.
468
+
469
+ ### Stateless Elicitation
432
470
 
433
- Elicitation is supported via `McpAgent` (which manages the request/response lifecycle through Durable Object storage) or via `WorkerTransport` (for stateful non-McpAgent setups).
471
+ For Stateless Elicitation, return `inputRequired(...)`. The client gathers the input and retries the request with SDK-managed `requestState` and `inputResponses`; the Worker does not suspend while a person responds.
434
472
 
435
473
  ```typescript
436
- import { McpAgent } from "agents/mcp";
437
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
474
+ import {
475
+ McpServer,
476
+ acceptedContent,
477
+ inputRequired
478
+ } from "@modelcontextprotocol/server";
479
+ import { createMcpHandler } from "agents/mcp/server";
438
480
  import { z } from "zod";
439
481
 
440
- export class MyMCP extends McpAgent<Env, { counter: number }> {
441
- server = new McpServer({ name: "Elicitation Demo", version: "1.0.0" });
442
-
443
- initialState = { counter: 0 };
444
-
445
- async init() {
446
- this.server.registerTool(
447
- "increase-counter",
448
- {
449
- description: "Increase the counter",
450
- inputSchema: {
451
- confirm: z.boolean().describe("Do you want to increase the counter?")
452
- }
453
- },
454
- async ({ confirm }, extra) => {
455
- if (!confirm) {
456
- return { content: [{ type: "text", text: "Cancelled." }] };
457
- }
458
-
459
- const result = await this.server.server.elicitInput(
460
- {
461
- message: "By how much?",
462
- requestedSchema: {
463
- type: "object",
464
- properties: {
465
- amount: { type: "number", title: "Amount" }
466
- },
467
- required: ["amount"]
468
- }
469
- },
470
- { relatedRequestId: extra.requestId }
471
- );
472
-
473
- if (result.action !== "accept" || !result.content?.amount) {
474
- return { content: [{ type: "text", text: "Cancelled." }] };
475
- }
476
-
477
- const amount = Number(result.content.amount);
478
- this.setState({ counter: this.state.counter + amount });
479
-
480
- return {
481
- content: [
482
- {
483
- type: "text",
484
- text: `Counter increased by ${amount}, now ${this.state.counter}`
485
- }
486
- ]
487
- };
482
+ function createServer() {
483
+ const server = new McpServer({
484
+ name: "Stateless Elicitation Demo",
485
+ version: "1.0.0"
486
+ });
487
+ server.registerTool(
488
+ "ask-name",
489
+ { inputSchema: z.object({}) },
490
+ async (_args, context) => {
491
+ const answer = acceptedContent(
492
+ context.mcpReq.inputResponses,
493
+ "name",
494
+ z.object({ name: z.string() })
495
+ );
496
+ if (!answer) {
497
+ return inputRequired({
498
+ inputRequests: {
499
+ name: inputRequired.elicit({
500
+ message: "What is your name?",
501
+ requestedSchema: {
502
+ type: "object",
503
+ properties: { name: { type: "string" } },
504
+ required: ["name"]
505
+ }
506
+ })
507
+ }
508
+ });
488
509
  }
489
- );
490
- }
510
+ return { content: [{ type: "text", text: `Hello ${answer.name}` }] };
511
+ }
512
+ );
513
+ return server;
491
514
  }
492
515
 
493
- export default MyMCP.serve("/mcp");
516
+ export default {
517
+ fetch(request, env, ctx) {
518
+ return createMcpHandler(createServer, { legacy: "reject" })(
519
+ request,
520
+ env,
521
+ ctx
522
+ );
523
+ }
524
+ } satisfies ExportedHandler;
494
525
  ```
495
526
 
496
- See the [`examples/mcp-elicitation`](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) example for a full working demo.
527
+ See [`examples/mcp-elicitation-mrtr`](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) for a two-round Stateless Elicitation example.
528
+
529
+ ### Legacy Elicitation
530
+
531
+ Existing Legacy deployments send pushed `elicitation/create` requests over a session-addressed response stream. Use `McpAgent` or `createLegacyMcpHandler` with `WorkerTransport` when that behavior must be retained.
532
+
533
+ See [`examples/mcp-elicitation`](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) for form- and URL-mode Legacy Elicitation with Durable Object state and SSE replay.
497
534
 
498
535
  ## WorkerTransport
499
536
 
500
- `WorkerTransport` is a server-side transport for running MCP servers in stateless Workers while optionally persisting session state. It is used internally by `createMcpHandler()` but can also be used directly for advanced scenarios like stateful sessions without `McpAgent`.
537
+ `WorkerTransport` is the retained Agents transport for stateful Legacy deployments. Use it with `createLegacyMcpHandler` for persistent sessions, storage, event replay, and other explicit legacy configurations. The Stateless handler's Legacy compatibility lane uses the SDK v2 web-standard transport instead and does not import `WorkerTransport`.
501
538
 
502
539
  ```typescript
503
540
  import { WorkerTransport, type TransportState } from "agents/mcp";
@@ -466,8 +466,9 @@ exists:
466
466
  | `cloudflare.agents.runtime_context.{key}` | Explicitly included scalar runtime context |
467
467
  | `cloudflare.agents.tool_context.{tool}.{key}` | Explicitly included scalar context on the executed tool span |
468
468
  | `cloudflare.agents.metadata.{key}` | Other scalar v6 telemetry metadata |
469
- | `cloudflare.agents.turn.{request_id,trigger,admission,channel,continuation,generation}` | Think turn context |
469
+ | `cloudflare.agents.turn.{request_id,trigger,admission,channel,continuation,generation}` | Think turn context, from v6 metadata or v7 runtime context |
470
470
  | `cloudflare.agents.canceled` | Recognized cancellation, not a failure |
471
+ | `cloudflare.agents.span.truncated` | Span closed by its invocation ending before the work finished |
471
472
 
472
473
  `gen_ai.response.finish_reasons` and `gen_ai.request.stop_sequences` are arrays
473
474
  in OTel. Workers' custom `Span.setAttribute` currently accepts only a string,