Skip to content

fix(core): replay completed MCP OAuth refreshes - #60896

Closed
Antonio-Faure wants to merge 1 commit into
anomalyco:devfrom
Antonio-Faure:oauth-refresh-replay
Closed

Antonio-Faure wants to merge 1 commit into
anomalyco:devfrom
Antonio-Faure:oauth-refresh-replay

Conversation

@Antonio-Faure

Copy link
Copy Markdown

Issue for this PR

Fixes #60612

Type of change

  • Bug fix

What does this PR do?

A remote MCP server with rotating refresh tokens can receive two refreshes for the same token. The in-flight map in packages/core/src/mcp/oauth.ts only shares a request until its response arrives. A second location can read the old refresh token before saveTokens has persisted the rotated one, send it again, get invalid_grant, and delete the shared credential.

Successful refresh responses are now kept for 60 seconds, keyed by the same URL/client/refresh-token tuple, and replayed to late callers. Failed refreshes are not cached, so a real failure remains retryable.

This covers the in-process race described in #60612. Serializing refreshes across separate OpenCode processes would be a separate follow-up using the credential file lock.

How did you verify your code works?

From packages/core:

  • bun test test/mcp-oauth.test.ts — 23 passed. The new test fails on bf544cd and passes with the change.
  • bun typecheck — no errors (1,859 files).

The new test runs two sequential refreshAuthorization calls with the same old token against a server that only accepts that token once, and asserts one network request and the same rotated response.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Rotating refresh tokens reject a second use. The in-flight map only shared requests until the response arrived, so a second location could read the old token before saveTokens persisted the rotated one and send it again.\n\nKeep successful refresh responses for 60s keyed by the consumed token and replay them to late callers. Failed refreshes remain retryable.
@opencodejr opencodejr Bot closed this Oct 31, 2026
@opencodejr

opencodejr Bot commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Thanks for tracking this down and adding a regression test for it! I think this duplicates #53275, which is still open:

  • fix(core): reuse MCP OAuth refresh answers for spent tokens #53275: keeps a successful MCP OAuth refresh answer for 60 seconds, keyed by the spent refresh token, so a connection that reads the old token before the rotated one is saved gets the same answer instead of invalid_grant. Failed answers are forgotten at once, the same as here.

If your test covers something #53275 doesn't (for example the staggered caller from #60612), you could share it there, or review or test that pull request.

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.

mcp: concurrent OAuth refreshes across locations invalidate rotating refresh tokens and delete the credential

1 participant