Remote MCP OAuth: shared user-scope refresh token breaks against IdPs with single-use refresh-token rotation (ODC)

Status Open
Reported on v2.1.258
Maintainer reply None cached
Activity 0 comments · opened Sep 2, 2026

Environment

  • Claude Code 2.1.258, macOS (Darwin 25.6)
  • Remote HTTP MCP server with OAuth via dynamic client registration (issuer as-proxy, scope openid), credential stored at user scope in the macOS keychain (Claude Code-credentialsmcpOAuth.<server>)
  • IdP behind the server: OutSystems Developer Cloud (ODC) Identity Service

Summary

The IdP issues 5‑minute access tokens and single-use rotating refresh tokens; reusing a refresh token revokes the whole session. Claude Code stores one refresh token per server at user scope, and every concurrent session refreshes from it independently. With two or more Claude Code sessions open (CLI worktrees, Desktop, Cowork), the second refresh is treated as reuse, the IdP revokes the token family, and every session then reports:

MCP server "<name>" requires re-authorization (token expired)

Observed ≥6 forced re-authorizations in one working day. Never observed with a single active session, and never while one session was continuously calling tools (the refresh itself works: keychain expiresAt advanced from 22:37:27Z to 22:38:39Z between two reads one minute apart). Each re-auth mints a new DCR client.

IdP behaviour is documented by the vendor:

The Claude Code MCP docs describe the "refresh token rejection" notice but say nothing about sharing one OAuth credential across concurrent sessions, so this looks like an unhandled interaction rather than intended behaviour. (v2.1.206's transient-refresh fix is not the cause; 2.1.258 is affected.)

Side effect: a server-side stateful operation bound to the token (an in-flight Mentor run) is lost when the family is revoked.

Steps to reproduce

  1. Add an OAuth remote MCP server whose IdP rotates refresh tokens with reuse detection (ODC; or Keycloak with "Revoke Refresh Token" enabled and max reuse 0). Sign in once.
  2. Open two Claude Code sessions in the same user account.
  3. In session A call any tool. Wait > 5 min (access token expiry). In session B call any tool (B refreshes from the shared token and rotates it). Wait > 5 min. In session A call a tool again.
  4. Session A refreshes from the now-consumed token → IdP treats it as reuse → whole family revoked → both sessions show "requires re-authorization".

Expected

Any of:

  • (a) Refreshes serialised through one owner/lock, with the rotated refresh token written back before any other session reads it, and sessions re-reading the store before refreshing.
  • (b) Credentials keyed per session/client instead of one user-scope entry.
  • (c) At minimum, documented guidance for IdPs with single-use rotation (e.g. "use one session per server").

Actual

Concurrent sessions silently invalidate each other's refresh token; the user must re-authenticate roughly every 30–90 minutes.

View original on GitHub ↗