agents-0-22-0-14-b
Claude Code's read-only tools, web search on, ludion sync on
Question
Where does MCPClientManager get its storage from in the Cloudflare Agents SDK?
Answer key
- A correct answer says
- `MCPClientManager` gets storage from the Lifecycle it is installed on; the `storage` option was removed, so standalone construction with explicit DurableObjectStorage is not supported.
- The stale answer
- Construct `MCPClientManager` with a `storage` option pointing at DurableObjectStorage.
Answer
In the installed `agents` 0.27.0, you don't pass storage to `MCPClientManager`. It extends `LifecycleCapability`, and its constructor only takes `(name, version, options?)`, where `options` holds just `env` and `createAuthProvider`. When the Durable Object lifecycle installs it (for example as `agent.mcp`), it gets the host Durable Object's `DurableObjectStorage` through `this.lifecycle.storage` (`LifecycleServices.storage`).
It uses that storage for three things:
- **Server list:** it keeps the `cf_agents_mcp_servers` table through `storage.sql.exec`, plus a schema-version key.
- **OAuth:** it builds its `DurableObjectOAuthClientProvider` on the same storage.
- **Renames:** it moves OAuth keys when a server id changes.
This is from the installed package's type definitions and compiled code (`node_modules/agents/dist/client-1tEQ1Oqz.js:1304-1353`). Older examples that pass storage into the constructor don't match this version.Grade
correct
The judge's reason: It says storage comes from the installing lifecycle (this.lifecycle.storage), that the constructor takes no storage option, and that passing storage to the constructor is outdated.
Session log
Tool call: Grep
{ "pattern": "class MCPClientManager|storage", "path": "C:\\Users\\haya0\\AppData\\Local\\Temp\\ludion-bench-Lmdynx\\work\\node_modules\\agents\\dist", "glob": "*.d.ts", "output_mode": "content", "head_limit": 40 }Tool output
node_modules\agents\dist\browser-tmkcAAI3.d.ts:137: * {@link DurableBrowserSessionStore} over the host object's storage, which node_modules\agents\dist\browser-tmkcAAI3.d.ts:203: * object's own storage. It schedules nothing: Browser Run reclaims idle node_modules\agents\dist\browser-tmkcAAI3.d.ts:210: * Store discipline: locks wrap storage operations only — liveness probes and node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts:42: * Agents SDK) to namespace or wrap internal state storage. node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts:167: /** Stable equality and storage key. */ readonly key: string /** Transport-owned serialized address. */; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts:228: readonly storage: DurableObjectStorage; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts:347: * Capabilities extending `LifecycleCapability` receive the standard storage, node_modules\agents\dist\connector-Cl-VqPmA.d.ts:320: * all holders using the same key. Held only around storage reads/writes — node_modules\agents\dist\connector-Cl-VqPmA.d.ts:336: private readonly storage; node_modules\agents\dist\connector-Cl-VqPmA.d.ts:337: constructor(storage: DurableObjectStorage); node_modules\agents\dist\browser\ai.d.ts:17: * storage — so the tool must be created from inside a Durable Object node_modules\agents\dist\browser\ai.d.ts:64: * {@link DurableBrowserSessionStore} over `ctx.storage`. node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:478: * Table and column names are storage-frozen — never rename them. node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:663: * Nothing here renames any wire- or storage-visible identifier: the node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:745: * `ctx.facets.delete` to wipe the target's storage. node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:758: * own children; storage is preserved. node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:763: * storage. Transitively deletes the child's own children. node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:933: * inside the child's isolate, so every storage write and `onStart()` node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:983: * its storage is preserved. Transitively aborts the child's own node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:991: * its storage. Transitively deletes the child's own children. node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:999: * auto-maintained SQLite registry in the parent's storage. node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:1054: * Pluggable storage adapter for persisting `WorkerTransport` state across node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:1057: * A typical implementation reads/writes a single key on `this.ctx.storage` node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:1078: * Optional storage adapter for persisting transport state across DO node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:1082: storage?: MCPStorageApi; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:1086: private readonly _storage?; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:1224: * stream from `eventId` without a storage hit. node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:1230: * immediately after writing the close frame, so storage growth is node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:1255: /** DO storage caps multi-key delete at 128. */ node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:1261: private readonly storage; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:1262: /** In-memory seq counters per stream, rehydrated lazily from storage. */ node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:1265: constructor(storage: DurableObjectStorage); node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:1283: * storage cap) so we never load the entire event log into memory. node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:1390: * {@link DurableObjectEventStore} backed by this agent's storage, node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:1397: * background cleanup — storage cost is bounded by the in-flight node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:2568://#region src/mcp/client/storage.d.ts node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:2638: * Normalize a caller-supplied MCP server id into a stable, storage- and node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:2645: * - as a key on the `mcpConnections` map and OAuth provider storage node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:2769:declare class MCPClientManager extends LifecycleCapability { node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:2829: * - OAuth-related storage keys under `/{clientName}/{oldId}/...` [Showing results with pagination = limit: 40]Tool call: Read
{ "file_path": "C:\\Users\\haya0\\AppData\\Local\\Temp\\ludion-bench-Lmdynx\\work\\node_modules\\agents\\dist\\agent-routing-Bhdbvf7s.d.ts", "offset": 2740, "limit": 90 }Tool output
2740 url?: MCPClientElicitationHandler; 2741 }; 2742 /** Dependencies used by {@link MCPClientManager} across Durable Object wakes. */ 2743 type MCPClientManagerOptions = { 2744 /** 2745 * Runtime bindings used to restore persisted RPC MCP connections. 2746 * Required when the durable catalog contains `rpc://` servers. 2747 */ 2748 readonly env?: Cloudflare.Env /** Construct the OAuth provider used for a persisted HTTP server. */; 2749 readonly createAuthProvider?: (callbackUrl: string) => AgentMcpOAuthProvider; 2750 }; 2751 /** 2752 * Filter options for scoping tools, prompts, resources, and resource templates 2753 * to a subset of connected MCP servers. All specified criteria are AND'd together. 2754 */ 2755 type MCPServerFilter = { 2756 /** Include only connections matching this server ID (or IDs). */ serverId?: 2757 | string 2758 | string[] /** Include only connections whose stored name matches (or is in) this value. */; 2759 serverName?: 2760 | string 2761 | string[] /** Include only connections currently in this state (or states). */; 2762 state?: MCPConnectionState | MCPConnectionState[]; 2763 }; 2764 /** 2765 * A Durable Object capability that persists and aggregates MCP client 2766 * connections. Installing it directly on a Lifecycle (outside `agent.mcp`) 2767 * is experimental: that capability surface may change before stabilizing. 2768 */ 2769 declare class MCPClientManager extends LifecycleCapability { 2770 private readonly _name; 2771 private readonly _version; 2772 mcpConnections: Record<string, MCPClientConnection>; 2773 /** Cache only the current catalog so old schema graphs are not retained. */ 2774 private readonly _aiToolSchemas; 2775 private _didWarnAboutUnstableGetAITools; 2776 private _oauthCallbackConfig?; 2777 private _connectionDisposables; 2778 private readonly _env; 2779 private readonly _createAuthProviderFn; 2780 private _isRestored; 2781 private _pendingConnections; 2782 private _elicitationHandlers?; 2783 /** @internal Protected for testing purposes. */ 2784 protected readonly _onObservabilityEvent: Emitter<MCPObservabilityEvent>; 2785 readonly onObservabilityEvent: Event<MCPObservabilityEvent>; 2786 private readonly _onServerStateChanged; 2787 /** 2788 * Event that fires whenever any MCP server state changes (registered, connected, removed, etc.) 2789 * This is useful for broadcasting server state to clients. 2790 */ 2791 readonly onServerStateChanged: Event<void>; 2792 /** 2793 * Construct a reusable Durable Object MCP client capability. 2794 * 2795 * @param _name - MCP client implementation name sent during negotiation. 2796 * @param _version - MCP client implementation version sent during negotiation. 2797 * @param options - Optional runtime bindings and OAuth configuration. 2798 */ 2799 constructor( 2800 _name: string, 2801 _version: string, 2802 options?: MCPClientManagerOptions 2803 ); 2804 /** Restore persisted HTTP and RPC connections before the host handles work. */ 2805 onStart(): Promise<void>; 2806 /** Intercept a registered MCP OAuth callback request. */ 2807 onRequest(context: CapabilityRequestContext): Promise<Response | undefined>; 2808 private oauthCallbackResponse; 2809 /** 2810 * Scope the manager-level elicitation handler to a single connection. 2811 * Returns undefined when no handler is configured so the connection keeps 2812 * its default throwing behavior. 2813 */ 2814 private scopedElicitationHandlers; 2815 private sql; 2816 private saveServerToStorage; 2817 private removeServerFromStorage; 2818 /** 2819 * Rename a server's id, in-place, across every place the id is used as a 2820 * key. Used to JIT-migrate servers that were originally registered under an 2821 * auto-generated nanoid to a caller-supplied stable id (see 2822 * `Agent.addMcpServer`'s `{ id }` option). 2823 * 2824 * Migrates: 2825 * - the `cf_agents_mcp_servers` row (primary key) 2826 * - the in-memory `mcpConnections` map key 2827 * - the connection disposables map key 2828 * - the attached `authProvider.serverId`, if any 2829 * - OAuth-related storage keys under `/{clientName}/{oldId}/...`Tool call: Grep
{ "pattern": "LifecycleCapability|storage|sql", "path": "C:\\Users\\haya0\\AppData\\Local\\Temp\\ludion-bench-Lmdynx\\work\\node_modules\\agents\\dist\\capability-runner-O5VbQzqg.d.ts", "output_mode": "content", "-C": 3 }Tool output
39- * 40- * This property is configurable, meaning it can be redefined via 41- * `Object.defineProperty` by downstream consumers (e.g. the Cloudflare 42: * Agents SDK) to namespace or wrap internal state storage. 43- */ 44- state: ConnectionState<TState>; 45- /** -- 164-//#region src/lifecycle/capability.d.ts 165-/** Opaque address understood by a Lifecycle routing transport. */ 166-type LifecycleRouteAddress = { 167: /** Stable equality and storage key. */ readonly key: string /** Transport-owned serialized address. */; 168- readonly data: string; 169-}; 170-/** Context supplied with a routed capability message. */ -- 225-type LifecycleServices = { 226- /** The host's Durable Object name; see `Lifecycle.name`. */ readonly name: string /** The host class name, as exported from the Worker. */; 227- readonly className: string; 228: readonly storage: DurableObjectStorage; 229- readonly sockets: LifecycleSockets; 230- /** 231- * Resolve once Lifecycle startup has finished, starting it if needed, and -- 278- * 279- * @experimental The API surface may change before stabilizing. 280- */ 281:declare abstract class LifecycleCapability<Props extends object = object> { 282- readonly capabilityId: string; 283- /** How this capability claims traffic; see {@link DurableObjectCapability.claims}. */ 284- readonly claims: "selective" | "catch-all"; -- 344-/** 345- * A capability installed into a Durable Object lifecycle. 346- * 347: * Capabilities extending `LifecycleCapability` receive the standard storage, 348- * readiness, alarm, event, and routing surface. Host-specific bindings and 349- * protocol adapters remain explicit constructor dependencies. Hook parameters 350- * carry only phase data; hooks do not run in ambient host context — a -- 486- MemoryLimitContext as o, 487- LifecycleServices as p, 488- CapabilityWebSocketUpgradeContext as r, 489: LifecycleCapability as s, 490- CapabilityRequestContext as t, 491- LifecycleRouteAddress as u, 492- LifecycleJobOutcome as v,Tool call: Read
{ "file_path": "C:\\Users\\haya0\\AppData\\Local\\Temp\\ludion-bench-Lmdynx\\work\\node_modules\\agents\\dist\\capability-runner-O5VbQzqg.d.ts", "offset": 281, "limit": 40 }Tool output
281 declare abstract class LifecycleCapability<Props extends object = object> { 282 readonly capabilityId: string; 283 /** How this capability claims traffic; see {@link DurableObjectCapability.claims}. */ 284 readonly claims: "selective" | "catch-all"; 285 protected constructor(capabilityId: string); 286 /** Default startup hook; capabilities override when they own startup work. */ 287 onStart(_context: CapabilityStartContext<Props>): void; 288 /** Standard services when installed, or undefined in isolated unit tests. */ 289 protected get lifecycleServices(): LifecycleServices | undefined; 290 /** Standard services supplied when Lifecycle installs this capability. */ 291 protected get lifecycle(): LifecycleServices; 292 } 293 //#endregion 294 //#region src/lifecycle/capability-runner.d.ts 295 type MaybePromise<T> = T | Promise<T>; 296 /** One best-effort event published by a Lifecycle capability. */ 297 type LifecycleEvent = { 298 /** Stable capability or subsystem name. */ readonly source: string /** Stable event name within that source. */; 299 readonly type: string /** Event-specific data. */; 300 readonly payload: unknown; 301 }; 302 /** Context supplied when durable capabilities start. */ 303 type CapabilityStartContext<Props extends object = object> = { 304 /** Properties supplied while resolving the Durable Object. */ readonly props: 305 | Props 306 | undefined; 307 }; 308 /** Context supplied when durable capabilities inspect an HTTP request. */ 309 type CapabilityRequestContext = { 310 /** The request entering the Durable Object. */ readonly request: Request; 311 }; 312 /** Context supplied when a capability inspects a WebSocket upgrade. */ 313 type CapabilityWebSocketUpgradeContext = { 314 /** The WebSocket upgrade request entering the Durable Object. */ readonly request: Request; 315 }; 316 /** 317 * Context supplied when the alarm memory-limit circuit breaker records a 318 * strike (#1825), to capabilities and the host alike. 319 */ 320 type MemoryLimitContext = {Tool call: Grep
{ "pattern": "lifecycle\\.storage", "path": "C:\\Users\\haya0\\AppData\\Local\\Temp\\ludion-bench-Lmdynx\\work\\node_modules\\agents\\dist", "glob": "*.js", "output_mode": "content", "head_limit": 8 }Tool output
node_modules\agents\dist\client-1tEQ1Oqz.js:1304: if ((await this.lifecycle.storage.get(MCP_SCHEMA_VERSION_KEY) ?? 0) < CURRENT_MCP_SCHEMA_VERSION) { node_modules\agents\dist\client-1tEQ1Oqz.js:1305: ensureMcpServerTable(this.lifecycle.storage); node_modules\agents\dist\client-1tEQ1Oqz.js:1306: await this.lifecycle.storage.put(MCP_SCHEMA_VERSION_KEY, CURRENT_MCP_SCHEMA_VERSION); node_modules\agents\dist\client-1tEQ1Oqz.js:1353: return [...this.lifecycle.storage.sql.exec(query, ...bindings)]; node_modules\agents\dist\client-1tEQ1Oqz.js:1398: const keys = await this.lifecycle.storage.list({ prefix: oldPrefix }); node_modules\agents\dist\client-1tEQ1Oqz.js:1407: await this.lifecycle.storage.put(writes); node_modules\agents\dist\client-1tEQ1Oqz.js:1408: await this.lifecycle.storage.delete(deletes); node_modules\agents\dist\client-1tEQ1Oqz.js:1585: const authProvider = new DurableObjectOAuthClientProvider(this.lifecycle.storage, clientName, callbackUrl); [Showing results with pagination = limit: 8]Agent
In the installed `agents` 0.27.0, you don't pass storage to `MCPClientManager`. It extends `LifecycleCapability`, and its constructor only takes `(name, version, options?)`, where `options` holds just `env` and `createAuthProvider`. When the Durable Object lifecycle installs it (for example as `agent.mcp`), it gets the host Durable Object's `DurableObjectStorage` through `this.lifecycle.storage` (`LifecycleServices.storage`). It uses that storage for three things: - **Server list:** it keeps the `cf_agents_mcp_servers` table through `storage.sql.exec`, plus a schema-version key. - **OAuth:** it builds its `DurableObjectOAuthClientProvider` on the same storage. - **Renames:** it moves OAuth keys when a server id changes. This is from the installed package's type definitions and compiled code (`node_modules/agents/dist/client-1tEQ1Oqz.js:1304-1353`). Older examples that pass storage into the constructor don't match this version.