fix: use the auth provider's current token instead of a connect-time snapshot#1434
Open
buptliuhs wants to merge 1 commit into
Open
fix: use the auth provider's current token instead of a connect-time snapshot#1434buptliuhs wants to merge 1 commit into
buptliuhs wants to merge 1 commit into
Conversation
…snapshot When relying on OAuth, useConnection read the access token from the provider once at connection setup and baked `Authorization: Bearer <token>` into `requestInit.headers`. The SDK transports already receive the same provider as their `authProvider` and add a fresh `Authorization` from it on every request, refreshing it on 401. But the SDK's `_commonHeaders()` spreads `requestInit.headers` after the provider token, so the connect-time snapshot always won. The effect: after the access token expires, the SDK refreshes successfully (token endpoint returns 200 and the provider stores the new token), but the retried request still carries the stale snapshot token, gets another 401, and the SDK's circuit breaker throws "Server returned 401 after successful authentication" — a 401 loop the session can't recover from. Stop snapshotting the OAuth token into requestInit and let the provider be the single source of truth. A user-supplied static Authorization header still flows through the custom-headers path and intentionally overrides the provider. Update the related test to assert the token is no longer baked in, and add direct SSE / Streamable HTTP tests covering it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
|
@cliffhall can you please review? thanks! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
After a successful OAuth token refresh, direct (non-proxy) connections keep sending the old access token, so the session enters a 401 loop it can't recover from. Once the access token expires:
POST /mcpreturns 401, the SDK refreshes,POST /token(grant_type=refresh_token) returns 200, and the provider stores the new token.POST /mcpstill carries the oldAuthorization: Bearer …, gets another 401.This is rarely hit with a long token TTL, but a short TTL breaks the session at first expiry.
Root cause
When relying on OAuth,
useConnectionread the access token from the provider once at connection setup and bakedAuthorization: Bearer <token>intorequestInit.headers.The transports already receive that same provider as their
authProvider, so the SDK adds a freshAuthorizationfrom it on every request (and refreshes it on 401). But the SDK's_commonHeaders()spreadsrequestInit.headersafter the provider token:So the connect-time snapshot always shadows the provider's current token. The provider does store the refreshed token (
saveTokens/tokens()round-trip the samesessionStoragekey) — it just never gets used.Fix
Stop snapshotting the OAuth token into
requestInit. The provider is the single, always-current source of the token, and the SDK injects and refreshes it per request. A user-supplied staticAuthorizationheader still flows through the custom-headers path and intentionally overrides the provider.Tests
requestInitand thatauthProvideris set.All
useConnectiontests pass; lint and prettier clean; client build compiles.Scope
Independent of the malformed-
Content-Typefix. Affects connections that use the SDK's OAuthauthProvider.