rmcp MCP OAuth sessions do not recover on 401 invalid_token; stale session fails while new session succeeds

Resolved 💬 2 comments Opened Mar 17, 2026 by vrv Closed Mar 17, 2026

Summary

For OAuth-enabled remote MCP servers using the rmcp streamable HTTP transport, an existing Codex session can get stuck sending a stale access token. When the MCP server returns 401 with WWW-Authenticate: Bearer error="invalid_token", Codex surfaces Auth required and does not refresh/retry. Starting a brand new Codex session succeeds immediately with the same MCP server/account.

This looks like a real recovery gap distinct from the older expires_at refresh bug.

Expected behavior

If an MCP server returns 401 invalid_token for an OAuth-backed MCP connection, Codex should attempt OAuth recovery:

  1. refresh the OAuth token if possible
  2. retry the MCP request once with the fresh token

At minimum, existing sessions should recover the same way a freshly started session does.

Observed behavior

  • In one running Codex session, MCP tool calls start failing with:

Transport ... AuthClient ... error: Auth required

  • The MCP resource server logs show it rejected an expired bearer token and returned 401 / invalid_token.
  • A new Codex session using the same MCP server and same user succeeds immediately.

That strongly suggests the old session is holding stale auth state and not recovering from a 401.

What I found in the current source

Current main has proactive refresh based on stored expires_at in OAuthPersistor::refresh_if_needed, and MCP operations call that before each request.

But the rmcp client test suite also explicitly asserts that 401 does not trigger recovery:

  • codex-rs/rmcp-client/tests/streamable_http_recovery.rs
  • test: streamable_http_401_does_not_trigger_recovery

So the current behavior appears to be:

  • proactive refresh only, if local expiry metadata says it is needed
  • no refresh/retry path when the server actually returns 401 invalid_token

That matches the real-world symptom above.

Steps to reproduce

I reproduced this against a custom OAuth-enabled remote MCP server, but the pattern should apply to any MCP server that returns OAuth-style 401 invalid_token when the access token expires server-side.

  1. Authenticate an OAuth-backed remote MCP server in Codex.
  2. Use the MCP server successfully in a running Codex session.
  3. Let the access token expire or otherwise become invalid server-side.
  4. In the same running session, invoke an MCP tool.
  5. Observe Auth required from the rmcp transport.
  6. Start a fresh Codex session.
  7. Observe the same MCP server now works immediately.

Why this seems distinct from existing issues

Related, but not exact duplicates:

  • #6572 fixed/identified the expires_at-based proactive refresh problem.
  • #14144 is about reauth succeeding but the active session still using stale refresh-token state.
  • #7318 asks for config/header reload on 401/403, but this report is specifically about OAuth-backed MCP recovery.

This issue is narrower:

  • existing session receives 401 invalid_token
  • Codex does not recover
  • new session succeeds
  • current rmcp test coverage appears to codify the non-recovery behavior

Proposed fix direction

For OAuth-backed rmcp MCP connections, consider treating 401 / WWW-Authenticate: Bearer error="invalid_token" as recoverable once:

  • attempt token refresh (or re-load refreshed credentials)
  • reinitialize transport if needed
  • retry the request once

Environment

Observed on macOS in March 2026 against a custom remote MCP server.

If helpful, I can add more detail from the server-side logs, but the key signal was:

  • stale session -> Auth required
  • new session -> same get_me MCP call succeeds immediately

View original on GitHub ↗

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