modelcontextprotocol / modelcontextprotocol/typescript-sdk
Race condition in auth() causes refresh token invalidation when rotating tokens are used
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 13.4k
- Forks
- 2.2k
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 4
Description
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in client/auth.ts, especially auth(), authInternal, and refreshAuthorization, where the logs show the concurrent refresh flow. Reproduce the issue with two parallel requests against a server using rotating refresh tokens. Done means one refresh occurs and the second request waits for and reuses the resulting tokens without invalidating the token family.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, authentication
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100