rmcp streamable HTTP OAuth tokens never refresh before expiry
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
codex mcp login notion.- 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). - Observe that the next
notion.notion-fetchcall immediately fails withAuth requiredduring 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
- codex-rmcp-client @ 004264457a46d0424b58e2200fe3e472c0e39623
- rmcp 0.8.5
- MCP server: https://mcp.notion.com/mcp
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.