diff --git a/codex-rs/codex-mcp/src/mcp/mod.rs b/codex-rs/codex-mcp/src/mcp/mod.rs index ad42c5425c..8026703124 100644 --- a/codex-rs/codex-mcp/src/mcp/mod.rs +++ b/codex-rs/codex-mcp/src/mcp/mod.rs @@ -475,14 +475,12 @@ fn normalize_codex_apps_base_url(base_url: &str) -> String { fn codex_apps_mcp_url_for_base_url(base_url: &str) -> String { let base_url = normalize_codex_apps_base_url(base_url); - let (base_url, default_path) = if base_url.contains("/backend-api") { - (base_url, "wham/apps") - } else if base_url.contains("/api/codex") { - (base_url, "apps") + let base_url = if base_url.contains("/backend-api") || base_url.contains("/api/codex") { + base_url } else { - (format!("{base_url}/api/codex"), "apps") + format!("{base_url}/api/codex") }; - format!("{base_url}/{default_path}") + format!("{base_url}/ps/mcp") } pub fn codex_apps_mcp_server_config( @@ -504,18 +502,7 @@ pub fn hosted_plugin_runtime_mcp_server_config( apps_mcp_product_sku: Option<&str>, originator: Option<&str>, ) -> McpServerConfig { - let base_url = normalize_codex_apps_base_url(chatgpt_base_url); - let base_url = if base_url.contains("/backend-api") || base_url.contains("/api/codex") { - base_url - } else { - format!("{base_url}/api/codex") - }; - mcp_server_config_for_url( - format!("{base_url}/ps/mcp"), - apps_mcp_product_sku, - originator, - McpServerAuth::ChatGpt, - ) + codex_apps_mcp_server_config(chatgpt_base_url, apps_mcp_product_sku, originator) } fn mcp_server_config_for_url( diff --git a/codex-rs/codex-mcp/src/mcp/mod_tests.rs b/codex-rs/codex-mcp/src/mcp/mod_tests.rs index 99143673e4..fb84700b15 100644 --- a/codex-rs/codex-mcp/src/mcp/mod_tests.rs +++ b/codex-rs/codex-mcp/src/mcp/mod_tests.rs @@ -239,27 +239,27 @@ fn selected_mcp_attribution_does_not_join_an_unrelated_local_summary() { } #[test] -fn codex_apps_mcp_url_for_base_url_keeps_existing_paths() { +fn codex_apps_mcp_url_for_base_url_uses_plugin_service_paths() { assert_eq!( codex_apps_mcp_url_for_base_url("https://chatgpt.com/backend-api"), - "https://chatgpt.com/backend-api/wham/apps" + "https://chatgpt.com/backend-api/ps/mcp" ); assert_eq!( codex_apps_mcp_url_for_base_url("https://chat.openai.com"), - "https://chat.openai.com/backend-api/wham/apps" + "https://chat.openai.com/backend-api/ps/mcp" ); assert_eq!( codex_apps_mcp_url_for_base_url("http://localhost:8080/api/codex"), - "http://localhost:8080/api/codex/apps" + "http://localhost:8080/api/codex/ps/mcp" ); assert_eq!( codex_apps_mcp_url_for_base_url("http://localhost:8080"), - "http://localhost:8080/api/codex/apps" + "http://localhost:8080/api/codex/ps/mcp" ); } #[test] -fn codex_apps_server_config_uses_legacy_codex_apps_path() { +fn codex_apps_server_config_uses_plugin_service_path() { let config = codex_apps_mcp_server_config( "https://chatgpt.com", /*apps_mcp_product_sku*/ None, @@ -270,7 +270,7 @@ fn codex_apps_server_config_uses_legacy_codex_apps_path() { _ => panic!("expected streamable http transport for codex apps"), }; - assert_eq!(url, "https://chatgpt.com/backend-api/wham/apps"); + assert_eq!(url, "https://chatgpt.com/backend-api/ps/mcp"); } #[test] @@ -460,7 +460,7 @@ async fn effective_mcp_servers_preserve_runtime_servers() { } match &codex_apps.transport { McpServerTransportConfig::StreamableHttp { url, .. } => { - assert_eq!(url, "https://chatgpt.com/backend-api/wham/apps"); + assert_eq!(url, "https://chatgpt.com/backend-api/ps/mcp"); } other => panic!("expected streamable http transport, got {other:?}"), } diff --git a/codex-rs/core/tests/common/apps_test_server.rs b/codex-rs/core/tests/common/apps_test_server.rs index 990e52973a..ba619dd25a 100644 --- a/codex-rs/core/tests/common/apps_test_server.rs +++ b/codex-rs/core/tests/common/apps_test_server.rs @@ -26,7 +26,7 @@ const DISCOVERABLE_CALENDAR_ID: &str = "connector_2128aebfecb84f64a069897515042a const DISCOVERABLE_GMAIL_ID: &str = "connector_68df038e0ba48191908c8434991bbac2"; const CONNECTOR_DESCRIPTION: &str = "Plan events and manage your calendar."; const CODEX_APPS_META_KEY: &str = "_codex_apps"; -const CODEX_APPS_MCP_PATH_REGEX: &str = "^/api/codex/apps/?$"; +const CODEX_APPS_MCP_PATH_REGEX: &str = "^/api/codex/ps/mcp/?$"; const HOSTED_PLUGIN_RUNTIME_MCP_PATH_REGEX: &str = "^/api/codex/ps/mcp/?$"; const PROTOCOL_VERSION: &str = "2025-11-25"; const SERVER_NAME: &str = "codex-apps-test"; @@ -288,7 +288,7 @@ pub async fn recorded_apps_tool_calls(server: &MockServer) -> Vec { .into_iter() .filter_map(|request| { let body: Value = serde_json::from_slice(&request.body).ok()?; - (request.url.path() == "/api/codex/apps" + (request.url.path() == "/api/codex/ps/mcp" && body.get("method").and_then(Value::as_str) == Some("tools/call")) .then_some(body) }) diff --git a/codex-rs/core/tests/suite/mcp_auth_elicitation.rs b/codex-rs/core/tests/suite/mcp_auth_elicitation.rs index 735006d495..f41c42b48f 100644 --- a/codex-rs/core/tests/suite/mcp_auth_elicitation.rs +++ b/codex-rs/core/tests/suite/mcp_auth_elicitation.rs @@ -77,7 +77,7 @@ async fn codex_apps_auth_failure_requests_elicitation_by_default() -> Result<()> let server = start_mock_server().await; let apps_server = AppsTestServer::mount_searchable(&server).await?; Mock::given(method("POST")) - .and(path_regex("^/api/codex/apps/?$")) + .and(path_regex("^/api/codex/ps/mcp/?$")) .and(body_partial_json(json!({ "method": "tools/call", "params": { diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index 63c4d38e75..2d1f33b9cf 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -2453,7 +2453,7 @@ async fn streamable_http_chatgpt_auth_is_not_sent_to_configured_origin() -> anyh let server = responses::start_mock_server().await; let untrusted_server = MockServer::start().await; let untrusted_apps = AppsTestServer::mount(&untrusted_server).await?; - let untrusted_mcp_url = format!("{}/api/codex/apps", untrusted_apps.chatgpt_base_url); + let untrusted_mcp_url = format!("{}/api/codex/ps/mcp", untrusted_apps.chatgpt_base_url); let untrusted_chatgpt_base_url = untrusted_apps.chatgpt_base_url; let fixture = test_codex() @@ -2484,7 +2484,7 @@ async fn streamable_http_chatgpt_auth_is_not_sent_to_configured_origin() -> anyh .await .expect("mock server should capture MCP startup requests") .into_iter() - .filter(|request| request.url.path() == "/api/codex/apps") + .filter(|request| request.url.path() == "/api/codex/ps/mcp") .filter_map(|request| { let body: Value = serde_json::from_slice(&request.body).ok()?; let method = body.get("method")?.as_str()?.to_string(); @@ -2516,7 +2516,7 @@ async fn configured_chatgpt_base_url_does_not_grant_mcp_chatgpt_auth() -> anyhow let server = responses::start_mock_server().await; let untrusted_server = MockServer::start().await; let untrusted_apps = AppsTestServer::mount(&untrusted_server).await?; - let untrusted_mcp_url = format!("{}/api/codex/apps", untrusted_apps.chatgpt_base_url); + let untrusted_mcp_url = format!("{}/api/codex/ps/mcp", untrusted_apps.chatgpt_base_url); let untrusted_chatgpt_base_url = untrusted_apps.chatgpt_base_url; let fixture = test_codex() @@ -2545,7 +2545,7 @@ auth = "chatgpt" .await .expect("mock server should capture MCP startup requests") .into_iter() - .filter(|request| request.url.path() == "/api/codex/apps") + .filter(|request| request.url.path() == "/api/codex/ps/mcp") .filter_map(|request| { let body: Value = serde_json::from_slice(&request.body).ok()?; let method = body.get("method")?.as_str()?.to_string(); diff --git a/codex-rs/ext/mcp/tests/hosted_apps_mcp.rs b/codex-rs/ext/mcp/tests/hosted_apps_mcp.rs index 66229713ce..9e5ae1c52f 100644 --- a/codex-rs/ext/mcp/tests/hosted_apps_mcp.rs +++ b/codex-rs/ext/mcp/tests/hosted_apps_mcp.rs @@ -72,7 +72,7 @@ async fn runtime_overlay_preserves_disabled_server() -> TestResult { } #[tokio::test] -async fn legacy_fallback_overwrites_reserved_config_without_an_extension() -> TestResult { +async fn default_fallback_overwrites_reserved_config_without_an_extension() -> TestResult { let codex_home = tempfile::tempdir()?; let config = ConfigBuilder::default() .codex_home(codex_home.path().to_path_buf()) @@ -95,11 +95,11 @@ async fn legacy_fallback_overwrites_reserved_config_without_an_extension() -> Te let server = servers .get(CODEX_APPS_MCP_SERVER_NAME) .and_then(|server| server.configured_config()) - .ok_or("legacy Apps MCP should be present")?; + .ok_or("default Apps MCP should be present")?; let McpServerTransportConfig::StreamableHttp { url, .. } = &server.transport else { - panic!("legacy Apps MCP should use streamable HTTP"); + panic!("default Apps MCP should use streamable HTTP"); }; - assert_eq!(url, "https://chatgpt.com/backend-api/wham/apps"); + assert_eq!(url, "https://chatgpt.com/backend-api/ps/mcp"); Ok(()) }