diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 9cb91b0e59..c62acc8832 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -139,7 +139,7 @@ client_request_definitions! { response: v2::ModelListResponse, }, - McpServerOauthLogin => "mcpServer/oauthLogin" { + McpServerOauthLogin => "mcpServer/oauth/login" { params: v2::McpServerOauthLoginParams, response: v2::McpServerOauthLoginResponse, }, diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 58464e0dc4..0a8445055d 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -166,6 +166,7 @@ use std::time::Duration; use tokio::select; use tokio::sync::Mutex; use tokio::sync::oneshot; +use toml::Value as TomlValue; use tracing::error; use tracing::info; use tracing::warn; @@ -203,6 +204,7 @@ pub(crate) struct CodexMessageProcessor { outgoing: Arc, codex_linux_sandbox_exe: Option, config: Arc, + cli_overrides: Vec<(String, TomlValue)>, conversation_listeners: HashMap>, active_login: Arc>>, // Queue of pending interrupt requests per conversation. We reply when TurnAborted arrives. @@ -249,6 +251,7 @@ impl CodexMessageProcessor { outgoing: Arc, codex_linux_sandbox_exe: Option, config: Arc, + cli_overrides: Vec<(String, TomlValue)>, feedback: CodexFeedback, ) -> Self { Self { @@ -257,6 +260,7 @@ impl CodexMessageProcessor { outgoing, codex_linux_sandbox_exe, config, + cli_overrides, conversation_listeners: HashMap::new(), active_login: Arc::new(Mutex::new(None)), pending_interrupts: Arc::new(Mutex::new(HashMap::new())), @@ -266,6 +270,16 @@ impl CodexMessageProcessor { } } + async fn load_latest_config(&self) -> Result { + Config::load_with_cli_overrides(self.cli_overrides.clone(), ConfigOverrides::default()) + .await + .map_err(|err| JSONRPCErrorError { + code: INTERNAL_ERROR_CODE, + message: format!("failed to reload config: {err}"), + data: None, + }) + } + fn review_request_from_target( target: ApiReviewTarget, ) -> Result<(ReviewRequest, String), JSONRPCErrorError> { @@ -1929,7 +1943,15 @@ impl CodexMessageProcessor { request_id: RequestId, params: McpServerOauthLoginParams, ) { - if !self.config.features.enabled(Feature::RmcpClient) { + let config = match self.load_latest_config().await { + Ok(config) => config, + Err(error) => { + self.outgoing.send_error(request_id, error).await; + return; + } + }; + + if !config.features.enabled(Feature::RmcpClient) { let error = JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, message: "OAuth login is only supported when [features].rmcp_client is true in config.toml".to_string(), @@ -1945,7 +1967,7 @@ impl CodexMessageProcessor { timeout_secs, } = params; - let Some(server) = self.config.mcp_servers.get(&name) else { + let Some(server) = config.mcp_servers.get(&name) else { let error = JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, message: format!("No MCP server named '{name}' found."), @@ -1977,7 +1999,7 @@ impl CodexMessageProcessor { match perform_oauth_login_return_url( &name, &url, - self.config.mcp_oauth_credentials_store_mode, + config.mcp_oauth_credentials_store_mode, http_headers, env_http_headers, scopes.as_deref().unwrap_or_default(), diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 90560e9b3c..6a6cf5edb2 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -59,6 +59,7 @@ impl MessageProcessor { outgoing.clone(), codex_linux_sandbox_exe, Arc::clone(&config), + cli_overrides.clone(), feedback, ); let config_api = ConfigApi::new(config.codex_home.clone(), cli_overrides); diff --git a/codex-rs/rmcp-client/src/perform_oauth_login.rs b/codex-rs/rmcp-client/src/perform_oauth_login.rs index 1ce57e16c8..9815a3a22d 100644 --- a/codex-rs/rmcp-client/src/perform_oauth_login.rs +++ b/codex-rs/rmcp-client/src/perform_oauth_login.rs @@ -22,6 +22,11 @@ use crate::save_oauth_tokens; use crate::utils::apply_default_headers; use crate::utils::build_default_headers; +struct OauthHeaders { + http_headers: Option>, + env_http_headers: Option>, +} + struct CallbackServerGuard { server: Arc, } @@ -40,12 +45,15 @@ pub async fn perform_oauth_login( env_http_headers: Option>, scopes: &[String], ) -> Result<()> { + let headers = OauthHeaders { + http_headers, + env_http_headers, + }; OauthLoginFlow::new( server_name, server_url, store_mode, - http_headers, - env_http_headers, + headers, scopes, true, None, @@ -64,12 +72,15 @@ pub async fn perform_oauth_login_return_url( scopes: &[String], timeout_secs: Option, ) -> Result { + let headers = OauthHeaders { + http_headers, + env_http_headers, + }; let flow = OauthLoginFlow::new( server_name, server_url, store_mode, - http_headers, - env_http_headers, + headers, scopes, false, timeout_secs, @@ -182,8 +193,7 @@ impl OauthLoginFlow { server_name: &str, server_url: &str, store_mode: OAuthCredentialsStoreMode, - http_headers: Option>, - env_http_headers: Option>, + headers: OauthHeaders, scopes: &[String], launch_browser: bool, timeout_secs: Option, @@ -213,6 +223,10 @@ impl OauthLoginFlow { let (tx, rx) = oneshot::channel(); spawn_callback_server(server, tx); + let OauthHeaders { + http_headers, + env_http_headers, + } = headers; let default_headers = build_default_headers(http_headers, env_http_headers)?; let http_client = apply_default_headers(ClientBuilder::new(), &default_headers).build()?;