[BUG] MCP OAuth: Claude doesn't auto-refresh access tokens, daily "Connection expired" despite valid refresh token

Open 💬 4 comments Opened Jun 3, 2026 by deiv061

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Custom HTTP MCP server (Azure Function App) using Entra OAuth 2.0 + PKCE shows "Connection expired" daily in Claude.ai web and Claude Desktop, despite the refresh token being valid and the user's Entra session being within its 180-day sign-in frequency window.

When the user clicks Connect, Claude exchanges the stored refresh token at Entra's /token endpoint silently and reconnects in ~200ms — proving the refresh path itself works correctly. Claude just doesn't call it automatically when the access token expires; it falls through to the "needs reconnect" UI immediately.

This matches the diagnosis in closed issue #43789 by @nwoonet:

The encrypted token cache is populated and persisted across sessions, so refresh tokens are being stored. The client just isn't exercising the refresh endpoint when the access token expires — it falls straight through to the bootstrap auth tools.

Related closed/duplicate issues: #43789, #28262, #5706, #12447, #55055, #53803, #54742, #47219.

Evidence from Entra sign-in logs when user clicks Connect after seeing "Connection expired":

  • additionalDetails: "MFA requirement satisfied by claim in the token" — Entra's signature of a successful silent refresh-token exchange
  • status.errorCode: 0
  • conditionalAccessStatus: success
  • appliedConditionalAccessPolicies: [] (no challenge required)

This proves the refresh exchange itself succeeds instantly when invoked — Claude is just not invoking it without a manual user trigger.

Impact: every OAuth-based MCP connector in our org sidebar (custom MCPs + Anthropic-first-party Atlassian, Microsoft 365, Google Drive) shows "Connection expired" daily. Multi-step automations stall when access tokens expire mid-workflow. The only workaround is the community mcp-stdio local bridge, which requires per-laptop install.

Cross-platform — not Windows-specific. Confirmed by multiple colleagues at our org on both Windows 11 and macOS. Matches @nwoonet's observation in #43789: "Confirming this on macOS 26 / Claude Code 2.1.122. The issue is not Windows-specific."

What Should Happen?

When the access token expires (or returns 401 from the MCP server), Claude should:

  1. Detect the expiry
  2. POST to the IdP's /token endpoint with the cached refresh token
  3. Receive a new access token (no user interaction required)
  4. Retry the original MCP call transparently
  5. Surface no UI to the user

Only fall back to the "Connection expired / reconnect" UI when the refresh attempt itself fails (e.g. refresh token is also expired or revoked).

This is standard OAuth 2.0 client behavior (RFC 6749 §6) — auto-refresh is what makes long-running OAuth sessions usable. Most OAuth-aware HTTP clients (Microsoft.Identity.Web, MSAL, Google's auth libs, etc.) handle this automatically.

Error Messages/Logs

UI banner shown daily:
  "Connection has expired. You can reconnect to re-authenticate."

No actual error logs are produced by Claude — the failure is silent (Claude flips state to expired without logging a 401 from a refresh attempt).

Entra sign-in log entry observed when user clicks Connect to recover (proves refresh works when invoked):
  createdDateTime:                   2026-06-01T13:18:50Z
  status.errorCode:                  0
  status.additionalDetails:          "MFA requirement satisfied by claim in the token"
  conditionalAccessStatus:           success
  appliedConditionalAccessPolicies:  []

The "MFA satisfied by claim in token" detail is Entra's signature of a silent refresh-token exchange. This succeeded in ~200ms with no user prompt. The same exchange should happen automatically on access token expiry, before showing the "Connection expired" UI.

Steps to Reproduce

  1. Set up a custom HTTP MCP server that requires OAuth (Authorization Code + PKCE)

against any IdP that follows standard OAuth 2.0. We used Microsoft Entra,
but the issue is IdP-agnostic per other reports in #43789 (which mentions
Sentry, Atlassian, Superhuman Mail, Vercel, etc.)

  1. Add the MCP server as a custom connector in Claude.ai (web) and/or

Claude Desktop. Provide OAuth Client ID + Secret.

  1. Click "Connect". OAuth flow completes successfully. Connector shows

as "Connected". Claude.ai stores: access_token (~1h lifetime) +
refresh_token (typically days to months).

  1. Use the MCP server normally, invoke its tools from a chat. Works fine.
  1. Wait for the access_token to expire (~1 hour after step 3).
  1. Open Claude.ai / Claude Desktop. Observed: the connector shows

"Connection issue / Connection has expired. You can reconnect to
re-authenticate." Expected: silent background refresh; user sees
nothing.

  1. Click "Connect". Claude silently exchanges the cached refresh_token

at the IdP's /token endpoint and reconnects in ~200ms — no Microsoft
login window, no MFA prompt, no UI interaction.

This step proves the refresh_token is valid and the refresh exchange
works perfectly when manually triggered. The bug is that step 6
should not require step 7 — Claude should perform step 7 automatically
when the access_token expires.

  1. Use any MCP tool works again until the next ~1-hour expiry.
  2. Next morning, GOTO step 6.

Minimal repro setup is in the linked closed issues #43789, #55055, etc.
We can provide our specific Azure Function App OAuth metadata if helpful
for reproduction with an Entra-backed test app.

Claude Model

None

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

2.1.144 (Claude Code)

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

PowerShell

Additional Information

Workaround in use today

Daily 1-click reconnect. We extended our Entra Conditional Access "Sign-in frequency" policy to 180 days specifically scoped to the MCP app, which makes the reconnect silent (no Microsoft login window, no MFA prompt) — but the "Connection expired" UI in Claude still appears daily because it's not driven by actual token state.

Community workaround

mcp-stdio (https://github.com/shigechika/mcp-stdio) — a local stdio bridge that holds the refresh token client-side and refreshes transparently on 401. Confirms the behavior is fixable from the client side; the gap is in Claude itself.

OAuth refresh is the foundation of every modern long-lived API integration. Without it, every OAuth-based MCP feels broken to users and looks "in beta" forever. Fixing this single client-side bug probably resolves a large portion of the connector pain reported across #28262, #5706, #12447, #43789, #47219, #53803, #54742, #55055.

View original on GitHub ↗

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