Repository navigation
fix(mcp): surface mid-session re-authorization and stop serving dead auth links - #2660
mattzcarey wants to merge 6 commits into
Conversation
|
🟢 agents import sizes: 1 entry point changed, no growth
Changed exports (2)
How this worksEach runtime export is bundled on its own, minified, and gzipped. Changes smaller than 100 B, or smaller than 1% and 1 KiB, are ignored. Growth over 10% or 5 KiB is marked 🔴. This report is informational and does not fail CI. The workflow artifact contains every measurement. Compared |
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
ddeb8e2 to
4601cff
Compare
…nager A request on a ready connection can fail with 403 insufficient_scope (scope step-up) or a 401 whose refresh fails. The SDK transport builds a new authorization URL, stores it on the provider and throws UnauthorizedError. MCPClientManager only surfaced auth URLs at connect time, so the URL was never shown, and a callback for it was dropped as stale because the connection was still ready. callTool(), readResource() and getPrompt() now detect this (unauthorized error plus a new provider authUrl), move the connection to authenticating, persist auth_url, and throw MCPAuthorizationRequiredError with authUrl. The normal OAuth callback then exchanges the code and reconnects. Other errors are rethrown unchanged.
- connect, discovery and mid-session requests enter authenticating through enterAuthenticating, so auth_url, state and broadcast always agree - addMcpServer returns authenticating when discovery needs OAuth, waits for in-flight connection work instead of reporting it ready, and rediscovers a connected-but-undiscovered server - restore only trusts a persisted auth_url whose state can still be redeemed; otherwise it reconnects (fixes dead links and the callback crash window) - callTool/readResource/getPrompt reject with MCPConnectionNotFoundError for an unknown serverId - conformance worker drops its requestReauth state patch
When several requests got a 401 together, as discovery's parallel catalog lists do against a server that allows the handshake without a token, each started its own OAuth flow on the shared provider: five client registrations, and a saved link whose code verifier belonged to another flow, so the callback failed with "No code verifier found for OAuth state". A 401 from the MCP endpoint now waits until the authorization an earlier one started has built a link or saved tokens, so later flows reuse that client. Other responses are not delayed.
… turns too The SSE transport posts to a per-session message endpoint, not the connection URL, so matching on the URL let concurrent 401s there refresh in parallel. A request is MCP traffic when it carries a JSON-RPC message or opens an event stream; OAuth requests are neither.
Wrap the transport's onUnauthorized instead of inferring the end of a flow from fetch responses and provider calls. A flow that failed outright used to hold later 401s until a 10 second timeout; now the next flow starts as soon as the previous one returns or throws. A flow that finds a newer access token than the one it was called with skips authorizing, so two calls hitting an expired token refresh once.
acab922 to
978d6f9
Compare
When a request on a
readyMCP server needs re-authorization,MCPClientManagernow moves the server toauthenticating, saves the newauth_urland throwsMCPAuthorizationRequiredError. The OAuth callback for that link then exchanges the code instead of dropping it as a stale popup. On wake, a savedauth_urlrestores only while itsstatecan still be redeemed, and a dead link is cleared so the server reconnects.Fixes #2658. The SDK already built the step-up URL for a
403 insufficient_scopeor a failed token refresh, but nobody saw it:readyandauth_urlwas never savedconnectionStateby hand to get around it, and that patch is removed hereOther changes callers will see:
MCPAuthorizationRequiredErrorextendsUnauthorizedErrorand keeps itsname, so existing checks still matchcallTool(),readResource()andgetPrompt()reject withMCPConnectionNotFoundErrorfor an unknownserverId, where they used to throw aTypeErroraddMcpServer()returns{ state: "authenticating", authUrl }when discovery gets a401, and waits for a server that is still connecting instead of reporting itready401at the same time now share one OAuth flow. Before, each registered its own client, so against a server that allows the handshake without a token the returned link could never complete, and two calls whose token expired together refreshed in parallel and lost the race on a rotating refresh tokenFirst of four stacked PRs, followed by #2661, #2662 and #2663. The first commit is @jonnyparris's fix from his fork, kept as he wrote it.
pnpm run checkand the agents workers suite pass. The client conformance lanes pass without the old conformance patch.