Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions docs/agents/get-current-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,10 @@ context:

```typescript
import { RpcTarget } from "cloudflare:workers";
import { Agent, getCurrentAgent } from "agents";

class HostCallbackBridge extends RpcTarget {
constructor(private agent: MyMcpAgent) {
constructor(private agent: MyAgent) {
super();
}

Expand All @@ -155,9 +156,9 @@ class HostCallbackBridge extends RpcTarget {
}
}

export class MyMcpAgent extends McpAgent {
export class MyAgent extends Agent {
async handleSandboxCallback() {
const { agent } = getCurrentAgent<MyMcpAgent>();
const { agent } = getCurrentAgent<MyAgent>();
// `agent` is available again.
}
}
Expand All @@ -166,10 +167,6 @@ export class MyMcpAgent extends McpAgent {
Context restored this way has `connection`, `request`, and `email` unset. It
is not tied to live client I/O.

Server-initiated MCP requests (`elicitInput`, `createMessage`, and `listRoots`)
on `McpAgent` do not require this indirection because the MCP transport retains
its owning agent.

## API reference

The agents package exports one main function for context management:
Expand Down
10 changes: 3 additions & 7 deletions docs/agents/human-in-the-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,14 @@ Agents SDK supports multiple human-in-the-loop patterns. Choose based on your us
| 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) |
| Client-side tools | `onToolCall` | Tools that need browser APIs or user interaction | Pattern below |
| 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) |
| Legacy servers | Legacy Elicitation | Existing sessionful MCP deployments | [examples/mcp-elicitation/](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) |

### Decision Guide

```
Is this part of a multi-step workflow?
├── Yes → Use Workflow Approval (waitForApproval)
└── No → Are you building an MCP server?
├── Yes → Use MCP Elicitation (elicitInput)
├── Yes → Use MCP Elicitation (inputRequired)
└── No → Is this an AI chat interaction?
├── Yes → Does the tool need browser APIs?
│ ├── Yes → Use onToolCall (client-side execution)
Expand Down Expand Up @@ -460,7 +459,7 @@ See the complete example: [openai-sdk/human-in-the-loop/](https://github.com/clo

### MCP Elicitation

**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.
MCP servers built with `createMcpHandler` use multi-round-trip requests (MRTR) for elicitation. A handler returns `inputRequired(...)`; the client gathers the requested input and retries with SDK-managed state. No Worker remains suspended while the user responds.

```typescript
import {
Expand Down Expand Up @@ -517,8 +516,6 @@ The MCP client renders the JSON Schema form and the original operation remains p

See the [Stateless Elicitation example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr).

**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`.

## State Patterns for Approvals

Track pending approvals in agent state for UI rendering and persistence:
Expand Down Expand Up @@ -640,10 +637,9 @@ async submitForApproval(request: ApprovalRequest): Promise<string> {
| 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 |
| 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 |
| Stateless Elicitation | [examples/mcp-elicitation-mrtr/](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) | Stateless multi-round input |
| Legacy Elicitation | [examples/mcp-elicitation/](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) | Stateful pushed input requests |

For detailed API documentation, see:

- [Workflows](./workflows.md) - `waitForApproval()`, `approveWorkflow()`, `rejectWorkflow()`
- [MCP Servers](./mcp-servers.md) - `inputRequired()` and legacy `elicitInput()`
- [MCP Servers](./mcp-servers.md) - `inputRequired()` elicitation
- [Callable Methods](./callable-methods.md) - `@callable()` decorator for approval endpoints
4 changes: 2 additions & 2 deletions docs/agents/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,10 @@ Building your own harness? Sessions stores the conversation, Streams holds durab

## MCP (Model Context Protocol)

- [Creating MCP Servers](./mcp-servers.md) - Build MCP servers with `McpAgent`
- [Creating MCP Servers](./mcp-servers.md) - Build stateless MCP servers with `createMcpHandler`
- [Securing MCP Servers](./securing-mcp-servers.md) - OAuth and authentication for MCP
- [Connecting to MCP Servers](./mcp-client.md) - `MCPClientManager` on a Durable Object, or `addMcpServer()` on an Agent
- [MCP Transports](./mcp-transports.md) - Transport options: Streamable HTTP, SSE, and RPC
- [MCP Transports](./mcp-transports.md) - Streamable HTTP on the server, and the transports an Agent can connect with

## Operations

Expand Down
30 changes: 2 additions & 28 deletions docs/agents/mcp-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,6 @@ callback URL to `registerServer()` and route that request to the same named
Durable Object. The manager persists the URL and only intercepts a callback
whose origin and pathname match it.

An HTTP-only manager does not need `env`. Pass the Durable Object environment
when the catalog can contain RPC servers so persisted binding names can be
resolved after a wake:

```ts
readonly mcp = new MCPClientManager("my-object", "1.0.0", {
env: this.env
});
```

If an RPC row exists without `env`, startup logs a warning identifying the
server and does not recreate that connection.

`Agent` installs this same capability object directly. Existing `this.mcp`,
`addMcpServer()`, `removeMcpServer()`, and `getMcpServers()` APIs remain
available.
Expand Down Expand Up @@ -188,7 +175,7 @@ These options are persisted and used when reconnecting after hibernation or afte

### Protocol negotiation and elicitation

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.
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 and SSE servers.

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.

Expand Down Expand Up @@ -243,8 +230,6 @@ MCP server URLs are validated before connection to prevent Server-Side Request F

Loopback development URLs such as `localhost`, `127.0.0.1`, and `::1` are allowed.

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.

### Return Value

`addMcpServer()` returns the connection state:
Expand Down Expand Up @@ -666,17 +651,6 @@ async addMcpServer(
| { id: string; state: "authenticating"; authUrl: string }
>

// RPC transport (Durable Object binding — no HTTP overhead)
async addMcpServer(
name: string,
binding: DurableObjectNamespace,
options?: {
props?: Record<string, unknown>; // passed to the McpAgent's onStart(props)
client?: ClientOptions;
retry?: RetryOptions;
}
): Promise<{ id: string; state: "ready" }>

// Legacy signature (still supported)
async addMcpServer(
name: string,
Expand All @@ -689,7 +663,7 @@ async addMcpServer(

Add and connect to an MCP server. Throws if connection or discovery fails.

`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.
`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). See [MCP Transports](./mcp-transports.md) for the transport options.

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.

Expand Down
Loading
Loading