Describe the bug
When two requests for the same OAuth MCP server arrive in parallel, both calls independently detect a 401, call auth(), and race to refresh using the same refresh token. With OAuth servers that use rotating refresh tokens (e.g. Atlassian, Asana), this triggers RFC 6819 5.2.2.3 replay detection — the authorization server detects that a consumed refresh token was reused and revokes the entire token family, permanently breaking the connection until the user manually re-authorizes.
To Reproduce
- Set up an MCP server connection using an OAuth provider with rotating refresh tokens (e.g. Atlassian)
- Let the access token expire, or manually add a bad token
- Send two parallel requests to the MCP server before either completes the refresh, this will have to be quick so use a simple script to make 2 immediate calls
curl ... &
curl ... &
wait
- Both requests call auth() simultaneously, each POSTing to the token endpoint with the same refresh token
- The OAuth server receives two requests with the same refresh token — the second is a replay
Expected behavior
Only one token refresh should occur. The second concurrent request should wait for the in-flight refresh to complete and reuse the resulting tokens, not trigger its own parallel refresh.
Logs
InvalidGrantError: Invalid refresh token
at parseErrorResponse (client/auth.ts:292:16)
at refreshAuthorization (client/auth.ts:1022:15)
at authInternal (client/auth.ts:419:31)
at auth (client/auth.ts:325:20)
at StreamableHTTPClientTransport.send (client/streamableHttp.ts:442:36)
Additional context
Root cause is in auth() in client/auth.ts — there is no concurrency guard preventing two simultaneous refresh flows for the same provider.
Describe the bug
When two requests for the same OAuth MCP server arrive in parallel, both calls independently detect a 401, call
auth(), and race to refresh using the same refresh token. With OAuth servers that use rotating refresh tokens (e.g. Atlassian, Asana), this triggers RFC 6819 5.2.2.3 replay detection — the authorization server detects that a consumed refresh token was reused and revokes the entire token family, permanently breaking the connection until the user manually re-authorizes.To Reproduce
Expected behavior
Only one token refresh should occur. The second concurrent request should wait for the in-flight refresh to complete and reuse the resulting tokens, not trigger its own parallel refresh.
Logs
Additional context
Root cause is in auth() in
client/auth.ts— there is no concurrency guard preventing two simultaneous refresh flows for the same provider.