Implement proper fix: also set the token expiration time when it's expired

The old load path in codex-rs/rmcp-client/src/oauth.rs only set expires_in when the stored expiry was in the future:

  // codex-rs/rmcp-client/src/oauth.rs:369-374 (original)
  if let Some(expires_at) = entry.expires_at
      && let Some(seconds) = expires_in_from_timestamp(expires_at)
  {
      let duration = Duration::from_secs(seconds);
      token_response.set_expires_in(Some(&duration));
  }

  And expires_in_from_timestamp returned None for expired tokens:

  // codex-rs/rmcp-client/src/oauth.rs:444-453 (original)
  if expires_at <= now_ms {
      None
  } else {
      Some((expires_at - now_ms) / 1000)
  }

  So when the stored token was already expired, set_expires_in was never called, RMCP saw “no expiry,” and it didn’t auto-refresh on handshake.
This commit is contained in:
Javier Soto
2025-11-04 11:31:41 -08:00
parent bc90844a63
commit 24535c5922
2 changed files with 2 additions and 20 deletions
+2 -3
View File
@@ -366,9 +366,8 @@ fn load_oauth_tokens_from_file(server_name: &str, url: &str) -> Result<Option<St
token_response.set_scopes(Some(scopes.into_iter().map(Scope::new).collect()));
}
if let Some(expires_at) = entry.expires_at
&& let Some(seconds) = expires_in_from_timestamp(expires_at)
{
if let Some(expires_at) = entry.expires_at {
let seconds = expires_in_from_timestamp(expires_at).unwrap_or(0);
let duration = Duration::from_secs(seconds);
token_response.set_expires_in(Some(&duration));
}
-17
View File
@@ -9,7 +9,6 @@ use std::time::Duration;
use anyhow::Result;
use anyhow::anyhow;
use futures::FutureExt;
use oauth2::TokenResponse;
use mcp_types::CallToolRequestParams;
use mcp_types::CallToolResult;
use mcp_types::InitializeRequestParams;
@@ -57,8 +56,6 @@ use crate::utils::convert_to_rmcp;
use crate::utils::create_env_for_mcp_server;
use crate::utils::run_with_timeout;
const REFRESH_SKEW_SECS: u64 = 60;
enum PendingTransport {
ChildProcess(TokioChildProcess),
StreamableHttp {
@@ -401,13 +398,6 @@ async fn create_oauth_transport_and_runtime(
let auth_client = AuthClient::new(http_client, manager);
let auth_manager = auth_client.auth_manager.clone();
// If the stored token is expired or about to expire, refresh before the handshake.
if should_refresh_initial_token(&initial_tokens.token_response.0) {
if let Err(err) = auth_manager.lock().await.refresh_token().await {
warn!("failed to refresh OAuth token before handshake: {err}");
}
}
let transport = StreamableHttpClientTransport::with_client(
auth_client,
StreamableHttpClientTransportConfig::with_uri(url.to_string()),
@@ -423,10 +413,3 @@ async fn create_oauth_transport_and_runtime(
Ok((transport, runtime))
}
fn should_refresh_initial_token(token: &OAuthTokenResponse) -> bool {
match token.expires_in() {
Some(duration) => duration.as_secs() <= REFRESH_SKEW_SECS,
None => token.refresh_token().is_some(),
}
}