Skip to content

fix: export DurableObjectOAuthClientProvider and respect createMcpOAuthProvider override during restore#944

Merged
threepointone merged 2 commits intomainfrom
fix/mcp-oauth-provider-restore
Feb 19, 2026
Merged

fix: export DurableObjectOAuthClientProvider and respect createMcpOAuthProvider override during restore#944
threepointone merged 2 commits intomainfrom
fix/mcp-oauth-provider-restore

Conversation

@threepointone
Copy link
Copy Markdown
Contributor

@threepointone threepointone commented Feb 19, 2026

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:

Changes

Code (packages/agents/)

  1. Export DurableObjectOAuthClientProvider from src/index.ts — changes the existing export type to a combined value + type export. Users can now import { DurableObjectOAuthClientProvider } from "agents".

  2. Add createAuthProvider factory to MCPClientManagerOptions — optional callback with signature (callbackUrl: string) => AgentMcpOAuthProvider, matching the existing Agent.createMcpOAuthProvider signature.

  3. Wire the factory from Agent → MCPClientManager — the Agent constructor now passes (callbackUrl) => this.createMcpOAuthProvider(callbackUrl) into the manager, so both addMcpServer() and restoreConnectionsFromStorage() use the same override.

  4. restoreConnectionsFromStorage() delegates to the factory — when present, calls the factory instead of the hardcoded new DurableObjectOAuthClientProvider(this._storage, ...). Falls back to the existing behavior when no factory is provided (backward compatible for direct MCPClientManager usage in tests).

Tests

  • 6 unit tests in client-manager.test.ts covering: factory called during restore, serverId/clientId set by manager, fallback without factory, factory for OAuth-in-progress servers, factory for failed connection recreation, factory called per-server in mixed restore.
  • 1 integration test in create-oauth-provider.test.ts — uses TestCustomOAuthAgent (which overrides createMcpOAuthProvider) to verify the override is respected during restoreConnectionsFromStorage, not just addMcpServer.

Docs (docs/)

  • Updated mcp-client.md to document the DurableObjectOAuthClientProvider export and the custom storage backend pattern.

Reviewer notes

  • The factory signature is deliberately (callbackUrl: string) => AgentMcpOAuthProvider — just callbackUrl, matching the public createMcpOAuthProvider API. Internal concerns (serverId, clientId) are set by MCPClientManager after calling the factory.
  • The authProvider.serverId = server.id / authProvider.clientId = server.client_id assignments after the factory call are idempotent with the fallback path (which already sets them in createAuthProvider). This keeps both paths consistent without branching the post-creation logic.
  • There is a companion PR for cloudflare-docs that fixes a pre-existing wrong function signature in the API reference and adds the custom storage example.

Test plan

  • All 6 new unit tests pass in client-manager.test.ts
  • New integration test passes in create-oauth-provider.test.ts
  • All 543 existing tests continue to pass
  • CI passes

Made with Cursor

threepointone and others added 2 commits February 19, 2026 09:21
…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-bot
Copy link
Copy Markdown

changeset-bot bot commented Feb 19, 2026

🦋 Changeset detected

Latest commit: 2dfa86e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
agents Patch

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

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Feb 19, 2026

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/agents@944

commit: 2dfa86e

@threepointone
Copy link
Copy Markdown
Contributor Author

/bonk review this pr please

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Feb 19, 2026

I've now reviewed all the changed files thoroughly. Here's my review:


PR Review: fix: export DurableObjectOAuthClientProvider and respect createMcpOAuthProvider override during restore

Overall Assessment

This 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

packages/agents/src/index.ts — Two changes, both correct:

  1. Export change (lines 255-260): Converting export type { AgentMcpOAuthProvider, AgentsOAuthProvider } to export { DurableObjectOAuthClientProvider, type AgentMcpOAuthProvider, type AgentsOAuthProvider }. This is the right fix — users need the class as a value (to instantiate it or use instanceof), not just as a type. The type qualifier is correctly preserved on the interfaces.

  2. Factory wiring (lines 826-829): Passing createAuthProvider: (callbackUrl) => this.createMcpOAuthProvider(callbackUrl) into MCPClientManager. This correctly captures this via the arrow function, so subclass overrides of createMcpOAuthProvider are respected. The closure is created in the constructor where this is the Agent instance.

packages/agents/src/mcp/client.ts — Three changes:

  1. MCPClientManagerOptions.createAuthProvider (line 119): Optional factory field. Clean signature that matches the public Agent.createMcpOAuthProvider API.

  2. Store factory in constructor (lines 131-132, 166): Straightforward.

  3. restoreConnectionsFromStorage delegation (lines 325-336): The factory check with fallback is correct. The authProvider.serverId and authProvider.clientId assignments after the factory call (lines 333-336) ensure the manager's internal concerns are always set regardless of which path created the provider. This is idempotent with the fallback path's behavior — no issue there.

Observations

  1. No factory wiring for addMcpServer: The PR description mentions "both addMcpServer() and restoreConnectionsFromStorage() use the same override," but looking at the addMcpServer code path in index.ts:3820-3825, it already calls this.createMcpOAuthProvider(callbackUrl) directly and passes the result into registerServer. So addMcpServer was never broken — only restoreConnectionsFromStorage was, because MCPClientManager hardcoded new DurableObjectOAuthClientProvider(...). The factory is only needed for the restore path. This is correctly implemented.

  2. The createAuthProvider private method on MCPClientManager (lines 242-263) is now only used as the fallback when no factory is injected. It could be cleaned up or marked as the explicit fallback, but leaving it as-is is fine for a patch release.

  3. Test quality: The 6 new unit tests in client-manager.test.ts (lines 2283-2500) are well-structured and cover the key scenarios: factory called during restore, serverId/clientId propagation, fallback without factory, OAuth-in-progress servers, failed connection recreation, and per-server factory calls in mixed restore. The integration test via TestCustomOAuthAgent.testRestoreUsesOverride() validates the end-to-end flow through real Durable Object storage.

  4. Docs update (lines 229-247 of mcp-client.md): The new "Custom storage backend" section is well-placed and the code example is correct. It follows the existing doc structure and clearly explains the most common customization use case.

One minor concern

In the TestCustomOAuthAgent.testRestoreUsesOverride() method (oauth.ts:276-311), the test resets this.mcp._isRestored = false via @ts-expect-error to force a second restore. This is a pragmatic test approach, but it means the test depends on the internal _isRestored flag name. If that field is ever renamed, the test will still compile (the @ts-expect-error will just suppress a different error) but may silently break. This is a very minor concern — acceptable for test code.

Verdict

Ship it. The fix is minimal, correct, backward-compatible, and well-tested. The changeset is appropriate as a patch bump.

github run

@threepointone threepointone merged commit e729b5d into main Feb 19, 2026
4 checks passed
@threepointone threepointone deleted the fix/mcp-oauth-provider-restore branch February 19, 2026 13:05
@github-actions github-actions bot mentioned this pull request Feb 19, 2026
mattzcarey pushed a commit that referenced this pull request Feb 19, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Request: export DurableObjectOAuthClientProvider from top-level agents package

2 participants