diff --git a/codex-rs/core/src/mcp/mod.rs b/codex-rs/core/src/mcp/mod.rs index 35c9975edb..e1aa48b0b4 100644 --- a/codex-rs/core/src/mcp/mod.rs +++ b/codex-rs/core/src/mcp/mod.rs @@ -30,8 +30,6 @@ const MCP_TOOL_NAME_PREFIX: &str = "mcp"; const MCP_TOOL_NAME_DELIMITER: &str = "__"; pub(crate) const CODEX_APPS_MCP_SERVER_NAME: &str = "codex_apps"; const CODEX_CONNECTORS_TOKEN_ENV_VAR: &str = "CODEX_CONNECTORS_TOKEN"; -const DEFAULT_CODEX_APPS_MCP_URL: &str = - "https://connectorsapi.gateway.unified-0.api.openai.com/v1/connectors/mcp/"; fn codex_apps_mcp_bearer_token_env_var() -> Option { match env::var(CODEX_CONNECTORS_TOKEN_ENV_VAR) { @@ -67,13 +65,37 @@ fn codex_apps_mcp_http_headers(auth: Option<&CodexAuth>) -> Option) -> String { - let base_url = configured_url +fn default_codex_apps_mcp_url(base_url: &str) -> String { + let mut base_url = base_url.trim_end_matches('/').to_string(); + if (base_url.starts_with("https://chatgpt.com") + || base_url.starts_with("https://chat.openai.com")) + && !base_url.contains("/backend-api") + { + base_url = format!("{base_url}/backend-api"); + } + if base_url.contains("/backend-api") { + format!("{base_url}/wham/apps") + } else if base_url.contains("/api/codex") { + format!("{base_url}/apps") + } else { + format!("{base_url}/api/codex/apps") + } +} + +fn codex_apps_mcp_url( + chatgpt_base_url: &str, + configured_url: Option<&str>, + active_profile: Option<&str>, +) -> String { + if let Some(configured_url) = configured_url + .filter(|_| active_profile == Some("dev")) .map(str::trim) .filter(|url| !url.is_empty()) - .unwrap_or(DEFAULT_CODEX_APPS_MCP_URL) - .trim_end_matches('/'); - format!("{base_url}/") + { + return configured_url.to_string(); + } + + default_codex_apps_mcp_url(chatgpt_base_url) } fn codex_apps_mcp_server_config(config: &Config, auth: Option<&CodexAuth>) -> McpServerConfig { @@ -83,7 +105,11 @@ fn codex_apps_mcp_server_config(config: &Config, auth: Option<&CodexAuth>) -> Mc } else { codex_apps_mcp_http_headers(auth) }; - let url = codex_apps_mcp_url(config.apps_mcp_url.as_deref()); + let url = codex_apps_mcp_url( + &config.chatgpt_base_url, + config.apps_mcp_url.as_deref(), + config.active_profile.as_deref(), + ); McpServerConfig { transport: McpServerTransportConfig::StreamableHttp { @@ -381,22 +407,46 @@ mod tests { } #[test] - fn codex_apps_mcp_url_defaults_to_internal_endpoint() { + fn codex_apps_mcp_url_defaults_to_original_chatgpt_base_behavior() { assert_eq!( - codex_apps_mcp_url(None), - "https://connectorsapi.gateway.unified-0.api.openai.com/v1/connectors/mcp/" + codex_apps_mcp_url("https://chatgpt.com", None, None), + "https://chatgpt.com/backend-api/wham/apps" ); } #[test] - fn codex_apps_mcp_url_normalizes_custom_value() { + fn codex_apps_mcp_url_uses_original_path_behavior_for_capi() { assert_eq!( - codex_apps_mcp_url(Some("https://example.com/custom/path")), - "https://example.com/custom/path/" + codex_apps_mcp_url("https://example.com/api/codex", None, None), + "https://example.com/api/codex/apps" ); assert_eq!( - codex_apps_mcp_url(Some("https://example.com/custom/path/")), - "https://example.com/custom/path/" + codex_apps_mcp_url("https://example.com/api/codex/", None, None), + "https://example.com/api/codex/apps" + ); + } + + #[test] + fn codex_apps_mcp_url_uses_configured_url_for_dev_profile() { + assert_eq!( + codex_apps_mcp_url( + "https://chatgpt.com", + Some("https://example.com/custom/path"), + Some("dev") + ), + "https://example.com/custom/path" + ); + } + + #[test] + fn codex_apps_mcp_url_uses_default_for_non_dev_profile() { + assert_eq!( + codex_apps_mcp_url( + "https://chatgpt.com", + Some("https://example.com/custom/path"), + Some("prod") + ), + "https://chatgpt.com/backend-api/wham/apps" ); } }