Skip to content

Commit 3e491df

Browse files
committed
fix: refresh expired MCP OAuth tokens
Repro: - Set up an HTTP MCP server with OAuth expiring tokens like Datadog - Use it for a bit until the token expires - Start a new codex session Expected results: - The token gets transparently refreshed and the MCP server continues to work Actual results: - You start getting 401s and need to do `codex mcp logout` and log back in.
1 parent 13e1d03 commit 3e491df

File tree

1 file changed

+18
-0
lines changed

1 file changed

+18
-0
lines changed

codex-rs/rmcp-client/src/rmcp_client.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::time::Duration;
99
use anyhow::Result;
1010
use anyhow::anyhow;
1111
use futures::FutureExt;
12+
use oauth2::TokenResponse;
1213
use mcp_types::CallToolRequestParams;
1314
use mcp_types::CallToolResult;
1415
use mcp_types::InitializeRequestParams;
@@ -31,6 +32,7 @@ use rmcp::service::RunningService;
3132
use rmcp::service::{self};
3233
use rmcp::transport::StreamableHttpClientTransport;
3334
use rmcp::transport::auth::AuthClient;
35+
use rmcp::transport::auth::OAuthTokenResponse;
3436
use rmcp::transport::auth::OAuthState;
3537
use rmcp::transport::child_process::TokioChildProcess;
3638
use rmcp::transport::streamable_http_client::StreamableHttpClientTransportConfig;
@@ -55,6 +57,8 @@ use crate::utils::convert_to_rmcp;
5557
use crate::utils::create_env_for_mcp_server;
5658
use crate::utils::run_with_timeout;
5759

60+
const REFRESH_SKEW_SECS: u64 = 60;
61+
5862
enum PendingTransport {
5963
ChildProcess(TokioChildProcess),
6064
StreamableHttp {
@@ -397,6 +401,13 @@ async fn create_oauth_transport_and_runtime(
397401
let auth_client = AuthClient::new(http_client, manager);
398402
let auth_manager = auth_client.auth_manager.clone();
399403

404+
// If the stored token is expired or about to expire, refresh before the handshake.
405+
if should_refresh_initial_token(&initial_tokens.token_response.0) {
406+
if let Err(err) = auth_manager.lock().await.refresh_token().await {
407+
warn!("failed to refresh OAuth token before handshake: {err}");
408+
}
409+
}
410+
400411
let transport = StreamableHttpClientTransport::with_client(
401412
auth_client,
402413
StreamableHttpClientTransportConfig::with_uri(url.to_string()),
@@ -412,3 +423,10 @@ async fn create_oauth_transport_and_runtime(
412423

413424
Ok((transport, runtime))
414425
}
426+
427+
fn should_refresh_initial_token(token: &OAuthTokenResponse) -> bool {
428+
match token.expires_in() {
429+
Some(duration) => duration.as_secs() <= REFRESH_SKEW_SECS,
430+
None => token.refresh_token().is_some(),
431+
}
432+
}

0 commit comments

Comments
 (0)