agents-0-22-0-14-b
Claude Code's read-only tools, web search on, ludion sync off
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` package, `MCPClientManager` doesn't take storage as a constructor argument. Its options are only `env` and `createAuthProvider`. It extends `LifecycleCapability` and gets storage through `this.lifecycle.storage`. When the host Lifecycle (an Agent or Durable Object) installs the capability, it supplies a `LifecycleServices` object whose `storage` field is the host Durable Object's `DurableObjectStorage`, i.e. its `ctx.storage`.
`onStart()` uses that storage to check the MCP schema version and create the MCP server table if needed (`node_modules/agents/dist/client-1tEQ1Oqz.js:1304-1306`). It then restores the persisted HTTP and RPC connections from the same storage. If the manager isn't installed on a Lifecycle, as in isolated unit tests, `lifecycleServices` is undefined, so there's no storage to use.Grade
correct
The judge's reason: It says storage comes from the host Lifecycle via lifecycle services, that there is no storage constructor option, and that a standalone manager has no storage.
Session log
Tool call: Grep
{ "pattern": "class MCPClientManager", "output_mode": "content", "-A": 40 }Tool output
node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts:2769:declare class MCPClientManager extends LifecycleCapability { node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2770- private readonly _name; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2771- private readonly _version; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2772- mcpConnections: Record<string, MCPClientConnection>; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2773- /** Cache only the current catalog so old schema graphs are not retained. */ node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2774- private readonly _aiToolSchemas; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2775- private _didWarnAboutUnstableGetAITools; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2776- private _oauthCallbackConfig?; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2777- private _connectionDisposables; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2778- private readonly _env; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2779- private readonly _createAuthProviderFn; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2780- private _isRestored; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2781- private _pendingConnections; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2782- private _elicitationHandlers?; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2783- /** @internal Protected for testing purposes. */ node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2784- protected readonly _onObservabilityEvent: Emitter<MCPObservabilityEvent>; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2785- readonly onObservabilityEvent: Event<MCPObservabilityEvent>; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2786- private readonly _onServerStateChanged; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2787- /** node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2788- * Event that fires whenever any MCP server state changes (registered, connected, removed, etc.) node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2789- * This is useful for broadcasting server state to clients. node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2790- */ node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2791- readonly onServerStateChanged: Event<void>; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2792- /** node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2793- * Construct a reusable Durable Object MCP client capability. node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2794- * node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2795- * @param _name - MCP client implementation name sent during negotiation. node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2796- * @param _version - MCP client implementation version sent during negotiation. node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2797- * @param options - Optional runtime bindings and OAuth configuration. node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2798- */ node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2799- constructor( node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2800- _name: string, node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2801- _version: string, node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2802- options?: MCPClientManagerOptions node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2803- ); node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2804- /** Restore persisted HTTP and RPC connections before the host handles work. */ node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2805- onStart(): Promise<void>; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2806- /** Intercept a registered MCP OAuth callback request. */ node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2807- onRequest(context: CapabilityRequestContext): Promise<Response | undefined>; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2808- private oauthCallbackResponse; node_modules\agents\dist\agent-routing-Bhdbvf7s.d.ts-2809- /** -- node_modules\agents\dist\client-1tEQ1Oqz.js.map:1:[Omitted long matching line]Tool call: Grep
{ "pattern": "type MCPClientManagerOptions|interface MCPClientManagerOptions|declare (abstract )?class LifecycleCapability|this\\.storage|ctx\\.storage|get storage", "path": "node_modules\\agents\\dist\\agent-routing-Bhdbvf7s.d.ts", "output_mode": "content", "-A": 25 }Tool output
1057: * A typical implementation reads/writes a single key on `this.ctx.storage` 1058- * inside a Durable Object or Agent. 1059- */ 1060-interface MCPStorageApi { 1061- get(): Promise<TransportState | undefined> | TransportState | undefined; 1062- set(state: TransportState): Promise<void> | void; 1063-} 1064-/** Shape of the persisted transport state. */ 1065-interface TransportState { 1066- sessionId?: string; 1067- initialized: boolean; 1068- initializeParams?: InitializeRequestParams; 1069-} 1070-interface WorkerTransportOptions extends WebStandardStreamableHTTPServerTransportOptions { 1071- /** 1072- * CORS options applied to every response and to OPTIONS preflight. 1073- * Defaults: `origin: *`, expose `mcp-session-id`, allow the standard MCP 1074- * methods/headers, max-age 86400. 1075- */ 1076- corsOptions?: CORSOptions; 1077- /** 1078- * Optional storage adapter for persisting transport state across DO 1079- * hibernation / restart. Use this to keep an MCP session alive across 1080- * Durable Object wake-ups. 1081- */ 1082- storage?: MCPStorageApi; -- 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- */ -- 6701: * this.ctx.storage, 6702- * this.name, 6703- * callbackUrl 6704- * ); 6705- * } 6706- * } 6707- * 6708- * @param callbackUrl The OAuth callback URL for the authorization flow 6709- * @returns An {@link AgentMcpOAuthProvider} instance used by {@link addMcpServer} 6710- */ 6711- createMcpOAuthProvider(callbackUrl: string): AgentMcpOAuthProvider; 6712- private broadcastMcpServers; 6713-} 6714-/** 6715- * Namespace for creating Agent instances 6716- * @template Agentic Type of the Agent class 6717- * @deprecated Use DurableObjectNamespace instead 6718- */ 6719-type AgentNamespace<Agentic extends Agent<Cloudflare.Env>> = 6720- DurableObjectNamespace<Agentic>; 6721-/** 6722- * Agent's durable context 6723- */ 6724-type AgentContext = DurableObjectState; 6725-type EmailRoutingOptions<Env> = AgentOptions<Env> & { 6726- resolver: EmailResolver<Env>;Agent
Let me check LifecycleCapability to see how storage is provided.Tool call: Grep
{ "pattern": "class LifecycleCapability", "path": "node_modules\\agents\\dist", "glob": "*.d.ts", "output_mode": "content", "-B": 15, "-A": 30 }Tool output
node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-266- * (Agent adds tracing span scope). Pass `scope` to make a live node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-267- * connection or request ambient for the callback. node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-268- */ node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-269- readonly runInHostContext: ( node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-270- fn: () => unknown, node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-271- scope?: LifecycleHostContextScope node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-272- ) => Promise<unknown>; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-273- readonly events: LifecycleEvents; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-274- readonly routes: LifecycleRoutes; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-275-}; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-276-/** node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-277- * Base class for capabilities that consume standard Lifecycle services. node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-278- * node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-279- * @experimental The API surface may change before stabilizing. node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-280- */ node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts:281:declare abstract class LifecycleCapability<Props extends object = object> { node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-282- readonly capabilityId: string; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-283- /** How this capability claims traffic; see {@link DurableObjectCapability.claims}. */ node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-284- readonly claims: "selective" | "catch-all"; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-285- protected constructor(capabilityId: string); node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-286- /** Default startup hook; capabilities override when they own startup work. */ node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-287- onStart(_context: CapabilityStartContext<Props>): void; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-288- /** Standard services when installed, or undefined in isolated unit tests. */ node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-289- protected get lifecycleServices(): LifecycleServices | undefined; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-290- /** Standard services supplied when Lifecycle installs this capability. */ node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-291- protected get lifecycle(): LifecycleServices; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-292-} node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-293-//#endregion node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-294-//#region src/lifecycle/capability-runner.d.ts node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-295-type MaybePromise<T> = T | Promise<T>; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-296-/** One best-effort event published by a Lifecycle capability. */ node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-297-type LifecycleEvent = { node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-298- /** Stable capability or subsystem name. */ readonly source: string /** Stable event name within that source. */; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-299- readonly type: string /** Event-specific data. */; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-300- readonly payload: unknown; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-301-}; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-302-/** Context supplied when durable capabilities start. */ node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-303-type CapabilityStartContext<Props extends object = object> = { node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-304- /** Properties supplied while resolving the Durable Object. */ readonly props: node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-305- | Props node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-306- | undefined; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-307-}; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-308-/** Context supplied when durable capabilities inspect an HTTP request. */ node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-309-type CapabilityRequestContext = { node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-310- /** The request entering the Durable Object. */ readonly request: Request; node_modules\agents\dist\capability-runner-O5VbQzqg.d.ts-311-};Tool call: Grep
{ "pattern": "type LifecycleServices = \\{", "path": "node_modules\\agents\\dist\\capability-runner-O5VbQzqg.d.ts", "output_mode": "content", "-A": 25 }Tool output
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 232- * reject if that startup fails. Calls made from inside startup resolve 233- * immediately. 234- * 235- * Await it first in any operation that can run before startup and starts 236- * work `onStart` also awaits, such as lazily opening a store. Native RPC 237- * bypasses the automatic startup on fetch, alarm and WebSocket events, so 238- * such an operation can begin that work outside startup. `onStart` then 239- * awaits it while startup holds the input gate (`blockConcurrencyWhile`), 240- * which holds back the work's timers and I/O, and startup times out after 241- * 30 seconds and resets the object. 242- */ 243- readonly ready: () => Promise<void>; 244- /** 245- * Startup state: `"zero"` before startup, `"starting"` while capability 246- * and host startup hooks run, `"started"` once `ready()` resolves without 247- * waiting. 248- */ 249- readonly status: () => LifecycleStatus; 250- /**Tool call: Grep
{ "pattern": "lifecycle\\.(storage|sql|ctx)\\w*", "path": "node_modules\\agents\\dist", "glob": "*.js", "output_mode": "content", "-o": true, "head_limit": 20 }Tool output
node_modules\agents\dist\client-1tEQ1Oqz.js:1304:lifecycle.storage node_modules\agents\dist\client-1tEQ1Oqz.js:1305:lifecycle.storage node_modules\agents\dist\client-1tEQ1Oqz.js:1306:lifecycle.storage node_modules\agents\dist\client-1tEQ1Oqz.js:1353:lifecycle.storage node_modules\agents\dist\client-1tEQ1Oqz.js:1398:lifecycle.storage node_modules\agents\dist\client-1tEQ1Oqz.js:1407:lifecycle.storage node_modules\agents\dist\client-1tEQ1Oqz.js:1408:lifecycle.storage node_modules\agents\dist\client-1tEQ1Oqz.js:1585:lifecycle.storage node_modules\agents\dist\browser\index.js:239:lifecycle.storage node_modules\agents\dist\browser\index.js:241:lifecycle.storage node_modules\agents\dist\harness\ai-sdk\index.js:406:lifecycle.storage node_modules\agents\dist\harness\think\index.js:727:lifecycle.storage node_modules\agents\dist\harness\think\index.js:1690:lifecycle.storage node_modules\agents\dist\harness\think\index.js:1806:lifecycle.storage node_modules\agents\dist\harness\pi\index.js:530:lifecycle.storage node_modules\agents\dist\harness\container\index.js:1042:lifecycle.storage node_modules\agents\dist\harness\container\index.js:1186:lifecycle.storage node_modules\agents\dist\harness\container\index.js:1199:lifecycle.storage node_modules\agents\dist\harness\container\index.js:1202:lifecycle.storage node_modules\agents\dist\harness\container\index.js:1223:lifecycle.storage [Showing results with pagination = limit: 20]Tool call: Read
{ "file_path": "C:\\Users\\haya0\\AppData\\Local\\Temp\\ludion-bench-62A7Aw\\work\\node_modules\\agents\\dist\\client-1tEQ1Oqz.js", "offset": 1298, "limit": 12 }Tool output
1298 this.onServerStateChanged = this._onServerStateChanged.event; 1299 this._env = options.env; 1300 this._createAuthProviderFn = options.createAuthProvider; 1301 } 1302 /** Restore persisted HTTP and RPC connections before the host handles work. */ 1303 async onStart() { 1304 if ((await this.lifecycle.storage.get(MCP_SCHEMA_VERSION_KEY) ?? 0) < CURRENT_MCP_SCHEMA_VERSION) { 1305 ensureMcpServerTable(this.lifecycle.storage); 1306 await this.lifecycle.storage.put(MCP_SCHEMA_VERSION_KEY, CURRENT_MCP_SCHEMA_VERSION); 1307 } 1308 await this.restoreConnectionsFromStorage(this._name); 1309 await restoreRpcConnections(this, this._env, this.getRpcServersFromStorage());Agent
In the installed `agents` package, `MCPClientManager` doesn't take storage as a constructor argument. Its options are only `env` and `createAuthProvider`. It extends `LifecycleCapability` and gets storage through `this.lifecycle.storage`. When the host Lifecycle (an Agent or Durable Object) installs the capability, it supplies a `LifecycleServices` object whose `storage` field is the host Durable Object's `DurableObjectStorage`, i.e. its `ctx.storage`. `onStart()` uses that storage to check the MCP schema version and create the MCP server table if needed (`node_modules/agents/dist/client-1tEQ1Oqz.js:1304-1306`). It then restores the persisted HTTP and RPC connections from the same storage. If the manager isn't installed on a Lifecycle, as in isolated unit tests, `lifecycleServices` is undefined, so there's no storage to use.