agents 0.29.0 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/browser/tools/pi.d.ts +6 -2
- package/dist/browser/tools/pi.js +6 -2
- package/dist/browser/tools/pi.js.map +1 -1
- package/dist/chat-sdk/index.js +1 -1
- package/dist/cli.js +46 -45
- package/dist/experimental/channels/web/index.js +1 -1
- package/dist/harness/container/index.js +1 -1
- package/dist/harness/opencode/index.js +1 -1
- package/dist/harness/pi/index.d.ts +60 -35
- package/dist/harness/pi/index.js +271 -119
- package/dist/harness/pi/index.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/lifecycle/index.js +1 -1
- package/dist/{lifecycle-ClT4DmF2.js → lifecycle-v1rKHV2C.js} +57 -17
- package/dist/lifecycle-v1rKHV2C.js.map +1 -0
- package/dist/mcp/index.js +1 -1
- package/dist/models/pi-ai/index.js +2 -2
- package/dist/models/pi-ai/index.js.map +1 -1
- package/dist/{src-6lr7MtM_.js → src-DfLzdwlK.js} +3 -3
- package/dist/{src-6lr7MtM_.js.map → src-DfLzdwlK.js.map} +1 -1
- package/dist/web/fetch/tools/pi.d.ts +10 -6
- package/dist/web/fetch/tools/pi.js +10 -6
- package/dist/web/fetch/tools/pi.js.map +1 -1
- package/dist/web/search/tools/pi.d.ts +10 -6
- package/dist/web/search/tools/pi.js +10 -6
- package/dist/web/search/tools/pi.js.map +1 -1
- package/dist/websockets/index.js +1 -1
- package/dist/{websockets-CqxnntmE.js → websockets-CyoeosdO.js} +2 -2
- package/dist/{websockets-CqxnntmE.js.map → websockets-CyoeosdO.js.map} +1 -1
- package/dist/workflows.js +1 -1
- package/docs/get-current-agent.md +4 -7
- package/docs/harnesses/pi.md +62 -28
- package/docs/human-in-the-loop.md +3 -7
- package/docs/index.md +2 -2
- package/docs/lifecycle.md +7 -1
- package/docs/mcp-client.md +2 -28
- package/docs/mcp-servers.md +121 -414
- package/docs/mcp-transports.md +30 -289
- package/docs/models-pi-ai.md +5 -1
- package/docs/routing.md +0 -2
- package/docs/securing-mcp-servers.md +7 -2
- package/package.json +11 -6
- package/dist/lifecycle-ClT4DmF2.js.map +0 -1
package/docs/harnesses/pi.md
CHANGED
|
@@ -11,10 +11,10 @@ Pi owns everything about a run: the transcript, the inbox of steers and follow-u
|
|
|
11
11
|
## Install the Pi packages
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
npm install agents @earendil-works/pi-durable @earendil-works/pi-ai
|
|
14
|
+
npm install agents @earendil-works/pi-durable @earendil-works/pi-ai @earendil-works/chord
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
The three Pi packages are optional peer dependencies of `agents`, at `~1.4.0`. pi-durable is experimental, and its API can change in minor releases, so `agents` supports one Pi minor at a time. The examples use Workers AI through the `AI` binding. To configure the binding and choose other models, refer to [Models for pi-ai](../models-pi-ai.md).
|
|
18
18
|
|
|
19
19
|
## Create the harness
|
|
20
20
|
|
|
@@ -27,20 +27,17 @@ import { Agent } from "agents";
|
|
|
27
27
|
import { PiHarness } from "agents/harness/pi";
|
|
28
28
|
import { createAI } from "agents/models/pi-ai";
|
|
29
29
|
|
|
30
|
-
// Skip Pi's idle caches (refer to the note below).
|
|
31
|
-
const settings = { contextRetentionMs: 0, documentRetentionMs: 0 };
|
|
32
|
-
|
|
33
30
|
export class Assistant extends Agent<Env> {
|
|
34
31
|
ai = createAI({ binding: this.env.AI });
|
|
35
32
|
registry = createRegistry();
|
|
36
33
|
|
|
37
34
|
harness = new PiHarness({
|
|
38
|
-
harness: ({ storage, context }) => {
|
|
35
|
+
harness: ({ storage, context, onWake }) => {
|
|
39
36
|
const models = createModels();
|
|
40
37
|
models.setProvider(this.ai.provider);
|
|
41
38
|
return Harness.open(
|
|
42
39
|
storage,
|
|
43
|
-
{ models, registry: this.registry,
|
|
40
|
+
{ models, registry: this.registry, onWake },
|
|
44
41
|
context
|
|
45
42
|
);
|
|
46
43
|
},
|
|
@@ -61,14 +58,14 @@ export class Assistant extends Agent<Env> {
|
|
|
61
58
|
}
|
|
62
59
|
```
|
|
63
60
|
|
|
64
|
-
| Option | What it is
|
|
65
|
-
| ---------- |
|
|
66
|
-
| `harness` | Required. Receives `{ storage, context }` and returns Pi's `Harness`, usually from `Harness.open`. Models, the registry, `settings`, `env` and `onReport` are all yours to build here.
|
|
67
|
-
| `defaults` | What a new session starts with: `model`, a pi-ai `Model` such as `ai("@cf/…")`, and `thinkingLevel`. Without a model, a session's prompts end unanswered (`no_model`) until one is set.
|
|
61
|
+
| Option | What it is |
|
|
62
|
+
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
63
|
+
| `harness` | Required. Receives `{ storage, context, onWake }` and returns Pi's `Harness`, usually from `Harness.open`. Pass `onWake` to `Harness.open`, so the object can hibernate while Pi's tasks sleep (see [How recovery works](#how-recovery-works)). Models, the registry, `settings`, `env` and `onReport` are all yours to build here. |
|
|
64
|
+
| `defaults` | What a new session starts with: `model`, a pi-ai `Model` such as `ai("@cf/…")`, and `thinkingLevel`. Without a model, a session's prompts end unanswered (`no_model`) until one is set. |
|
|
68
65
|
|
|
69
|
-
|
|
66
|
+
Create the registry per object, as the `registry` field above does, not at module scope: Pi keeps every `Harness` subscribed to its registry until the `Harness` closes, and an evicted object never closes it.
|
|
70
67
|
|
|
71
|
-
The factory runs
|
|
68
|
+
The factory runs once per open: at startup, and again after a storage error (see [How recovery works](#how-recovery-works)), so keep it safe to run more than once. If it throws the operation that triggered it fails and the next operation tries again.
|
|
72
69
|
|
|
73
70
|
A model in `defaults` or `session.setModel()` is stored by its provider and id only. Pi resolves it at each request against the `Models` the factory opened Pi with, so it must be a model those `Models` list. Options passed to `ai(id, options)` there, such as `fallback`, are not applied.
|
|
74
71
|
|
|
@@ -98,14 +95,18 @@ const wordCount: ToolRegistration<typeof WordCount> = {
|
|
|
98
95
|
}
|
|
99
96
|
};
|
|
100
97
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
// One per object, as a field: `registry = createWritingRegistry();`
|
|
99
|
+
function createWritingRegistry() {
|
|
100
|
+
const registry = createRegistry();
|
|
101
|
+
registry.install({
|
|
102
|
+
name: "writing",
|
|
103
|
+
sections: [
|
|
104
|
+
{ key: "preamble", render: () => "You are an editor.", tag: false }
|
|
105
|
+
],
|
|
106
|
+
tools: [wordCount]
|
|
107
|
+
});
|
|
108
|
+
return registry;
|
|
109
|
+
}
|
|
109
110
|
```
|
|
110
111
|
|
|
111
112
|
- **Tool arguments are typed from the schema.** Type a tool as `ToolRegistration<typeof Parameters>`; a bare `ToolRegistration` types its arguments as `unknown`.
|
|
@@ -122,11 +123,11 @@ Pi's extensions can do more: hooks on model requests and tool calls (`hook(ToolT
|
|
|
122
123
|
```ts
|
|
123
124
|
import { skills } from "agents/harness/pi";
|
|
124
125
|
|
|
125
|
-
harness: async ({ storage, context }) => {
|
|
126
|
+
harness: async ({ storage, context, onWake }) => {
|
|
126
127
|
this.registry.install(await skills(sources));
|
|
127
128
|
return Harness.open(
|
|
128
129
|
storage,
|
|
129
|
-
{ models, registry: this.registry,
|
|
130
|
+
{ models, registry: this.registry, onWake },
|
|
130
131
|
context
|
|
131
132
|
);
|
|
132
133
|
};
|
|
@@ -144,7 +145,7 @@ import { browserTool } from "agents/browser/pi";
|
|
|
144
145
|
|
|
145
146
|
readonly browser = new Browser({ provider: browserRun(this.env.BROWSER) });
|
|
146
147
|
|
|
147
|
-
harness: async ({ storage, context }) => {
|
|
148
|
+
harness: async ({ storage, context, onWake }) => {
|
|
148
149
|
this.registry.install({
|
|
149
150
|
name: "browser",
|
|
150
151
|
tools: [
|
|
@@ -153,7 +154,7 @@ harness: async ({ storage, context }) => {
|
|
|
153
154
|
});
|
|
154
155
|
return Harness.open(
|
|
155
156
|
storage,
|
|
156
|
-
{ models, registry: this.registry,
|
|
157
|
+
{ models, registry: this.registry, onWake },
|
|
157
158
|
context
|
|
158
159
|
);
|
|
159
160
|
};
|
|
@@ -189,14 +190,15 @@ const result = await session.wait(receipt.operationId);
|
|
|
189
190
|
| `wait(operationId, signal?)` | The operation's result once Pi settles it. Aborting `signal` stops only the wait, not the work. |
|
|
190
191
|
| `steer(input)` | `submit` with `whenBusy: "steer"`. |
|
|
191
192
|
| `abort(operationId?)` | Withdraws a queued operation, or with no id, stops everything running in the session. |
|
|
192
|
-
| `pending()` | Operations Pi has not settled yet: queued,
|
|
193
|
+
| `pending()` | Operations Pi has not settled yet: queued, part of the running work, or `parked` (see [How recovery works](#how-recovery-works)). |
|
|
194
|
+
| `retry()` | Runs again the tasks Pi's restart limit parked in the session's work. Resolves `false` when nothing was parked. |
|
|
193
195
|
| `messages()` | Pi's transcript entries since the newest reset. |
|
|
194
196
|
| `events()` | Pi's agent events: a `snapshot`, then one batch per commit. |
|
|
195
197
|
| `reset(handoff?)` | Starts a new context, optionally from a handoff note. |
|
|
196
198
|
| `setModel(model)` | Changes this session's model. |
|
|
197
199
|
| `busy()` | Whether a run is going. |
|
|
198
200
|
|
|
199
|
-
`harness.prompt()`, `submit()`, `wait()`, `abort()`, `messages()` and `pending()` act on the root session, or on `{ session }`. `harness.sessions` has `create()`, `fork(from)`, `get(id)` and `list()`, which includes sessions subagents created. `ROOT_SESSION` is the root's id.
|
|
201
|
+
`harness.prompt()`, `submit()`, `wait()`, `abort()`, `retry()`, `messages()` and `pending()` act on the root session, or on `{ session }`. `harness.sessions` has `create()`, `fork(from)`, `get(id)` and `list()`, which includes sessions subagents created. `ROOT_SESSION` is the root's id.
|
|
200
202
|
|
|
201
203
|
`messages()` and `events()` are Pi's own records, not a UI-ready chat transcript. The example below folds them into one.
|
|
202
204
|
|
|
@@ -216,9 +218,41 @@ The kind is a label, not a permission. It records what your code said the input
|
|
|
216
218
|
|
|
217
219
|
The example app folds the transcript into chat messages, ignores `kind` and `data`, and shows every input as a user message. To show labelled input differently, read them from the entry.
|
|
218
220
|
|
|
221
|
+
### Inputs queued behind a failed run
|
|
222
|
+
|
|
223
|
+
A run fails with `model_error` when the model errors and Pi does not retry or the error outlasts its retries, and with `no_model` when the session has no usable model. Its own operation settles `unanswered` with that reason. Pi's `settings.afterFailedRun` decides what happens to the inputs queued behind it. Refer to [Failures and Recovery](https://github.com/earendil-works/pi/tree/main/packages/durable#failures-and-recovery) in the pi-durable README for what each value does. Set it in the factory:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
harness: ({ storage, context, onWake }) =>
|
|
227
|
+
Harness.open(
|
|
228
|
+
storage,
|
|
229
|
+
{
|
|
230
|
+
models,
|
|
231
|
+
registry: this.registry,
|
|
232
|
+
onWake,
|
|
233
|
+
settings: { afterFailedRun: "withdraw" }
|
|
234
|
+
},
|
|
235
|
+
context
|
|
236
|
+
);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
| Value | What callers see |
|
|
240
|
+
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
241
|
+
| `keep` (default) | `pending()` lists the inputs as `queued`, `busy()` is `false`, and `wait()` keeps waiting. The next `submit()` runs them first, in order, then the new input. The next `reset()` runs them in the new context. |
|
|
242
|
+
| `continue` | `wait()` returns their results as usual. If the cause persists, such as a missing model, each queued input fails in its own run. |
|
|
243
|
+
| `withdraw` | `wait()` returns `{ status: "unanswered", reason: "run_failed" }`, and `pending()` no longer lists them. |
|
|
244
|
+
|
|
245
|
+
With `keep`, the kept inputs are not work to Pi: its `wake()` counts tasks, not queued inputs. The wake job completes, and the object can hibernate until the next `submit()` or `reset()`. Pass a `signal` to `wait()` if a caller should not wait that long. To withdraw a kept input, call `abort(operationId)`, which settles it `unanswered` with reason `aborted`. `session.abort()` with no id stops the whole session, which withdraws every kept input too.
|
|
246
|
+
|
|
219
247
|
## How recovery works
|
|
220
248
|
|
|
221
|
-
The harness schedules one lifecycle job
|
|
249
|
+
The harness schedules one lifecycle job, driven by Pi's `wake()`. While Pi has work running, the job waits on it and refreshes itself as a heartbeat. While Pi's tasks sleep, such as a retry backoff, the job is due when the first one wakes. Pi says when through the `onWake` the factory passes to `Harness.open`, and sets no in-memory timer of its own, so the object can hibernate until then. A task that sleeps while other work runs, such as a compaction retry during a generation, is resumed from the job's wait when it is due. Without `onWake` the harness still works, but Pi's timer keeps the object in memory, and billed, while a task sleeps, and the harness logs a warning. When Pi has nothing to do, the job completes. If the object is evicted or crashes, the job is still due, so its alarm restarts the object. Pi reopens its stored state and resumes the session's work: a replay-safe tool call runs again, and an unsafe one is reported to the model as interrupted.
|
|
250
|
+
|
|
251
|
+
A task that keeps crashing the object would restart it forever, so Pi parks a task cut off by a crash or eviction `settings.restartLimit` times in a row (default 5) without progress. A parked task is not work: the job completes and the object can hibernate. Its operations show as `parked` in `pending()`, and `wait()` keeps waiting. If your factory passes `onReport` to `Harness.open`, Pi reports it there as `task_parked`. Once you have dealt with the cause, `session.retry()` runs it again and wakes the harness; `session.abort()` gives up on it instead.
|
|
252
|
+
|
|
253
|
+
A storage error closes Pi for good: the call that hit it rejects with the error, and calls and `events()` streams already underway end with Pi's `SessionFailed`, so retry them. If your factory passes `onReport` to `Harness.open`, Pi reports the failure there as `session_failed`. Once the failed Pi has closed, the harness opens Pi again, which runs the factory again, on the next call or the job's next step. Closing waits for running task code, such as a tool call, to return, so code that ignores its abort signal delays the reopen. Pi recovers from what it committed and resumes its work the same way.
|
|
254
|
+
|
|
255
|
+
If a step of the job fails, such as when the factory throws, the job is not dropped. It tries again after 30 seconds, doubling the delay with each failure in a row up to 10 minutes, and goes back to its usual schedule once a step succeeds. A Durable Object memory-limit reset that fails Pi while the job waits on it counts against the alarm's memory-limit circuit breaker. Lifecycle backs the job off and drops it after repeated resets. The next call opens Pi again and pushes the job again, so Pi's restart limit is what eventually parks a task that keeps causing resets.
|
|
222
256
|
|
|
223
257
|
Every operation waits for the object's startup, which runs the factory. A call over RPC that arrives first waits for it rather than opening Pi on its own.
|
|
224
258
|
|
|
@@ -32,7 +32,6 @@ Agents SDK supports multiple human-in-the-loop patterns. Choose based on your us
|
|
|
32
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
33
|
| Client-side tools | `onToolCall` | Tools that need browser APIs or user interaction | Pattern below |
|
|
34
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) |
|
|
36
35
|
|
|
37
36
|
### Decision Guide
|
|
38
37
|
|
|
@@ -40,7 +39,7 @@ Agents SDK supports multiple human-in-the-loop patterns. Choose based on your us
|
|
|
40
39
|
Is this part of a multi-step workflow?
|
|
41
40
|
├── Yes → Use Workflow Approval (waitForApproval)
|
|
42
41
|
└── No → Are you building an MCP server?
|
|
43
|
-
├── Yes → Use MCP Elicitation (
|
|
42
|
+
├── Yes → Use MCP Elicitation (inputRequired)
|
|
44
43
|
└── No → Is this an AI chat interaction?
|
|
45
44
|
├── Yes → Does the tool need browser APIs?
|
|
46
45
|
│ ├── Yes → Use onToolCall (client-side execution)
|
|
@@ -460,7 +459,7 @@ See the complete example: [openai-sdk/human-in-the-loop/](https://github.com/clo
|
|
|
460
459
|
|
|
461
460
|
### MCP Elicitation
|
|
462
461
|
|
|
463
|
-
|
|
462
|
+
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.
|
|
464
463
|
|
|
465
464
|
```typescript
|
|
466
465
|
import {
|
|
@@ -517,8 +516,6 @@ The MCP client renders the JSON Schema form and the original operation remains p
|
|
|
517
516
|
|
|
518
517
|
See the [Stateless Elicitation example](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr).
|
|
519
518
|
|
|
520
|
-
**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`.
|
|
521
|
-
|
|
522
519
|
## State Patterns for Approvals
|
|
523
520
|
|
|
524
521
|
Track pending approvals in agent state for UI rendering and persistence:
|
|
@@ -640,10 +637,9 @@ async submitForApproval(request: ApprovalRequest): Promise<string> {
|
|
|
640
637
|
| 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 |
|
|
641
638
|
| 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 |
|
|
642
639
|
| Stateless Elicitation | [examples/mcp-elicitation-mrtr/](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation-mrtr) | Stateless multi-round input |
|
|
643
|
-
| Legacy Elicitation | [examples/mcp-elicitation/](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) | Stateful pushed input requests |
|
|
644
640
|
|
|
645
641
|
For detailed API documentation, see:
|
|
646
642
|
|
|
647
643
|
- [Workflows](./workflows.md) - `waitForApproval()`, `approveWorkflow()`, `rejectWorkflow()`
|
|
648
|
-
- [MCP Servers](./mcp-servers.md) - `inputRequired()`
|
|
644
|
+
- [MCP Servers](./mcp-servers.md) - `inputRequired()` elicitation
|
|
649
645
|
- [Callable Methods](./callable-methods.md) - `@callable()` decorator for approval endpoints
|
package/docs/index.md
CHANGED
|
@@ -149,10 +149,10 @@ Building your own harness? Sessions stores the conversation, Streams holds durab
|
|
|
149
149
|
|
|
150
150
|
## MCP (Model Context Protocol)
|
|
151
151
|
|
|
152
|
-
- [Creating MCP Servers](./mcp-servers.md) - Build MCP servers with `
|
|
152
|
+
- [Creating MCP Servers](./mcp-servers.md) - Build stateless MCP servers with `createMcpHandler`
|
|
153
153
|
- [Securing MCP Servers](./securing-mcp-servers.md) - OAuth and authentication for MCP
|
|
154
154
|
- [Connecting to MCP Servers](./mcp-client.md) - `MCPClientManager` on a Durable Object, or `addMcpServer()` on an Agent
|
|
155
|
-
- [MCP Transports](./mcp-transports.md) -
|
|
155
|
+
- [MCP Transports](./mcp-transports.md) - Streamable HTTP on the server, and the transports an Agent can connect with
|
|
156
156
|
|
|
157
157
|
## Operations
|
|
158
158
|
|
package/docs/lifecycle.md
CHANGED
|
@@ -234,7 +234,13 @@ physical alarm automatically — there is no explicit rearm call. When the
|
|
|
234
234
|
alarm fires, Lifecycle drives due jobs in due order as an event loop, then
|
|
235
235
|
runs host `onAlarm()`, then re-arms from queue state. Before driving any job
|
|
236
236
|
it arms a deadman pre-alarm so an isolate death mid-drive still wakes the
|
|
237
|
-
object to resume.
|
|
237
|
+
object to resume. While the queue holds jobs, it also arms a fallback alarm
|
|
238
|
+
before startup runs, at the next job's time but at least 30 seconds out. An
|
|
239
|
+
alarm whose startup throws still leaves that wake, whether or not the
|
|
240
|
+
platform's own alarm retries run, so the failed startup is retried then. A
|
|
241
|
+
startup that pushes or reschedules jobs and then throws, from an alarm or a
|
|
242
|
+
request, arms the same fallback from the queue as it stands, so those jobs
|
|
243
|
+
keep a wake too.
|
|
238
244
|
|
|
239
245
|
A job's drive result decides what happens next: returning nothing completes
|
|
240
246
|
and deletes it, `{ rescheduleAt }` suspends it until a future time, and
|
package/docs/mcp-client.md
CHANGED
|
@@ -51,19 +51,6 @@ callback URL to `registerServer()` and route that request to the same named
|
|
|
51
51
|
Durable Object. The manager persists the URL and only intercepts a callback
|
|
52
52
|
whose origin and pathname match it.
|
|
53
53
|
|
|
54
|
-
An HTTP-only manager does not need `env`. Pass the Durable Object environment
|
|
55
|
-
when the catalog can contain RPC servers so persisted binding names can be
|
|
56
|
-
resolved after a wake:
|
|
57
|
-
|
|
58
|
-
```ts
|
|
59
|
-
readonly mcp = new MCPClientManager("my-object", "1.0.0", {
|
|
60
|
-
env: this.env
|
|
61
|
-
});
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
If an RPC row exists without `env`, startup logs a warning identifying the
|
|
65
|
-
server and does not recreate that connection.
|
|
66
|
-
|
|
67
54
|
`Agent` installs this same capability object directly. Existing `this.mcp`,
|
|
68
55
|
`addMcpServer()`, `removeMcpServer()`, and `getMcpServers()` APIs remain
|
|
69
56
|
available.
|
|
@@ -188,7 +175,7 @@ These options are persisted and used when reconnecting after hibernation or afte
|
|
|
188
175
|
|
|
189
176
|
### Protocol negotiation and elicitation
|
|
190
177
|
|
|
191
|
-
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
|
|
178
|
+
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.
|
|
192
179
|
|
|
193
180
|
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.
|
|
194
181
|
|
|
@@ -243,8 +230,6 @@ MCP server URLs are validated before connection to prevent Server-Side Request F
|
|
|
243
230
|
|
|
244
231
|
Loopback development URLs such as `localhost`, `127.0.0.1`, and `::1` are allowed.
|
|
245
232
|
|
|
246
|
-
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.
|
|
247
|
-
|
|
248
233
|
### Return Value
|
|
249
234
|
|
|
250
235
|
`addMcpServer()` returns the connection state:
|
|
@@ -666,17 +651,6 @@ async addMcpServer(
|
|
|
666
651
|
| { id: string; state: "authenticating"; authUrl: string }
|
|
667
652
|
>
|
|
668
653
|
|
|
669
|
-
// RPC transport (Durable Object binding — no HTTP overhead)
|
|
670
|
-
async addMcpServer(
|
|
671
|
-
name: string,
|
|
672
|
-
binding: DurableObjectNamespace,
|
|
673
|
-
options?: {
|
|
674
|
-
props?: Record<string, unknown>; // passed to the McpAgent's onStart(props)
|
|
675
|
-
client?: ClientOptions;
|
|
676
|
-
retry?: RetryOptions;
|
|
677
|
-
}
|
|
678
|
-
): Promise<{ id: string; state: "ready" }>
|
|
679
|
-
|
|
680
654
|
// Legacy signature (still supported)
|
|
681
655
|
async addMcpServer(
|
|
682
656
|
name: string,
|
|
@@ -689,7 +663,7 @@ async addMcpServer(
|
|
|
689
663
|
|
|
690
664
|
Add and connect to an MCP server. Throws if connection or discovery fails.
|
|
691
665
|
|
|
692
|
-
`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).
|
|
666
|
+
`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.
|
|
693
667
|
|
|
694
668
|
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.
|
|
695
669
|
|