claude-code - 💡(How to fix) Fix HTTP MCP OAuth: refresh token not used; client drops to bootstrap tools when access token expires [2 comments, 3 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#55055Fetched 2026-05-01 05:47:24
View on GitHub
Comments
2
Participants
3
Timeline
6
Reactions
0
Author
Timeline (top)
labeled ×4commented ×2

For HTTP MCP servers using OAuth, Claude Code stores both accessToken and refreshToken in the keychain credentials, but does not exercise the refresh-token flow when the access token expires. Instead the MCP server is shown as connected with only its bootstrap auth tools exposed, forcing the user to re-authorize manually every time the access token expires.

For Slack-backed MCP servers (e.g. the official slack-frends plugin) the access token TTL is ~12 hours, so the user is prompted to re-auth roughly twice a day.

Root Cause

For HTTP MCP servers using OAuth, Claude Code stores both accessToken and refreshToken in the keychain credentials, but does not exercise the refresh-token flow when the access token expires. Instead the MCP server is shown as connected with only its bootstrap auth tools exposed, forcing the user to re-authorize manually every time the access token expires.

For Slack-backed MCP servers (e.g. the official slack-frends plugin) the access token TTL is ~12 hours, so the user is prompted to re-auth roughly twice a day.

Fix Action

Workaround

Run the bootstrap auth tools manually each time:

  1. mcp__slack-frends__authenticate → returns an authorization URL
  2. Open URL in browser, approve
  3. Paste the redirect callback URL into mcp__slack-frends__complete_authentication

This works but is needed twice a day for active users, which adds up.

Code Example

{
  "serverName": "slack-frends",
  "serverUrl": "https://mcp.slack.com/mcp",
  "accessToken": "<xoxe.xox...>",
  "refreshToken": "<xoxe-1-M...>",
  "expiresAt": 1777608318334,
  "scope": "channels:history ...",
  "discoveryState": {
    "authorizationServerUrl": "https://mcp.slack.com",
    "resourceMetadataUrl": "https://mcp.slack.com/.well-known/oauth-protected-resource",
    "oauthMetadataFound": true
  }
}
RAW_BUFFERClick to expand / collapse

Summary

For HTTP MCP servers using OAuth, Claude Code stores both accessToken and refreshToken in the keychain credentials, but does not exercise the refresh-token flow when the access token expires. Instead the MCP server is shown as connected with only its bootstrap auth tools exposed, forcing the user to re-authorize manually every time the access token expires.

For Slack-backed MCP servers (e.g. the official slack-frends plugin) the access token TTL is ~12 hours, so the user is prompted to re-auth roughly twice a day.

Environment

  • Claude Code: 2.1.119
  • Platform: macOS 26.3.1
  • Node: v25.2.1
  • Affected MCP server: slack-frends (HTTP, https://mcp.slack.com/mcp), installed from the slack@claude-plugins-official plugin marketplace
  • Likely affects any HTTP MCP server using OAuth refresh tokens

Repro

  1. Install the official Slack plugin and complete OAuth (token + refresh token are stored).
  2. Wait ~12 hours for the access token to expire (or shorter if you adjust your test).
  3. Start a new Claude Code session.

Expected

The HTTP MCP client detects the expired access token, calls the OAuth refresh endpoint with the stored refresh token, swaps in the new access token, persists the new expiry, and operational tools (slack_search_*, slack_read_*, slack_send_message, etc.) are available without any user interaction.

Actual

The MCP server is listed as connected, but the only tools exposed are the bootstrap auth tools:

  • mcp__slack-frends__authenticate
  • mcp__slack-frends__complete_authentication

All operational tools are missing until the user manually approves a fresh OAuth flow.

Evidence

Keychain item Claude Code-credentials (account $USER), JSON blob, key mcpOAuth["slack-frends|<hash>"]. Structure (values redacted):

{
  "serverName": "slack-frends",
  "serverUrl": "https://mcp.slack.com/mcp",
  "accessToken": "<xoxe.xox...>",
  "refreshToken": "<xoxe-1-M...>",
  "expiresAt": 1777608318334,
  "scope": "channels:history ...",
  "discoveryState": {
    "authorizationServerUrl": "https://mcp.slack.com",
    "resourceMetadataUrl": "https://mcp.slack.com/.well-known/oauth-protected-resource",
    "oauthMetadataFound": true
  }
}

The expiresAt shown above had already passed by ~12 hours when the new session started. The refresh token is present and the metadata endpoint was discovered, so the client has everything it needs to refresh — it just isn't using it.

Workaround

Run the bootstrap auth tools manually each time:

  1. mcp__slack-frends__authenticate → returns an authorization URL
  2. Open URL in browser, approve
  3. Paste the redirect callback URL into mcp__slack-frends__complete_authentication

This works but is needed twice a day for active users, which adds up.

Why it matters

For workspace-integration MCP servers with short-lived access tokens (Slack is the obvious case), the lack of auto-refresh effectively turns a "set once, works" experience into a "re-auth twice a day forever" experience. Refresh tokens already exist in storage; this is a client-side gap, not a protocol limitation.

extent analysis

TL;DR

Implement an automatic OAuth token refresh flow using the stored refresh token when the access token expires.

Guidance

  • Investigate the Claude Code OAuth implementation to identify why the refresh token is not being used to obtain a new access token when the existing one expires.
  • Verify that the expiresAt value in the keychain credentials is being checked and used to trigger the refresh token flow.
  • Consider adding a periodic check for expired access tokens and automatically refreshing them using the stored refresh token.
  • Review the Slack OAuth documentation to ensure that the refresh token flow is being implemented correctly.

Example

// Example of a potential refresh token flow
{
  "serverName": "slack-frends",
  "serverUrl": "https://mcp.slack.com/mcp",
  "accessToken": "<new_access_token>",
  "refreshToken": "<xoxe-1-M...>",
  "expiresAt": <new_expires_at>,
  "scope": "channels:history ...",
  // ...
}

Notes

The implementation of the automatic refresh token flow may require changes to the Claude Code OAuth client library or the MCP server integration.

Recommendation

Apply a workaround by implementing an automatic OAuth token refresh flow using the stored refresh token when the access token expires, as this will improve the user experience and reduce the need for manual re-authentication.

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

claude-code - 💡(How to fix) Fix HTTP MCP OAuth: refresh token not used; client drops to bootstrap tools when access token expires [2 comments, 3 participants]