[BUG] Claude Desktop 3P: managedMcpServers OAuth never refreshes on-demand — a failed silent refresh 403s every call until manual reconnect

Open 💬 1 comment Opened Jul 11, 2026 by 123andy

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?

On Claude Desktop (third-party / 3P), a managedMcpServers entry configured with an oauth
block (external OIDC IdP, short access-token lifetime) does not recover on its own after a
single failed silent token refresh.

The access token is short-lived (5 min in our IdP). The app refreshes it on a background timer.
When one refresh fails for a transient reason — a laptop sleep or Wi-Fi switch, i.e. normal
mobile use — the app enters an escalating backoff (observed up to 30 minutes) and does not:

  1. refresh on demand when the user actually invokes a tool (it holds a valid long-lived

offline refresh token, but doesn't use it at call time — it just fails), nor

  1. reset the backoff when network connectivity returns.

Result: every tool call returns Forbidden for up to ~30 minutes, and the connector's status
badge still shows "connected"
the whole time (it reflects the transport session established at
connect, not current token validity). The only recovery is a manual disconnect/reconnect,
which a non-technical end user will never discover. This makes managed OAuth MCP servers
unreliable for a fleet.

What Should Happen?

The connector should self-heal without user intervention. Any of these would fix it:

  • Refresh on demand: when a tool call hits an expired access token, attempt a refresh (using

the held offline refresh token) before failing the call. This alone would make the failure
invisible.

  • Reset backoff on network recovery (online event), instead of holding a 30-minute timer.
  • Reflect auth state in the connector status so "connected" doesn't display while every call

is 403ing (secondary, but it's why the failure is so confusing).

Error Messages/Logs

From main.log (hostnames genericized):

[warn] [custom3p-mcp] mid-session refresh did not rotate
       { server: 'Web Search', failCount: 7, backoffMs: 1800000,
         reason: 'discovery: net::ERR_INTERNET_DISCONNECTED' }
[info] [custom3p-mcp host] [directMcpHost:Web Search] callTool failed:
       <tool>: Streamable HTTP error: Error POSTing to endpoint:
       {"error":"Forbidden","request_id":"…"}

The gateway side confirms the presented token is simply expired (rejected at IdP-token
validation) — the server is behaving correctly; the client never renewed. Note also
reason: 'discovery: net::ERR_…' — the refresh re-runs full OIDC discovery each time, so a
blip during discovery (not just the token endpoint) fails the whole refresh; caching discovery
would make refresh more resilient.

Steps to Reproduce

  1. Deploy a 3P managedMcpServers entry with an oauth block against an external OIDC IdP whose

access-token lifetime is short (e.g. 5 min), with offline_access so a refresh token is held.

  1. Connect and complete the OAuth sign-in; confirm tools work.
  2. Cause one silent refresh to fail transiently — sleep the laptop or switch networks across

a background-refresh tick (log shows mid-session refresh did not rotate … net::ERR_…).

  1. Restore network; wait past the access-token lifetime.
  2. Invoke a tool on that server → Forbidden, and it keeps failing (no on-demand refresh)

while the connector shows connected, until the backoff elapses or you manually
disconnect/reconnect.

Claude Model

N/A (third-party inference; behavior is in the MCP OAuth client, model-independent)

Is this a regression?

Not sure — appears to be current 3P behavior since GA.

Last Working Version

N/A

Claude Code Version

Claude Desktop (3P) 1.19367.0

Platform

Claude Desktop (third-party / 3P)

Operating System

macOS (Darwin 25.5)

Terminal/Shell

N/A (desktop app)

Additional Information

  • Config surface: managedMcpServers[].oauth with clientId + authorizationServer

(external IdP), appendOfflineAccess: true. The held offline refresh token remains valid, so
on-demand refresh is possible — the app just doesn't attempt it at call time.

  • Same managed-MCP area as #64578, #72808, #69953 — the OAuth-lifecycle robustness of managed

servers on 3P.

  • Happy to provide full logs or a reproduction environment.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗