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.
- package/README.md +24 -19
- package/dist/{agent-tool-types-BNUGGBzQ.d.ts → agent-tool-types-BC-WFlsz.d.ts} +1026 -400
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-BFbzVLFc.d.ts → agent-tools-DeHe9Xov.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/browser/ai.js +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/chat/index.d.ts +2 -2
- package/dist/chat-sdk/index.d.ts +1 -1
- package/dist/client-invoker-BNSZxAkv.d.ts +20 -0
- package/dist/client-invoker-VNZ7X0nn.js +57 -0
- package/dist/client-invoker-VNZ7X0nn.js.map +1 -0
- package/dist/{client-CcjiFpTf.js → client-zqKcsyFa.js} +434 -168
- package/dist/client-zqKcsyFa.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/cloudflare-BduZwmYK.js +204 -0
- package/dist/cloudflare-BduZwmYK.js.map +1 -0
- package/dist/{connector-CdldGF3h.js → connector-KEJnl6e5.js} +2 -2
- package/dist/connector-KEJnl6e5.js.map +1 -0
- package/dist/{do-oauth-client-provider-D4ZwyBDu.d.ts → do-oauth-client-provider-VTZj2VtM.d.ts} +23 -11
- package/dist/experimental/webmcp.js +1 -1
- package/dist/handler-stateless-CIkKPETH.js +318 -0
- package/dist/handler-stateless-CIkKPETH.js.map +1 -0
- package/dist/handler-stateless-C_bo-Ytq.d.ts +107 -0
- package/dist/index.d.ts +14 -12
- package/dist/index.js +30 -20
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +22 -18
- package/dist/mcp/client.js +1 -1
- package/dist/mcp/do-oauth-client-provider.d.ts +1 -1
- package/dist/mcp/do-oauth-client-provider.js +25 -12
- package/dist/mcp/do-oauth-client-provider.js.map +1 -1
- package/dist/mcp/index.d.ts +48 -34
- package/dist/mcp/index.js +95 -156
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/server.d.ts +17 -0
- package/dist/mcp/server.js +2 -0
- package/dist/mcp/x402.d.ts +21 -9
- package/dist/mcp/x402.js +8 -7
- package/dist/mcp/x402.js.map +1 -1
- package/dist/observability/ai/index.d.ts +9 -1
- package/dist/observability/ai/index.js +188 -63
- package/dist/observability/ai/index.js.map +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/sub-routing.d.ts +6 -6
- package/dist/workflows.d.ts +1 -1
- package/docs/human-in-the-loop.md +63 -82
- package/docs/mcp-client.md +31 -7
- package/docs/mcp-servers.md +125 -88
- package/docs/observability.md +2 -1
- package/docs/securing-mcp-servers.md +9 -6
- package/package.json +28 -7
- package/dist/client-CcjiFpTf.js.map +0 -1
- package/dist/cloudflare-BldFV0Pa.js +0 -117
- package/dist/cloudflare-BldFV0Pa.js.map +0 -1
- package/dist/connector-CdldGF3h.js.map +0 -1
package/dist/react.d.ts
CHANGED
package/dist/serializable.d.ts
CHANGED
package/dist/sub-routing.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
ln as
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from "./agent-tool-types-
|
|
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,
|
package/dist/workflows.d.ts
CHANGED
|
@@ -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
|
|
29
|
-
| ---------------------- |
|
|
30
|
-
| Long-running workflows | Workflow Approval
|
|
31
|
-
| AIChatAgent tools | `needsApproval`
|
|
32
|
-
| OpenAI Agents SDK | `needsApproval`
|
|
33
|
-
| Client-side tools | `onToolCall`
|
|
34
|
-
|
|
|
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
|
-
|
|
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 {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
651
|
-
|
|
|
652
|
-
| Workflow approval
|
|
653
|
-
| AIChatAgent tools
|
|
654
|
-
| OpenAI Agents SDK
|
|
655
|
-
|
|
|
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) - `
|
|
641
|
+
- [MCP Servers](./mcp-servers.md) - `inputRequired()` and legacy `elicitInput()`
|
|
661
642
|
- [Callable Methods](./callable-methods.md) - `@callable()` decorator for approval endpoints
|
package/docs/mcp-client.md
CHANGED
|
@@ -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
|
-
###
|
|
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
|
|
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
|
);
|
package/docs/mcp-servers.md
CHANGED
|
@@ -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 |
|
|
10
|
-
| `McpAgent`
|
|
11
|
-
| Raw `WebStandardStreamableHTTPServerTransport` | No | No |
|
|
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
|
|
14
|
-
- **`McpAgent`**
|
|
15
|
-
- **Raw transport** gives you
|
|
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.
|
|
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 {
|
|
23
|
-
import {
|
|
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
|
|
48
|
-
|
|
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
|
-
|
|
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`
|
|
59
|
+
### `createMcpHandler` options
|
|
57
60
|
|
|
58
61
|
```typescript
|
|
59
|
-
createMcpHandler(
|
|
60
|
-
route: "/mcp",
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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`,
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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
|
|
467
|
+
MCP servers can request additional input during a tool call. The implementation differs by protocol generation.
|
|
468
|
+
|
|
469
|
+
### Stateless Elicitation
|
|
432
470
|
|
|
433
|
-
|
|
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 {
|
|
437
|
-
|
|
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
|
-
|
|
441
|
-
server = new McpServer({
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
|
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
|
|
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
|
|
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";
|
package/docs/observability.md
CHANGED
|
@@ -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,
|