[BUG] MCP OAuth: Claude doesn't auto-refresh access tokens, daily "Connection expired" despite valid refresh token
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 exchangestatus.errorCode:0conditionalAccessStatus:successappliedConditionalAccessPolicies:[](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:
- Detect the expiry
- POST to the IdP's
/tokenendpoint with the cached refresh token - Receive a new access token (no user interaction required)
- Retry the original MCP call transparently
- 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
- 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.)
- Add the MCP server as a custom connector in Claude.ai (web) and/or
Claude Desktop. Provide OAuth Client ID + Secret.
- Click "Connect". OAuth flow completes successfully. Connector shows
as "Connected". Claude.ai stores: access_token (~1h lifetime) +
refresh_token (typically days to months).
- Use the MCP server normally, invoke its tools from a chat. Works fine.
- Wait for the access_token to expire (~1 hour after step 3).
- 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.
- 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.
- Use any MCP tool works again until the next ~1-hour expiry.
- 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.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗