Skip to content

OAuth MCP server requires reauthentication after refresh-token rotation #65216

Description

@dastrobu

Reproduction steps

  1. On a clean Zed install, configure one OAuth-protected HTTP MCP server in user-level settings.json (not in either project's settings). The server's OAuth provider must rotate refresh tokens and reject a token after it has been used for refhresh (in compliance with rfc6749 "The authorization server MAY issue a new refresh token, in which case the client MUST discard the old refresh token and replace it with the new refresh token.")

    {
      "context_servers": {
        "example-mcp": {
          "enabled": true,
          "url": "https://mcp.example.com/mcp"
        }
      }
    }

    Replace the example URL with an OAuth-enabled MCP endpoint. Authenticate once in Zed so the OAuth session is saved.

  2. Open several projects in Zed. All should inherit that one user-level MCP server entry and connect to it. Restoring several projects at startup may make the problem more likely.

  3. Ensure setting "Restore On Startup" is set to "Last Session".

  4. Close Zed and wait until the access token has expired, the refrehs token must still be valid.

  5. Re-open Zed and let all projects connect to the MCP server. The first project to make a request will refresh its access token and rotate the refresh token.

  6. Other projects will still have the old refresh token in their in-memory OAuth provider. When they make a request that requires a refresh, the MCP server will reject it with invalid_grant and Zed will require reauthentication.

Here is what has been analyzed so far:
The two projects inherit one user setting but load separate in-memory copies of the saved session:

sequenceDiagram
    participant A as Project A
    participant K as Shared keychain
    participant B as Project B
    participant M as MCP server
    participant O as OAuth server
    K-->>A: Load refresh-0
    K-->>B: Load refresh-0
    A->>M: Request with access-0
    M-->>A: 401
    A->>O: Refresh with refresh-0
    O-->>A: access-1 and refresh-1
    A->>K: Persist refresh-1
    B->>M: Request with access-0
    M-->>B: 401
    B->>O: Refresh with stale refresh-0
    O-->>B: invalid_grant
    Note over B: Reauthentication required
Loading

I tried to let an AI agent craft a test to reproduce this in zed, which can be found here: feature/reproduce-mcp-oauth-refresh-race.

The test uses two real project stores, one user setting, one shared in-memory credentials provider, and a mock MCP/OAuth server with rotating refresh tokens. Both connections initialize with the same saved session. Project A refreshes successfully and the new token is confirmed persisted before project B sends its request. The test still fails at the intended assertion:

Additional evidence are logs of a real world MCP protected by GitHubs OAuth that show regularly bursts of refresh-token exchange failures:

2026/10/02 06:11:35 [provider] token exchange (refresh_token) error: bad_refresh_token

The MCP is private, though, so I cannot offer it for testing.

Current vs. Expected behavior

Current behavior: Each project independently connects to the same globally configured MCP server. After project A rotates the refresh token, project B can retain the old refresh token in its own in-memory OAuth provider. Its later refresh is rejected, and the MCP connection requires reauthentication even though the new session has already been persisted.

Expected behavior: Projects using the same OAuth-protected MCP URL should reuse or coordinate the current session. Refreshing in one project should not invalidate another project's connection or prompt the user to authenticate again. This should hold for sequential requests, not only simultaneous refreshes.

Zed version and system specs

Zed: v1.21.0+stable.362.33c95853ed2b6956f339733c63a8220964ecbeb6 (Zed)
OS: macOS 26.7.1
Memory: 64 GiB
Architecture: aarch64

Attach Zed log file

no relevant logs found.

Relevant Zed settings

settings.json
{
  "restore_on_startup": "last_session",
  "context_servers": {
    "example-mcp": {
      "enabled": true,
      "url": "https://mcp.example.com/mcp"
    }
  }
}

Relevant Keymap

No response

(for AI issues) Model provider details

No response

If you are using WSL on Windows, what flavor of Linux are you using?

None

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:needs triagetriage is not complete: no `area:*` label, or no `severity:*`/`reach:*` for Bugs/Crashes

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions