rmcp streamable HTTP OAuth tokens never refresh before expiry

Resolved 💬 0 comments Opened Nov 13, 2025 by LaelLuo Closed Nov 13, 2025

Summary

When using the rmcp streamable HTTP transport with OAuth, the cached OAuthTokenResponse keeps the original expires_in duration forever. AuthorizationManager::get_access_token() only refreshes when that duration reaches zero, so tokens that are still considered valid locally never trigger a refresh even if the server has rotated them. This causes long‑running SSE connections (e.g. the Notion MCP server) to suddenly fail with Auth required once the provider rotates the access token/refresh token pair.

Steps to Reproduce

  1. codex mcp login notion.
  2. Leave the SSE client idle for an hour (or run the new ignored test cargo test -p codex-rmcp-client notion_tests::notion_refreshes_after_tampered_access_token -- --ignored).
  3. Observe that the next notion.notion-fetch call immediately fails with Auth required during the initialize handshake.

Expected

The client should refresh the OAuth tokens using the stored refresh_token as soon as the server-side expiry (or a small skew window) is reached, so the session continues seamlessly.

Actual

AuthorizationManager keeps reporting the original expires_in, so the rmcp client never refreshes. When the access token expires server-side, every request (including the handshake) fails with Auth required, forcing the user to re-run codex mcp login notion.

Environment

Notes

I have a fix ready that tracks expires_at, refreshes when the remaining lifetime is below a 30s skew, and persists the refreshed credential set immediately. Let me know if any additional details would help.

View original on GitHub ↗