fix: export DurableObjectOAuthClientProvider and respect createMcpOAuthProvider override during restore#944
Conversation
…thProvider override during restore restoreConnectionsFromStorage() was hardcoding new DurableObjectOAuthClientProvider via MCPClientManager.createAuthProvider(), bypassing the Agent's createMcpOAuthProvider() override. This meant custom OAuth providers only worked for addMcpServer(), not after DO restarts. Inject a factory from the Agent into MCPClientManager so both code paths use the same override. Also exports DurableObjectOAuthClientProvider from the top-level agents package so users can reference/extend it in their overrides without fragile deep imports. Fixes #939, fixes #942 Co-authored-by: Cursor <cursoragent@cursor.com>
…rage pattern Co-authored-by: Cursor <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: 2dfa86e The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
|
/bonk review this pr please |
|
I've now reviewed all the changed files thoroughly. Here's my review: PR Review: fix: export DurableObjectOAuthClientProvider and respect createMcpOAuthProvider override during restoreOverall AssessmentThis is a clean, well-scoped bug fix. The two issues are closely related and the fix is minimal — only 24 lines of production code changed across 2 files. The approach is sound, tests are thorough, and backward compatibility is preserved. Code Changes
Observations
One minor concernIn the VerdictShip it. The fix is minimal, correct, backward-compatible, and well-tested. The changeset is appropriate as a patch bump. |
…thProvider override during restore (#944) * fix: export DurableObjectOAuthClientProvider and respect createMcpOAuthProvider override during restore restoreConnectionsFromStorage() was hardcoding new DurableObjectOAuthClientProvider via MCPClientManager.createAuthProvider(), bypassing the Agent's createMcpOAuthProvider() override. This meant custom OAuth providers only worked for addMcpServer(), not after DO restarts. Inject a factory from the Agent into MCPClientManager so both code paths use the same override. Also exports DurableObjectOAuthClientProvider from the top-level agents package so users can reference/extend it in their overrides without fragile deep imports. Fixes #939, fixes #942 Co-authored-by: Cursor <cursoragent@cursor.com> * docs: document DurableObjectOAuthClientProvider export and custom storage pattern Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
Fixes #939 and #942.
Two related issues reported by the same user building a multi-tenant agentic platform where OAuth tokens need to be shared across Durable Objects:
DurableObjectOAuthClientProvideris used internally but not exported from the top-levelagentspackage, forcing users to use fragile deep imports (agents/dist/mcp/do-oauth-client-provider.js) to reference or extend it.restoreConnectionsFromStorage()inMCPClientManagercreates auth providers via its own hardcodedcreateAuthProvider()method, bypassing the Agent'screateMcpOAuthProvider()override. Since DOs restart frequently (eviction, deploys, hibernation), the override is effectively broken for restored connections — OAuth state written to custom storage can't be found on restore, causing "State not found or already used" errors.Changes
Code (
packages/agents/)Export
DurableObjectOAuthClientProviderfromsrc/index.ts— changes the existingexport typeto a combined value + type export. Users can nowimport { DurableObjectOAuthClientProvider } from "agents".Add
createAuthProviderfactory toMCPClientManagerOptions— optional callback with signature(callbackUrl: string) => AgentMcpOAuthProvider, matching the existingAgent.createMcpOAuthProvidersignature.Wire the factory from Agent → MCPClientManager — the Agent constructor now passes
(callbackUrl) => this.createMcpOAuthProvider(callbackUrl)into the manager, so bothaddMcpServer()andrestoreConnectionsFromStorage()use the same override.restoreConnectionsFromStorage()delegates to the factory — when present, calls the factory instead of the hardcodednew DurableObjectOAuthClientProvider(this._storage, ...). Falls back to the existing behavior when no factory is provided (backward compatible for directMCPClientManagerusage in tests).Tests
client-manager.test.tscovering: factory called during restore,serverId/clientIdset by manager, fallback without factory, factory for OAuth-in-progress servers, factory for failed connection recreation, factory called per-server in mixed restore.create-oauth-provider.test.ts— usesTestCustomOAuthAgent(which overridescreateMcpOAuthProvider) to verify the override is respected duringrestoreConnectionsFromStorage, not justaddMcpServer.Docs (
docs/)mcp-client.mdto document theDurableObjectOAuthClientProviderexport and the custom storage backend pattern.Reviewer notes
(callbackUrl: string) => AgentMcpOAuthProvider— justcallbackUrl, matching the publiccreateMcpOAuthProviderAPI. Internal concerns (serverId,clientId) are set byMCPClientManagerafter calling the factory.authProvider.serverId = server.id/authProvider.clientId = server.client_idassignments after the factory call are idempotent with the fallback path (which already sets them increateAuthProvider). This keeps both paths consistent without branching the post-creation logic.Test plan
client-manager.test.tscreate-oauth-provider.test.tsMade with Cursor