agents 0.20.1 → 0.22.0
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 +51 -12
- package/dist/{agent-tool-types-BC-WFlsz.d.ts → agent-routing-CnkaHb-v.d.ts} +3305 -3138
- package/dist/agent-routing.d.ts +14 -0
- package/dist/agent-routing.js +187 -0
- package/dist/agent-routing.js.map +1 -0
- package/dist/agent-tool-types.d.ts +26 -26
- package/dist/{agent-tools-DeHe9Xov.d.ts → agent-tools-C0XJqWzB.d.ts} +7 -7
- package/dist/agent-tools.d.ts +44 -28
- package/dist/agent-tools.js +16 -7
- package/dist/agent-tools.js.map +1 -1
- package/dist/browser/ai.d.ts +5 -3
- package/dist/browser/ai.js +88 -8
- package/dist/browser/ai.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/tanstack-ai.js +13 -1
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/capability-BjSKYpzg.js +42 -0
- package/dist/capability-BjSKYpzg.js.map +1 -0
- package/dist/capability-runner-CvHGZqUu.d.ts +150 -0
- package/dist/chat/index.d.ts +51 -15
- package/dist/chat/index.js +59 -4
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +19 -180
- package/dist/chat/react.js +93 -600
- package/dist/chat/react.js.map +1 -1
- package/dist/chat/transport.d.ts +10 -0
- package/dist/chat/transport.js +2 -0
- package/dist/chat-sdk/index.d.ts +4 -4
- package/dist/chat-sdk/index.js +1 -1
- package/dist/{client-zqKcsyFa.js → client-jagG8a9_.js} +129 -37
- package/dist/client-jagG8a9_.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/{cloudflare-BduZwmYK.js → cloudflare-Dzvc7V2N.js} +10 -3
- package/dist/{cloudflare-BduZwmYK.js.map → cloudflare-Dzvc7V2N.js.map} +1 -1
- package/dist/{connector-v2M1zlZp.d.ts → connector-CkQD4MK3.d.ts} +20 -3
- package/dist/{connector-KEJnl6e5.js → connector-CptFKzRh.js} +158 -40
- package/dist/connector-CptFKzRh.js.map +1 -0
- package/dist/current-agent-CuMErtly.d.ts +283 -0
- package/dist/current-agent-DhoDkSnH.js +51 -0
- package/dist/current-agent-DhoDkSnH.js.map +1 -0
- package/dist/diagnostics-CaBjfz4J.js +41 -0
- package/dist/diagnostics-CaBjfz4J.js.map +1 -0
- package/dist/{do-oauth-client-provider-VTZj2VtM.d.ts → do-oauth-client-provider-Tmf1vgKz.d.ts} +2 -2
- package/dist/durable-object-lifecycle-D6nNQJJd.js +862 -0
- package/dist/durable-object-lifecycle-D6nNQJJd.js.map +1 -0
- package/dist/{email-CL27preh.d.ts → email-7TatiTnl.d.ts} +38 -9
- package/dist/email-send.d.ts +15 -0
- package/dist/email-send.js +32 -0
- package/dist/email-send.js.map +1 -0
- package/dist/email.d.ts +14 -10
- package/dist/email.js.map +1 -1
- package/dist/experimental/memory/session/index.d.ts +3 -2
- package/dist/experimental/memory/session/index.js +15 -10
- package/dist/experimental/memory/session/index.js.map +1 -1
- package/dist/{handler-stateless-C_bo-Ytq.d.ts → handler-stateless-DxYpJ_XF.d.ts} +3 -3
- package/dist/{handler-stateless-CIkKPETH.js → handler-stateless-VvrWSAVA.js} +5 -5
- package/dist/handler-stateless-VvrWSAVA.js.map +1 -0
- package/dist/{index-BRnybD6X.d.ts → index-DcSAZKsB.d.ts} +19 -31
- package/dist/index.d.ts +102 -91
- package/dist/index.js +12 -7235
- package/dist/internal_context-BlxFEWfn.d.ts +19 -0
- package/dist/internal_context.d.ts +10 -4
- package/dist/internal_context.js +1 -10
- package/dist/{client-invoker-BNSZxAkv.d.ts → invoker-CG0_p_Wq.d.ts} +2 -2
- package/dist/{client-invoker-VNZ7X0nn.js → invoker-CHMnoxIA.js} +2 -2
- package/dist/invoker-CHMnoxIA.js.map +1 -0
- package/dist/lifecycle/index.d.ts +48 -0
- package/dist/lifecycle/index.js +4 -0
- package/dist/mcp/{do-oauth-client-provider.d.ts → client/do-oauth-client-provider.d.ts} +1 -1
- package/dist/mcp/{do-oauth-client-provider.js → client/do-oauth-client-provider.js} +1 -1
- package/dist/mcp/client/do-oauth-client-provider.js.map +1 -0
- package/dist/mcp/client/index.d.ts +42 -0
- package/dist/mcp/{client.js → client/index.js} +1 -1
- package/dist/mcp/{x402.d.ts → client/x402.d.ts} +2 -2
- package/dist/mcp/{x402.js → client/x402.js} +2 -2
- package/dist/mcp/client/x402.js.map +1 -0
- package/dist/mcp/index.d.ts +36 -36
- package/dist/mcp/index.js +14 -16
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/{server.d.ts → server/index.d.ts} +1 -1
- package/dist/mcp/{server.js → server/index.js} +1 -1
- package/dist/observability/ai/index.d.ts +1 -130
- package/dist/observability/ai/index.js +88 -433
- package/dist/observability/ai/index.js.map +1 -1
- package/dist/observability/index.d.ts +4 -4
- package/dist/observability/index.js +3 -50
- package/dist/observability/index.js.map +1 -1
- package/dist/{wire-types-CU9rLoeS.js → protocol-B0nh6KNf.js} +20 -66
- package/dist/protocol-B0nh6KNf.js.map +1 -0
- package/dist/react.d.ts +4 -4
- package/dist/react.js +6 -9
- package/dist/react.js.map +1 -1
- package/dist/schedule.d.ts +25 -94
- package/dist/schedule.js +1 -98
- package/dist/schedule.js.map +1 -1
- package/dist/scheduler-CR9RHGos.js +857 -0
- package/dist/scheduler-CR9RHGos.js.map +1 -0
- package/dist/scheduler-DQoTGoAW.d.ts +224 -0
- package/dist/schedules/index.d.ts +22 -0
- package/dist/schedules/index.js +2 -0
- package/dist/schedules/parser.d.ts +79 -0
- package/dist/schedules/parser.js +103 -0
- package/dist/schedules/parser.js.map +1 -0
- package/dist/serializable.d.ts +1 -1
- package/dist/sql-error-CPY-GXyI.d.ts +12 -0
- package/dist/sql-error.d.ts +2 -0
- package/dist/sql-error.js +16 -0
- package/dist/sql-error.js.map +1 -0
- package/dist/src-5W6JNKVb.js +6613 -0
- package/dist/src-5W6JNKVb.js.map +1 -0
- package/dist/sub-routing.d.ts +18 -6
- package/dist/sub-routing.js +92 -2
- package/dist/sub-routing.js.map +1 -1
- package/dist/wire-types-CnMt6_HR.js +47 -0
- package/dist/wire-types-CnMt6_HR.js.map +1 -0
- package/dist/workflow-types.d.ts +25 -25
- package/dist/workflow-types.js.map +1 -1
- package/dist/workflows.d.ts +22 -22
- package/dist/workflows.js +2 -1
- package/dist/workflows.js.map +1 -1
- package/dist/ws-chat-transport-UNRIS2xl.d.ts +184 -0
- package/dist/ws-chat-transport-rWwta645.js +698 -0
- package/dist/ws-chat-transport-rWwta645.js.map +1 -0
- package/docs/adding-to-existing-project.md +4 -2
- package/docs/agent-class.md +29 -87
- package/docs/agent-tools.md +29 -0
- package/docs/browse-the-web.md +16 -1
- package/docs/chat-agents.md +16 -13
- package/docs/client-sdk.md +12 -8
- package/docs/configuration.md +7 -1
- package/docs/cross-domain-authentication.md +7 -35
- package/docs/durable-execution.md +1 -1
- package/docs/email.md +2 -13
- package/docs/http-websockets.md +1 -11
- package/docs/human-in-the-loop.md +16 -13
- package/docs/index.md +1 -0
- package/docs/lifecycle.md +323 -0
- package/docs/long-running-agents.md +13 -15
- package/docs/mcp-client.md +55 -0
- package/docs/mcp-servers.md +6 -0
- package/docs/mcp-transports.md +18 -12
- package/docs/migration-to-ai-sdk-v5.md +2 -2
- package/docs/migration-to-ai-sdk-v6.md +5 -1
- package/docs/observability.md +33 -77
- package/docs/resumable-streaming.md +1 -1
- package/docs/routing.md +27 -0
- package/docs/scheduling.md +175 -15
- package/docs/server-driven-messages.md +1 -1
- package/docs/sub-agents.md +47 -2
- package/docs/webhooks.md +109 -136
- package/docs/workflows.md +9 -1
- package/package.json +41 -23
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +0 -26
- package/dist/cli/index.js.map +0 -1
- package/dist/client-invoker-VNZ7X0nn.js.map +0 -1
- package/dist/client-zqKcsyFa.js.map +0 -1
- package/dist/connector-KEJnl6e5.js.map +0 -1
- package/dist/handler-stateless-CIkKPETH.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal_context-Dg4Cgjcu.d.ts +0 -37
- package/dist/internal_context.js.map +0 -1
- package/dist/mcp/client.d.ts +0 -42
- package/dist/mcp/do-oauth-client-provider.js.map +0 -1
- package/dist/mcp/x402.js.map +0 -1
- package/dist/wire-types-CU9rLoeS.js.map +0 -1
- package/dist/workflow-types-Baz_PO5v.d.ts +0 -280
package/docs/email.md
CHANGED
|
@@ -419,21 +419,10 @@ When your agent sends emails and expects replies, use secure reply routing to pr
|
|
|
419
419
|
|
|
420
420
|
### Setup
|
|
421
421
|
|
|
422
|
-
1.
|
|
423
|
-
|
|
424
|
-
```jsonc
|
|
425
|
-
// wrangler.jsonc
|
|
426
|
-
{
|
|
427
|
-
"vars": {
|
|
428
|
-
"EMAIL_SECRET": "change-me-in-production"
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
For production, use Wrangler secrets instead:
|
|
422
|
+
1. Store the signing key as a Wrangler secret. Do not put it in `vars` or commit it to source control:
|
|
434
423
|
|
|
435
424
|
```bash
|
|
436
|
-
wrangler secret put EMAIL_SECRET
|
|
425
|
+
pnpm exec wrangler secret put EMAIL_SECRET
|
|
437
426
|
```
|
|
438
427
|
|
|
439
428
|
2. Use the combined resolver pattern:
|
package/docs/http-websockets.md
CHANGED
|
@@ -408,17 +408,7 @@ export class MyAgent extends Agent {
|
|
|
408
408
|
|
|
409
409
|
## Hibernation
|
|
410
410
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
### Enabling Hibernation
|
|
414
|
-
|
|
415
|
-
Hibernation is enabled by default. To disable:
|
|
416
|
-
|
|
417
|
-
```typescript
|
|
418
|
-
export class AlwaysOnAgent extends Agent {
|
|
419
|
-
static options = { hibernate: false };
|
|
420
|
-
}
|
|
421
|
-
```
|
|
411
|
+
Agent WebSockets always use Cloudflare's Hibernation API. An idle Agent can leave memory while its WebSocket clients remain connected; there is no non-hibernating mode.
|
|
422
412
|
|
|
423
413
|
### How Hibernation Works
|
|
424
414
|
|
|
@@ -196,7 +196,7 @@ Define tools with `needsApproval` to require human confirmation:
|
|
|
196
196
|
```typescript
|
|
197
197
|
import { AIChatAgent } from "@cloudflare/ai-chat";
|
|
198
198
|
import { createWorkersAI } from "workers-ai-provider";
|
|
199
|
-
import { streamText, tool, convertToModelMessages } from "ai";
|
|
199
|
+
import { streamText, tool, convertToModelMessages, stepCountIs } from "ai";
|
|
200
200
|
import { z } from "zod";
|
|
201
201
|
|
|
202
202
|
export class MyAgent extends AIChatAgent {
|
|
@@ -238,7 +238,7 @@ export class MyAgent extends AIChatAgent {
|
|
|
238
238
|
execute: async ({ city }) => fetchWeather(city)
|
|
239
239
|
})
|
|
240
240
|
},
|
|
241
|
-
|
|
241
|
+
stopWhen: stepCountIs(5)
|
|
242
242
|
});
|
|
243
243
|
|
|
244
244
|
return result.toUIMessageStreamResponse();
|
|
@@ -246,6 +246,8 @@ export class MyAgent extends AIChatAgent {
|
|
|
246
246
|
}
|
|
247
247
|
```
|
|
248
248
|
|
|
249
|
+
The `inputSchema` accepts the AI SDK's flexible schema format, so you are not limited to Zod. You can also use Valibot, a Standard JSON Schema-compatible schema, or a raw JSON Schema wrapped with `jsonSchema()` from `ai`. See [Use Valibot or another schema library](./agent-tools.md#use-valibot-or-another-schema-library) for details.
|
|
250
|
+
|
|
249
251
|
### Client
|
|
250
252
|
|
|
251
253
|
Handle approval requests with `addToolApprovalResponse`:
|
|
@@ -355,7 +357,7 @@ See the complete example: [guides/human-in-the-loop/](https://github.com/cloudfl
|
|
|
355
357
|
|
|
356
358
|
### Surviving restarts while waiting for a human
|
|
357
359
|
|
|
358
|
-
A Durable Object can be evicted at any time (a deploy, an inactivity timeout, a resource limit), including while a turn is paused on an approval prompt or a client-side tool call.
|
|
360
|
+
A Durable Object can be evicted at any time (a deploy, an inactivity timeout, a resource limit), including while a turn is paused on an approval prompt or a client-side tool call. Durable [`chatRecovery`](./chat-agents.md#stream-recovery) is always enabled. The SDK recognizes that such a turn is _waiting on the human_, not stuck, and does **not** seal it: the no-progress window, attempt cap, `maxRecoveryWork`, and `shouldKeepRecovering` are all suspended while the interaction is pending. Recovery parks the turn instead of failing it, and the user's eventual approval or `tool_result` resumes the conversation through the normal continuation path. A user who takes minutes to respond to a prompt that was interrupted by a deploy therefore does not see a spurious "session interrupted" error.
|
|
359
361
|
|
|
360
362
|
This protection applies to interactions only the client can resolve — `approval-requested` parts and `input-available` parts for client-side tools (those without a server `execute`). A server tool whose `execute()` was killed mid-flight is a genuine orphan and recovers through the normal transcript-repair path instead.
|
|
361
363
|
|
|
@@ -380,7 +382,7 @@ export class MyAgent extends AIChatAgent {
|
|
|
380
382
|
inputSchema: z.object({})
|
|
381
383
|
})
|
|
382
384
|
},
|
|
383
|
-
|
|
385
|
+
stopWhen: stepCountIs(3)
|
|
384
386
|
});
|
|
385
387
|
|
|
386
388
|
return result.toUIMessageStreamResponse();
|
|
@@ -410,17 +412,18 @@ const { messages, sendMessage } = useAgentChat({
|
|
|
410
412
|
});
|
|
411
413
|
```
|
|
412
414
|
|
|
413
|
-
The server receives the tool output via `CF_AGENT_TOOL_RESULT` and can auto-continue the conversation
|
|
415
|
+
The server receives the tool output via `CF_AGENT_TOOL_RESULT` and can auto-continue the conversation when `stopWhen` allows another step, letting the LLM respond to the location data in the same turn.
|
|
414
416
|
|
|
415
417
|
### OpenAI Agents SDK Pattern
|
|
416
418
|
|
|
417
419
|
When using the [OpenAI Agents SDK](https://openai.github.io/openai-agents-js/), use the `needsApproval` function for conditional approval:
|
|
418
420
|
|
|
419
421
|
```typescript
|
|
420
|
-
import { Agent } from "agents";
|
|
421
|
-
import { tool, run } from "@openai/agents";
|
|
422
|
+
import { Agent as CloudflareAgent } from "agents";
|
|
423
|
+
import { Agent as OpenAIAgent, tool, run } from "@openai/agents";
|
|
424
|
+
import { z } from "zod";
|
|
422
425
|
|
|
423
|
-
export class WeatherAgent extends
|
|
426
|
+
export class WeatherAgent extends CloudflareAgent<Env> {
|
|
424
427
|
async processQuery(query: string) {
|
|
425
428
|
const weatherTool = tool({
|
|
426
429
|
name: "get_weather",
|
|
@@ -438,13 +441,13 @@ export class WeatherAgent extends Agent<Env, AgentState> {
|
|
|
438
441
|
}
|
|
439
442
|
});
|
|
440
443
|
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
444
|
+
const openaiAgent = new OpenAIAgent({
|
|
445
|
+
name: "Weather assistant",
|
|
446
|
+
instructions: "Help the user check the weather.",
|
|
447
|
+
tools: [weatherTool]
|
|
445
448
|
});
|
|
446
449
|
|
|
447
|
-
return
|
|
450
|
+
return run(openaiAgent, query);
|
|
448
451
|
}
|
|
449
452
|
}
|
|
450
453
|
```
|
package/docs/index.md
CHANGED
|
@@ -140,6 +140,7 @@ The differentiator is not "we have durable state" — it is what happens when a
|
|
|
140
140
|
|
|
141
141
|
## Reference
|
|
142
142
|
|
|
143
|
+
- [Durable Object Lifecycle](./lifecycle.md) - Compose reusable durable components outside the Agent base class
|
|
143
144
|
- TODO: [API Reference](./api-reference.md) - Complete API documentation
|
|
144
145
|
- TODO: [FAQ / How is this different from Durable Objects?](./faq.md)
|
|
145
146
|
- TODO: [Resources & Further Reading](./resources.md)
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# Durable Object lifecycle
|
|
2
|
+
|
|
3
|
+
> **Experimental.** Everything exported from `agents/lifecycle` — and the
|
|
4
|
+
> capabilities built on it, including `Scheduler` — may change between
|
|
5
|
+
> releases while the composition surface stabilizes.
|
|
6
|
+
|
|
7
|
+
`agents/lifecycle` lets reusable durable capabilities work in both `Agent` and a
|
|
8
|
+
plain Cloudflare Durable Object. It uses composition: your class extends the
|
|
9
|
+
platform `DurableObject`, then constructs a lifecycle with `this`.
|
|
10
|
+
|
|
11
|
+
## Plain Durable Object
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { DurableObject } from "cloudflare:workers";
|
|
15
|
+
import { Lifecycle } from "agents/lifecycle";
|
|
16
|
+
|
|
17
|
+
export class MyObject extends DurableObject<Env> {
|
|
18
|
+
readonly lifecycle = Lifecycle.install(this);
|
|
19
|
+
|
|
20
|
+
onStart(): void {
|
|
21
|
+
// Runs once per in-memory object lifetime, before work is handled.
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
onRequest(request: Request): Response {
|
|
25
|
+
return new Response(`Hello from ${this.lifecycle.name}: ${request.url}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
onAlarm(): void {
|
|
29
|
+
// Runs after lifecycle capabilities process the alarm.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The side-effect-named static factory constructs the lifecycle and installs the
|
|
35
|
+
runtime-facing `fetch`, `alarm`, `webSocketMessage`, `webSocketClose`, and
|
|
36
|
+
`webSocketError` handlers. Do not define forwarding versions of those methods.
|
|
37
|
+
Implement the semantic callbacks instead.
|
|
38
|
+
|
|
39
|
+
The expanded equivalent is available when useful:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
readonly lifecycle = new Lifecycle(this);
|
|
43
|
+
|
|
44
|
+
constructor(ctx: DurableObjectState, env: Env) {
|
|
45
|
+
super(ctx, env);
|
|
46
|
+
this.lifecycle.installHandlers();
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Route named objects from the outer Worker when you want URL routing:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { routeAgentRequest } from "agents";
|
|
54
|
+
|
|
55
|
+
export default {
|
|
56
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
57
|
+
return (
|
|
58
|
+
(await routeAgentRequest(request, env)) ??
|
|
59
|
+
new Response("Not found", { status: 404 })
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The default URL shape is `/agents/:binding/:name`. Direct
|
|
66
|
+
`env.MY_OBJECT.getByName(name).fetch(request)` calls work as well.
|
|
67
|
+
|
|
68
|
+
`Agent` already constructs this lifecycle. Existing Agent classes continue to
|
|
69
|
+
override `onStart`, `onRequest`, `onConnect`, `onMessage`, `onClose`, and
|
|
70
|
+
`onError` normally.
|
|
71
|
+
|
|
72
|
+
## Request call path
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
routeAgentRequest(request)
|
|
76
|
+
└─ named Durable Object stub.fetch(request)
|
|
77
|
+
└─ lifecycle-installed fetch
|
|
78
|
+
├─ lifecycle startup capabilities
|
|
79
|
+
├─ host onStart
|
|
80
|
+
├─ lifecycle request capabilities
|
|
81
|
+
│ └─ first Response wins
|
|
82
|
+
└─ host onRequest
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
A warm object skips startup but still offers every request to its capabilities.
|
|
86
|
+
|
|
87
|
+
## Reusable capabilities
|
|
88
|
+
|
|
89
|
+
A capability implements only the phases it needs:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import type { DurableObjectCapability } from "agents/lifecycle";
|
|
93
|
+
|
|
94
|
+
class AuditLog implements DurableObjectCapability {
|
|
95
|
+
constructor(private readonly storage: DurableObjectStorage) {}
|
|
96
|
+
|
|
97
|
+
onStart(): void {
|
|
98
|
+
this.storage.sql.exec(`
|
|
99
|
+
CREATE TABLE IF NOT EXISTS audit_log (
|
|
100
|
+
message TEXT NOT NULL
|
|
101
|
+
)
|
|
102
|
+
`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
onRequest({ request }: { request: Request }): Response | undefined {
|
|
106
|
+
if (new URL(request.url).pathname.endsWith("/health")) {
|
|
107
|
+
return new Response("ok");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
onAlarm(): void {
|
|
112
|
+
this.storage.sql.exec("DELETE FROM audit_log");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Install it before startup:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
export class MyObject extends DurableObject<Env> {
|
|
121
|
+
private readonly audit = new AuditLog(this.ctx.storage);
|
|
122
|
+
readonly lifecycle = Lifecycle.install(this).use(this.audit);
|
|
123
|
+
|
|
124
|
+
onRequest(): Response {
|
|
125
|
+
return new Response("application response");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Capabilities run in registration order. Startup and alarms run every hook
|
|
131
|
+
sequentially. Request handling stops at the first returned `Response`. A phase
|
|
132
|
+
failure propagates, and failed startup can be retried.
|
|
133
|
+
|
|
134
|
+
Capabilities extending `LifecycleCapability` receive one standard service
|
|
135
|
+
surface: storage, readiness, startup state, alarm coordination, a host
|
|
136
|
+
invocation boundary, best-effort events, and capability routing.
|
|
137
|
+
Host-specific bindings, authentication, and protocol adapters remain explicit
|
|
138
|
+
constructor dependencies. Lifecycle never grants a capability the complete
|
|
139
|
+
host implicitly.
|
|
140
|
+
|
|
141
|
+
Capability hooks run outside host context, but user callbacks run through
|
|
142
|
+
`this.lifecycle.runInHostContext(fn)` inside the host invocation context.
|
|
143
|
+
Scheduler dispatches its registered callbacks through this boundary, and a
|
|
144
|
+
future capability that calls user code should do the same.
|
|
145
|
+
|
|
146
|
+
## Shared alarm ownership
|
|
147
|
+
|
|
148
|
+
Lifecycle owns the Durable Object's single physical alarm. A capability that
|
|
149
|
+
needs a future wake-up keeps its work in its own durable storage and implements
|
|
150
|
+
`getNextAlarm()`:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { LifecycleCapability, type AlarmContribution } from "agents/lifecycle";
|
|
154
|
+
|
|
155
|
+
class Cleanup extends LifecycleCapability {
|
|
156
|
+
constructor() {
|
|
157
|
+
super("cleanup");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async getNextAlarm(): Promise<AlarmContribution> {
|
|
161
|
+
return (await this.lifecycle.storage.get<number>("cleanup:next")) ?? null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async onAlarm(): Promise<void> {
|
|
165
|
+
const next = await this.lifecycle.storage.get<number>("cleanup:next");
|
|
166
|
+
if (next === undefined || next > Date.now()) return;
|
|
167
|
+
await this.lifecycle.storage.delete("cleanup:next");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async scheduleCleanup(time: number): Promise<void> {
|
|
171
|
+
await this.lifecycle.storage.put("cleanup:next", time);
|
|
172
|
+
await this.lifecycle.alarms.rearm();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Lifecycle selects the earliest contribution from every capability and the
|
|
178
|
+
host. It runs all capability `onAlarm()` hooks, then host `onAlarm()`, then
|
|
179
|
+
recalculates the physical alarm. Capabilities do not depend on Scheduler or on
|
|
180
|
+
each other merely to receive alarm wakes.
|
|
181
|
+
A contribution can be `{ time, exclusive: true }` when its wake time must
|
|
182
|
+
replace ordinary wake candidates, such as a pending teardown. This changes only
|
|
183
|
+
which physical alarm is armed; when that alarm fires, normal capability and host
|
|
184
|
+
hook order still applies. Hosts can implement `getNextAlarm()` for alarm work
|
|
185
|
+
that has not yet been extracted into a capability.
|
|
186
|
+
|
|
187
|
+
## Capability events
|
|
188
|
+
|
|
189
|
+
Capabilities publish best-effort telemetry through their standard service
|
|
190
|
+
surface. Lifecycle assigns the capability source from the stable ID passed to
|
|
191
|
+
`super()`:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
class Cleanup extends LifecycleCapability {
|
|
195
|
+
constructor() {
|
|
196
|
+
super("cleanup");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
reportRemoval(key: string): void {
|
|
200
|
+
this.lifecycle.events.emit("cleanup:remove", { key });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Lifecycle publishes events from a plain Lifecycle Object to the existing
|
|
206
|
+
`agents:*` diagnostics channels according to the event type. Delivery is
|
|
207
|
+
best-effort, runs outside ambient host context, and does not fail the emitting
|
|
208
|
+
capability when a telemetry sink throws. Persist an outbox in the capability
|
|
209
|
+
when delivery is part of the durable business operation.
|
|
210
|
+
|
|
211
|
+
## Capability routing
|
|
212
|
+
|
|
213
|
+
Every `LifecycleCapability` also receives `lifecycle.routes`. `toRoot()` routes
|
|
214
|
+
a message to the matching capability ID on the root Lifecycle; `to(address, …)`
|
|
215
|
+
routes to another addressed Lifecycle. Lifecycle owns the generic envelope and
|
|
216
|
+
dispatch. A host with child objects supplies the transport internally.
|
|
217
|
+
|
|
218
|
+
Agent uses this for facet schedules: Scheduler sends owner-scoped CRUD to the
|
|
219
|
+
root Scheduler and routes due callbacks back to the matching facet Scheduler.
|
|
220
|
+
Existing rows remain in the root `cf_agents_schedules` table. Scheduler does not
|
|
221
|
+
implement facet traversal, and Agent exposes only one internal generic Lifecycle
|
|
222
|
+
route aperture.
|
|
223
|
+
|
|
224
|
+
## Explicit disposal
|
|
225
|
+
|
|
226
|
+
`lifecycle.dispose()` calls each capability's optional `dispose()` method in
|
|
227
|
+
reverse installation order. This phase releases live resources such as MCP
|
|
228
|
+
transports and listeners. It does not delete capability tables. An explicit
|
|
229
|
+
Lifecycle Object destruction disposes live resources once, then calls
|
|
230
|
+
`storage.deleteAll()` once for all shared durable state. Eviction calls neither.
|
|
231
|
+
|
|
232
|
+
## Lifecycle Object context
|
|
233
|
+
|
|
234
|
+
`agents/lifecycle` exports the `LifecycleObject` interface for a
|
|
235
|
+
`DurableObject` with an installed `Lifecycle` and the semantic hooks Lifecycle
|
|
236
|
+
dispatches. This is a host type, not the batteries-included `Agent` class
|
|
237
|
+
exported from `agents`.
|
|
238
|
+
|
|
239
|
+
Lifecycle establishes the `getCurrentAgent()` context only while it invokes
|
|
240
|
+
host hooks. Capability hooks run outside that ambient context and use their own
|
|
241
|
+
`this`, hook arguments, and explicitly supplied dependencies.
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { getCurrentAgent } from "agents/lifecycle";
|
|
245
|
+
|
|
246
|
+
function currentRequestOrigin(): string | undefined {
|
|
247
|
+
const { request } = getCurrentAgent();
|
|
248
|
+
return request ? new URL(request.url).origin : undefined;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export class MyObject extends DurableObject<Env> {
|
|
252
|
+
readonly lifecycle = Lifecycle.install(this);
|
|
253
|
+
|
|
254
|
+
onRequest(): Response {
|
|
255
|
+
return Response.json({ origin: currentRequestOrigin() });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Pass the concrete host class when shared host code needs its additional APIs:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
const { agent: object } = getCurrentAgent<MyObject>();
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Host context values follow the invocation:
|
|
267
|
+
|
|
268
|
+
- `onStart` and `onAlarm`: object;
|
|
269
|
+
- `onRequest`: object and request;
|
|
270
|
+
- `onConnect`: object, connection, and upgrade request;
|
|
271
|
+
- `onMessage`, `onClose`, and `onError`: object and connection.
|
|
272
|
+
|
|
273
|
+
`getConnectionTags(connection, { request })` remains argument-driven because it
|
|
274
|
+
already receives both values explicitly. The root `agents` package continues
|
|
275
|
+
to export `getCurrentAgent()` for the `Agent` class as a compatibility alias.
|
|
276
|
+
|
|
277
|
+
## WebSockets always hibernate
|
|
278
|
+
|
|
279
|
+
The lifecycle always uses Cloudflare's WebSocket Hibernation API. Idle clients
|
|
280
|
+
remain connected while the Durable Object can leave memory. When a message
|
|
281
|
+
wakes the object, its constructor and lifecycle startup run again before
|
|
282
|
+
`onMessage`.
|
|
283
|
+
|
|
284
|
+
State needed after a wake must be stored durably or through connection state:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
onConnect(connection: Connection): void {
|
|
288
|
+
connection.setState({ authenticated: true });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
onMessage(connection: Connection<{ authenticated: boolean }>): void {
|
|
292
|
+
console.log(connection.state?.authenticated);
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
There is no non-hibernating mode.
|
|
297
|
+
|
|
298
|
+
## Native RPC
|
|
299
|
+
|
|
300
|
+
Native Durable Object RPC does not pass through `fetch`. An RPC method that
|
|
301
|
+
requires initialized capabilities starts the lifecycle explicitly:
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
async runTask(): Promise<void> {
|
|
305
|
+
await this.lifecycle.start();
|
|
306
|
+
// initialized work
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Agent's internal RPC entry points already enforce this boundary.
|
|
311
|
+
|
|
312
|
+
## Object names
|
|
313
|
+
|
|
314
|
+
Use `idFromName()` or `getByName()`. The lifecycle reads the authoritative name
|
|
315
|
+
from `ctx.id.name` and exposes it as `lifecycle.name`.
|
|
316
|
+
|
|
317
|
+
For migration only, the lifecycle can read an existing `__ps_name` record
|
|
318
|
+
written by an older PartyServer release. It never writes that key. Deprecated
|
|
319
|
+
name headers and bootstrap methods are not supported.
|
|
320
|
+
|
|
321
|
+
If a name cannot be resolved, the error covers named addressing, updating local
|
|
322
|
+
Wrangler/workerd and the compatibility date, unsupported raw IDs and oversized
|
|
323
|
+
names, and rescheduling alarms created before 2026-03-15.
|
|
@@ -86,7 +86,7 @@ type Task = {
|
|
|
86
86
|
externalJobId?: string;
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
-
export class ProjectManager extends Agent<ProjectState> {
|
|
89
|
+
export class ProjectManager extends Agent<Env, ProjectState> {
|
|
90
90
|
initialState: ProjectState = {
|
|
91
91
|
name: "",
|
|
92
92
|
status: "planning",
|
|
@@ -115,7 +115,7 @@ The pattern extends naturally to any event source that can reach a Worker — an
|
|
|
115
115
|
The agent does not need to be "started" or "deployed" separately for each wake source — they all route to the same Durable Object instance. The agent's identity (its name) is the routing key.
|
|
116
116
|
|
|
117
117
|
```typescript
|
|
118
|
-
export class ProjectManager extends Agent<ProjectState> {
|
|
118
|
+
export class ProjectManager extends Agent<Env, ProjectState> {
|
|
119
119
|
async onStart() {
|
|
120
120
|
// Daily deadline check at 9am UTC — idempotent, safe across restarts
|
|
121
121
|
await this.schedule(
|
|
@@ -163,7 +163,7 @@ Sometimes an agent needs to do work that takes longer than the idle eviction win
|
|
|
163
163
|
`keepAlive()` prevents this by creating a heartbeat that resets the inactivity timer:
|
|
164
164
|
|
|
165
165
|
```typescript
|
|
166
|
-
export class ProjectManager extends Agent<ProjectState> {
|
|
166
|
+
export class ProjectManager extends Agent<Env, ProjectState> {
|
|
167
167
|
async generateProjectPlan(goal: string) {
|
|
168
168
|
const result = await this.keepAliveWhile(async () => {
|
|
169
169
|
const plan = await this.callLLM(`Create a project plan for: ${goal}`);
|
|
@@ -223,7 +223,7 @@ side effects. If recovery succeeds, return a recovery result from
|
|
|
223
223
|
aborted, failed, or intentionally left interrupted.
|
|
224
224
|
|
|
225
225
|
```typescript
|
|
226
|
-
export class ProjectManager extends Agent<ProjectState> {
|
|
226
|
+
export class ProjectManager extends Agent<Env, ProjectState> {
|
|
227
227
|
async executeTask(task: Task) {
|
|
228
228
|
await this.runFiber(`task:${task.id}`, async (ctx) => {
|
|
229
229
|
const resources = await this.gatherResources(task);
|
|
@@ -268,7 +268,7 @@ The project manager frequently kicks off work that takes far longer than any sin
|
|
|
268
268
|
The project manager starts a CI pipeline for a task. The pipeline takes 20 minutes. Rather than holding a connection open, the agent registers its own URL as the callback and goes to sleep:
|
|
269
269
|
|
|
270
270
|
```typescript
|
|
271
|
-
export class ProjectManager extends Agent<ProjectState> {
|
|
271
|
+
export class ProjectManager extends Agent<Env, ProjectState> {
|
|
272
272
|
async startCIPipeline(task: Task) {
|
|
273
273
|
const response = await fetch("https://ci.example.com/api/pipelines", {
|
|
274
274
|
method: "POST",
|
|
@@ -307,7 +307,7 @@ export class ProjectManager extends Agent<ProjectState> {
|
|
|
307
307
|
Not every external service supports callbacks. When the project manager submits a video asset for generation, it needs to check back periodically until the job completes:
|
|
308
308
|
|
|
309
309
|
```typescript
|
|
310
|
-
export class ProjectManager extends Agent<ProjectState> {
|
|
310
|
+
export class ProjectManager extends Agent<Env, ProjectState> {
|
|
311
311
|
async startVideoGeneration(task: Task) {
|
|
312
312
|
const response = await fetch("https://video-api.example.com/generate", {
|
|
313
313
|
method: "POST",
|
|
@@ -354,7 +354,7 @@ export class ProjectManager extends Agent<ProjectState> {
|
|
|
354
354
|
A production deployment involves multiple steps that must each retry independently — build, test, stage, promote. The project manager should not manage these steps internally; it delegates to a [Workflow](./workflows.md) that handles retries and step sequencing:
|
|
355
355
|
|
|
356
356
|
```typescript
|
|
357
|
-
export class ProjectManager extends Agent<ProjectState> {
|
|
357
|
+
export class ProjectManager extends Agent<Env, ProjectState> {
|
|
358
358
|
async startDeployment(task: Task) {
|
|
359
359
|
const instanceId = await this.runWorkflow("DEPLOY_WORKFLOW", {
|
|
360
360
|
taskId: task.id,
|
|
@@ -422,7 +422,7 @@ type PlanStep = {
|
|
|
422
422
|
result?: unknown;
|
|
423
423
|
};
|
|
424
424
|
|
|
425
|
-
export class ProjectManager extends Agent<ProjectState> {
|
|
425
|
+
export class ProjectManager extends Agent<Env, ProjectState> {
|
|
426
426
|
async createPlan(goal: string) {
|
|
427
427
|
const steps = await this.keepAliveWhile(async () => {
|
|
428
428
|
return this.callLLM(`
|
|
@@ -508,7 +508,7 @@ This pattern has several advantages for long-running agents:
|
|
|
508
508
|
A project manager does not do everything itself. It delegates specialized work to sub-agents — child Durable Objects (facets) spawned under the parent. Each facet has its own isolated SQLite state and runs in parallel, but stays colocated on the same machine as the parent.
|
|
509
509
|
|
|
510
510
|
```typescript
|
|
511
|
-
export class ProjectManager extends Agent<ProjectState> {
|
|
511
|
+
export class ProjectManager extends Agent<Env, ProjectState> {
|
|
512
512
|
async delegateTask(task: Task) {
|
|
513
513
|
// Get a stub to a specialized agent (same DO namespace, unique name)
|
|
514
514
|
const researcher = await this.subAgent(
|
|
@@ -537,7 +537,7 @@ For chat-oriented sub-agents, [Think](https://github.com/cloudflare/agents/blob/
|
|
|
537
537
|
|
|
538
538
|
The patterns above handle the project manager's coordination work — scheduling, delegating, polling. But the project manager also uses an LLM directly: generating plans, summarizing progress, drafting status emails. Those LLM calls stream tokens over a connection that cannot be resumed if the agent is evicted mid-response.
|
|
539
539
|
|
|
540
|
-
For chat-oriented agents built on `AIChatAgent`, this is an even sharper problem — the user is watching the response stream in real time and sees it stop mid-sentence.
|
|
540
|
+
For chat-oriented agents built on `AIChatAgent` or `Think`, this is an even sharper problem — the user is watching the response stream in real time and sees it stop mid-sentence. Durable recovery wraps every chat turn in a `runFiber`, providing automatic `keepAlive` during streaming and a recovery hook when the agent restarts:
|
|
541
541
|
|
|
542
542
|
```typescript
|
|
543
543
|
import { AIChatAgent } from "@cloudflare/ai-chat";
|
|
@@ -547,8 +547,6 @@ import type {
|
|
|
547
547
|
} from "@cloudflare/ai-chat";
|
|
548
548
|
|
|
549
549
|
class ProjectChat extends AIChatAgent<Env> {
|
|
550
|
-
override chatRecovery = true;
|
|
551
|
-
|
|
552
550
|
override async onChatRecovery(
|
|
553
551
|
ctx: ChatRecoveryContext
|
|
554
552
|
): Promise<ChatRecoveryOptions> {
|
|
@@ -573,7 +571,7 @@ The right recovery strategy depends on the LLM provider:
|
|
|
573
571
|
|
|
574
572
|
For a complete multi-provider implementation with full code for each strategy, see the [`forever-chat` example](https://github.com/cloudflare/agents/tree/main/experimental/forever-chat) and the [`forever.md` design doc](https://github.com/cloudflare/agents/tree/main/experimental/forever.md).
|
|
575
573
|
|
|
576
|
-
[Think](https://github.com/cloudflare/agents/blob/main/docs/think/index.md)
|
|
574
|
+
`AIChatAgent` and [Think](https://github.com/cloudflare/agents/blob/main/docs/think/index.md) always enable durable recovery. The default path persists partial output and auto-continues or retries the turn when safe, so many apps do not need a custom hook. Override `onChatRecovery` when a provider has a better recovery strategy, or configure `chatRecovery = { maxAttempts, terminalMessage, onExhausted }` to tune the terminal user experience.
|
|
577
575
|
|
|
578
576
|
If the agent is interrupted before any assistant stream chunks are written, there is no partial assistant message to continue. When the latest persisted message is still the unanswered user message from that turn, chat recovery retries the turn automatically unless `onChatRecovery` returns `{ continue: false }`.
|
|
579
577
|
|
|
@@ -586,7 +584,7 @@ An agent that runs for months accumulates data: conversation history, timeline e
|
|
|
586
584
|
Schedule periodic cleanup to prune old data and archive completed work:
|
|
587
585
|
|
|
588
586
|
```typescript
|
|
589
|
-
export class ProjectManager extends Agent<ProjectState> {
|
|
587
|
+
export class ProjectManager extends Agent<Env, ProjectState> {
|
|
590
588
|
async onStart() {
|
|
591
589
|
await this.schedule("0 0 * * *", "housekeeping", {}, { idempotent: true });
|
|
592
590
|
}
|
|
@@ -634,7 +632,7 @@ For simpler cases: keep only the last N messages in the active context (sliding
|
|
|
634
632
|
A long-running agent eventually completes its purpose. The project ships, the investigation concludes, the monitoring window closes. Clean up explicitly:
|
|
635
633
|
|
|
636
634
|
```typescript
|
|
637
|
-
export class ProjectManager extends Agent<ProjectState> {
|
|
635
|
+
export class ProjectManager extends Agent<Env, ProjectState> {
|
|
638
636
|
async completeProject() {
|
|
639
637
|
// Cancel remaining schedules
|
|
640
638
|
const schedules = await this.listSchedules();
|
package/docs/mcp-client.md
CHANGED
|
@@ -13,6 +13,61 @@ The MCP client capability lets your agent:
|
|
|
13
13
|
|
|
14
14
|
> **Note:** This page covers connecting to MCP servers as a client. To create your own MCP server, see [Creating MCP Servers](./mcp-servers.md).
|
|
15
15
|
|
|
16
|
+
## Compose the MCP client with a Durable Object
|
|
17
|
+
|
|
18
|
+
`MCPClientManager` is a lifecycle capability. A class can extend the platform
|
|
19
|
+
`DurableObject` directly and install the manager without extending `Agent`:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { DurableObject } from "cloudflare:workers";
|
|
23
|
+
import { Lifecycle } from "agents/lifecycle";
|
|
24
|
+
import { MCPClientManager } from "agents/mcp/client";
|
|
25
|
+
|
|
26
|
+
export class MyObject extends DurableObject<Env> {
|
|
27
|
+
readonly mcp = new MCPClientManager("my-object", "1.0.0");
|
|
28
|
+
|
|
29
|
+
readonly lifecycle = Lifecycle.install(this).use(this.mcp);
|
|
30
|
+
|
|
31
|
+
onRequest() {
|
|
32
|
+
return Response.json({ tools: this.mcp.listTools() });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The lifecycle calls the manager automatically:
|
|
38
|
+
|
|
39
|
+
- `onStart()` initializes its schema and restores persisted connections before
|
|
40
|
+
the host handles work.
|
|
41
|
+
- `onRequest()` intercepts registered OAuth callback URLs before the host's
|
|
42
|
+
request handler.
|
|
43
|
+
|
|
44
|
+
Do not call these hooks manually. For a native Durable Object RPC method,
|
|
45
|
+
which bypasses `fetch`, call `await this.lifecycle.start()` before using the
|
|
46
|
+
manager. Explicit cleanup remains available through `mcp.dispose()`; Durable
|
|
47
|
+
Objects do not provide an eviction callback.
|
|
48
|
+
|
|
49
|
+
The manager does not require a particular OAuth callback route. Pass the exact
|
|
50
|
+
callback URL to `registerServer()` and route that request to the same named
|
|
51
|
+
Durable Object. The manager persists the URL and only intercepts a callback
|
|
52
|
+
whose origin and pathname match it.
|
|
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
|
+
`Agent` installs this same capability object directly. Existing `this.mcp`,
|
|
68
|
+
`addMcpServer()`, `removeMcpServer()`, and `getMcpServers()` APIs remain
|
|
69
|
+
available.
|
|
70
|
+
|
|
16
71
|
## Quick Start
|
|
17
72
|
|
|
18
73
|
Install the exact MCP client peer used by this Agents release:
|
package/docs/mcp-servers.md
CHANGED
|
@@ -14,6 +14,12 @@ This guide covers the different ways to create MCP servers with the Agents SDK a
|
|
|
14
14
|
- **`McpAgent`** is a retained, feature-frozen SDK v1 path for existing stateful deployments. New servers should use `createMcpHandler()`.
|
|
15
15
|
- **Raw transport** gives you low-level control if the standard handler lifecycle is not suitable.
|
|
16
16
|
|
|
17
|
+
`McpAgent` and the other retained SDK v1 registration APIs use the upstream
|
|
18
|
+
Zod-based schema contract. Define their tool input and output schemas as Zod
|
|
19
|
+
shapes; AI SDK flexible-schema adapters are not accepted. For a new MCP server
|
|
20
|
+
that needs Standard Schema support, use `@modelcontextprotocol/server` v2 with
|
|
21
|
+
`createMcpHandler()`.
|
|
22
|
+
|
|
17
23
|
## Stateless MCP Server with `createMcpHandler()`
|
|
18
24
|
|
|
19
25
|
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:
|