claude-code - 💡(How to fix) Fix [Bug] MCP OAuth refresh fails with static clientId—not persisted to credentials [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#53021Fetched 2026-04-25 06:14:31
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
0
Timeline (top)
labeled ×5commented ×1

When an MCP server is configured with a static pre-registered OAuth client via mcpServers.<name>.oauth.clientId (instead of relying on dynamic client registration / DCR), the
initial authorization-code flow succeeds and tokens are stored, but clientId is never written onto
the mcpOAuth[<entry>] record in ~/.claude/.credentials.json. When the access token later expires,
refresh fails because the token request is formed without a client_id, which any spec-compliant
authorization server rejects with invalid_request. Claude Code surfaces this as "Failed to reconnect to <server>".

Environment

  • Claude Code 2.1.119
  • Windows 11 Pro 10.0.26200
  • Node v24.11.1
  • MCP server: remote HTTP + OAuth, authorization server is an IdentityServer-based OIDC provider,
    static client pre-registered.

Repro

  1. Configure an HTTP MCP server with a static OAuth client:
"mcpServers": {
   "example-server": {
     "type": "http",
     "url": "https://mcp.example.com/mcp",
     "oauth": { "clientId": "my-static-client" }
   }
 }
  1. Run Claude Code, connect, complete the browser auth flow. Tokens are stored successfully under
    mcpOAuth["example-server|<hash>"].
  2. Wait for the access token to expire (>1 h in my case).
  3. Restart Claude Code or run /mcp.

Expected: Claude refreshes the access token using refresh_token + client_id=my-static-client + the
token endpoint discovered from .well-known/openid-configuration, and reconnects silently.

Actual: /mcp reports "Failed to reconnect to example-server" with no further detail. No entry is
added to mcp-needs-auth-cache.json so the UI doesn't offer to re-auth.

Manual verification against the actual authorization server:

  • Stored refresh_token + grant_type=refresh_token + client_id=my-static-client → 200 OK, returns a
    fresh access token ✓
  • Same request with client_id omitted → {"error":"invalid_request"} ✗
  • Using the fresh access token to POST /mcp initialize → 200 OK, server responds normally ✓

So the server, the refresh token, and the OIDC discovery chain all work; only the token request
Claude Code forms at refresh time is broken.

Root cause (best guess)

Only the DCR code path writes clientId onto the mcpOAuth entry. The static-clientId path reads
oauth.clientId from the MCP config at initial-auth time but doesn't copy it into the persisted
credential record. Refresh logic then reads clientId from the persisted record, finds nothing, and
issues a malformed token request.

Suggested fix

When an MCP server is configured with a static oauth.clientId, also write that value into mcpOAuth[<entry>].clientId at the end of the initial authorization flow (or, at refresh time, fall
back to reading oauth.clientId from the MCP config when the credential entry has none).

Workaround

  1. Remove the affected entry from ~/.claude/.credentials.json → mcpOAuth.
  2. Restart Claude Code and complete the browser auth flow again.
  3. Manually add "clientId": "<your-static-id>" to the newly-created credential entry so subsequent
    refreshes work.

Error Message

  • Same request with client_id omitted → {"error":"invalid_request"} ✗

Root Cause

When an MCP server is configured with a static pre-registered OAuth client via mcpServers.<name>.oauth.clientId (instead of relying on dynamic client registration / DCR), the
initial authorization-code flow succeeds and tokens are stored, but clientId is never written onto
the mcpOAuth[<entry>] record in ~/.claude/.credentials.json. When the access token later expires,
refresh fails because the token request is formed without a client_id, which any spec-compliant
authorization server rejects with invalid_request. Claude Code surfaces this as "Failed to reconnect to <server>".

Fix Action

Fix / Workaround

Workaround

Code Example

"mcpServers": {
    "example-server": {
      "type": "http",
      "url": "https://mcp.example.com/mcp",
      "oauth": { "clientId": "my-static-client" }
    }
  }
RAW_BUFFERClick to expand / collapse

Summary

When an MCP server is configured with a static pre-registered OAuth client via mcpServers.<name>.oauth.clientId (instead of relying on dynamic client registration / DCR), the
initial authorization-code flow succeeds and tokens are stored, but clientId is never written onto
the mcpOAuth[<entry>] record in ~/.claude/.credentials.json. When the access token later expires,
refresh fails because the token request is formed without a client_id, which any spec-compliant
authorization server rejects with invalid_request. Claude Code surfaces this as "Failed to reconnect to <server>".

Environment

  • Claude Code 2.1.119
  • Windows 11 Pro 10.0.26200
  • Node v24.11.1
  • MCP server: remote HTTP + OAuth, authorization server is an IdentityServer-based OIDC provider,
    static client pre-registered.

Repro

  1. Configure an HTTP MCP server with a static OAuth client:
"mcpServers": {
   "example-server": {
     "type": "http",
     "url": "https://mcp.example.com/mcp",
     "oauth": { "clientId": "my-static-client" }
   }
 }
  1. Run Claude Code, connect, complete the browser auth flow. Tokens are stored successfully under
    mcpOAuth["example-server|<hash>"].
  2. Wait for the access token to expire (>1 h in my case).
  3. Restart Claude Code or run /mcp.

Expected: Claude refreshes the access token using refresh_token + client_id=my-static-client + the
token endpoint discovered from .well-known/openid-configuration, and reconnects silently.

Actual: /mcp reports "Failed to reconnect to example-server" with no further detail. No entry is
added to mcp-needs-auth-cache.json so the UI doesn't offer to re-auth.

Manual verification against the actual authorization server:

  • Stored refresh_token + grant_type=refresh_token + client_id=my-static-client → 200 OK, returns a
    fresh access token ✓
  • Same request with client_id omitted → {"error":"invalid_request"} ✗
  • Using the fresh access token to POST /mcp initialize → 200 OK, server responds normally ✓

So the server, the refresh token, and the OIDC discovery chain all work; only the token request
Claude Code forms at refresh time is broken.

Root cause (best guess)

Only the DCR code path writes clientId onto the mcpOAuth entry. The static-clientId path reads
oauth.clientId from the MCP config at initial-auth time but doesn't copy it into the persisted
credential record. Refresh logic then reads clientId from the persisted record, finds nothing, and
issues a malformed token request.

Suggested fix

When an MCP server is configured with a static oauth.clientId, also write that value into mcpOAuth[<entry>].clientId at the end of the initial authorization flow (or, at refresh time, fall
back to reading oauth.clientId from the MCP config when the credential entry has none).

Workaround

  1. Remove the affected entry from ~/.claude/.credentials.json → mcpOAuth.
  2. Restart Claude Code and complete the browser auth flow again.
  3. Manually add "clientId": "<your-static-id>" to the newly-created credential entry so subsequent
    refreshes work.

extent analysis

TL;DR

The most likely fix is to write the static oauth.clientId into the mcpOAuth record during the initial authorization flow or at refresh time.

Guidance

  • Verify that the mcpOAuth record is missing the clientId field after the initial authorization flow.
  • Check the Claude Code configuration to ensure that the oauth.clientId is correctly set for the MCP server.
  • Consider implementing a fallback to read oauth.clientId from the MCP config when the credential entry is missing.
  • Test the refresh token request with and without the client_id parameter to confirm the issue.

Example

No code snippet is provided as the issue is related to the internal logic of Claude Code and its handling of OAuth credentials.

Notes

The suggested fix requires modifications to the Claude Code logic for handling static OAuth clients. The workaround provided can be used as a temporary solution until the fix is implemented.

Recommendation

Apply the workaround by manually adding the clientId to the mcpOAuth record and restarting Claude Code, as this provides a temporary solution to the issue.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING