MCP OAuth tokens not auto-refreshing despite valid refresh tokens

Resolved 💬 4 comments Opened Feb 24, 2026 by wcampbellgalaxy Closed Apr 12, 2026

Description

MCP OAuth tokens for HTTP-based servers (Atlassian, Notion) are not being automatically refreshed when they expire, despite valid refresh tokens being stored in ~/.claude/.credentials.json.

Users are forced to manually run /mcp and re-authenticate through the browser OAuth flow every time tokens expire, which interrupts workflow.

Evidence

The credentials file (~/.claude/.credentials.json) under mcpOAuth stores all the necessary data for a token refresh:

{
  "mcpOAuth": {
    "atlassian|<hash>": {
      "serverName": "atlassian",
      "serverUrl": "https://mcp.atlassian.com/v1/mcp",
      "clientId": "<client_id>",
      "accessToken": "<expired_token>",
      "expiresAt": 1771535707586,
      "refreshToken": "<valid_refresh_token>",
      "scope": ""
    },
    "notion|<hash>": {
      "serverName": "notion",
      "serverUrl": "https://mcp.notion.com/mcp",
      "clientId": "<client_id>",
      "accessToken": "<expired_token>",
      "expiresAt": 1771872083256,
      "refreshToken": "<valid_refresh_token>",
      "scope": ""
    }
  }
}

Both tokens were expired (Atlassian ~5 days, Notion ~1 day) with valid refresh tokens present. Claude Code did not attempt to refresh them — tool calls simply failed until /mcp was run manually.

Expected Behavior

When an MCP OAuth access token expires (or is about to expire), Claude Code should automatically use the stored refreshToken to obtain a new accessToken via the standard OAuth2 refresh token grant, without requiring user interaction.

Ideally:

  1. On tool call failure due to auth, attempt a token refresh using the stored refresh token
  2. On session start, proactively check expiresAt and refresh tokens that are expired or expiring soon
  3. Only fall back to the interactive /mcp browser OAuth flow if the refresh token itself is invalid/revoked

Current Workaround

Manually running /mcp to re-authenticate when tool calls start failing. A SessionStart hook can be used to check expiresAt and warn early, but cannot trigger the refresh itself.

Environment

  • macOS (Darwin 25.3.0)
  • Claude Code (latest stable)
  • MCP servers: Atlassian (https://mcp.atlassian.com/v1/mcp), Notion (https://mcp.notion.com/mcp) — both HTTP transport with OAuth

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗