mirror of
https://github.com/openai/codex.git
synced 2026-11-05 16:29:31 +00:00
Route Codex Apps MCP through plugin service (#34389)
## What changed - Point the default Codex Apps MCP server at `ps/mcp` instead of the legacy Apps endpoint for both `backend-api` and `api/codex` base URLs. - Use the same server configuration for Codex Apps and the hosted plugin runtime. - Update MCP integration tests and test-server routing to expect the plugin-service path. GitOrigin-RevId: 939f20dcff67ba6f79c11b328bfc624b25e3aac2
This commit is contained in:
1 parent
5a4f5ee64c
commit
6bf4845b60
6 files changed
+24
-37
No files matched your search
@@ -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 {
|
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 = normalize_codex_apps_base_url(base_url);
|
||||||
let (base_url, default_path) = if base_url.contains("/backend-api") {
|
let base_url = if base_url.contains("/backend-api") || base_url.contains("/api/codex") {
|
||||||
(base_url, "wham/apps")
|
base_url
|
||||||
} else if base_url.contains("/api/codex") {
|
|
||||||
(base_url, "apps")
|
|
||||||
} else {
|
} 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(
|
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>,
|
apps_mcp_product_sku: Option<&str>,
|
||||||
originator: Option<&str>,
|
originator: Option<&str>,
|
||||||
) -> McpServerConfig {
|
) -> McpServerConfig {
|
||||||
let base_url = normalize_codex_apps_base_url(chatgpt_base_url);
|
codex_apps_mcp_server_config(chatgpt_base_url, apps_mcp_product_sku, originator)
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mcp_server_config_for_url(
|
fn mcp_server_config_for_url(
|
||||||
|
|||||||
@@ -239,27 +239,27 @@ fn selected_mcp_attribution_does_not_join_an_unrelated_local_summary() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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!(
|
assert_eq!(
|
||||||
codex_apps_mcp_url_for_base_url("https://chatgpt.com/backend-api"),
|
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!(
|
assert_eq!(
|
||||||
codex_apps_mcp_url_for_base_url("https://chat.openai.com"),
|
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!(
|
assert_eq!(
|
||||||
codex_apps_mcp_url_for_base_url("http://localhost:8080/api/codex"),
|
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!(
|
assert_eq!(
|
||||||
codex_apps_mcp_url_for_base_url("http://localhost:8080"),
|
codex_apps_mcp_url_for_base_url("http://localhost:8080"),
|
||||||
"http://localhost:8080/api/codex/apps"
|
"http://localhost:8080/api/codex/ps/mcp"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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(
|
let config = codex_apps_mcp_server_config(
|
||||||
"https://chatgpt.com",
|
"https://chatgpt.com",
|
||||||
/*apps_mcp_product_sku*/ None,
|
/*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"),
|
_ => 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]
|
#[test]
|
||||||
@@ -460,7 +460,7 @@ async fn effective_mcp_servers_preserve_runtime_servers() {
|
|||||||
}
|
}
|
||||||
match &codex_apps.transport {
|
match &codex_apps.transport {
|
||||||
McpServerTransportConfig::StreamableHttp { url, .. } => {
|
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:?}"),
|
other => panic!("expected streamable http transport, got {other:?}"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const DISCOVERABLE_CALENDAR_ID: &str = "connector_2128aebfecb84f64a069897515042a
|
|||||||
const DISCOVERABLE_GMAIL_ID: &str = "connector_68df038e0ba48191908c8434991bbac2";
|
const DISCOVERABLE_GMAIL_ID: &str = "connector_68df038e0ba48191908c8434991bbac2";
|
||||||
const CONNECTOR_DESCRIPTION: &str = "Plan events and manage your calendar.";
|
const CONNECTOR_DESCRIPTION: &str = "Plan events and manage your calendar.";
|
||||||
const CODEX_APPS_META_KEY: &str = "_codex_apps";
|
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 HOSTED_PLUGIN_RUNTIME_MCP_PATH_REGEX: &str = "^/api/codex/ps/mcp/?$";
|
||||||
const PROTOCOL_VERSION: &str = "2025-11-25";
|
const PROTOCOL_VERSION: &str = "2025-11-25";
|
||||||
const SERVER_NAME: &str = "codex-apps-test";
|
const SERVER_NAME: &str = "codex-apps-test";
|
||||||
@@ -288,7 +288,7 @@ pub async fn recorded_apps_tool_calls(server: &MockServer) -> Vec<Value> {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|request| {
|
.filter_map(|request| {
|
||||||
let body: Value = serde_json::from_slice(&request.body).ok()?;
|
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"))
|
&& body.get("method").and_then(Value::as_str) == Some("tools/call"))
|
||||||
.then_some(body)
|
.then_some(body)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ async fn codex_apps_auth_failure_requests_elicitation_by_default() -> Result<()>
|
|||||||
let server = start_mock_server().await;
|
let server = start_mock_server().await;
|
||||||
let apps_server = AppsTestServer::mount_searchable(&server).await?;
|
let apps_server = AppsTestServer::mount_searchable(&server).await?;
|
||||||
Mock::given(method("POST"))
|
Mock::given(method("POST"))
|
||||||
.and(path_regex("^/api/codex/apps/?$"))
|
.and(path_regex("^/api/codex/ps/mcp/?$"))
|
||||||
.and(body_partial_json(json!({
|
.and(body_partial_json(json!({
|
||||||
"method": "tools/call",
|
"method": "tools/call",
|
||||||
"params": {
|
"params": {
|
||||||
|
|||||||
@@ -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 server = responses::start_mock_server().await;
|
||||||
let untrusted_server = MockServer::start().await;
|
let untrusted_server = MockServer::start().await;
|
||||||
let untrusted_apps = AppsTestServer::mount(&untrusted_server).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 untrusted_chatgpt_base_url = untrusted_apps.chatgpt_base_url;
|
||||||
|
|
||||||
let fixture = test_codex()
|
let fixture = test_codex()
|
||||||
@@ -2484,7 +2484,7 @@ async fn streamable_http_chatgpt_auth_is_not_sent_to_configured_origin() -> anyh
|
|||||||
.await
|
.await
|
||||||
.expect("mock server should capture MCP startup requests")
|
.expect("mock server should capture MCP startup requests")
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|request| request.url.path() == "/api/codex/apps")
|
.filter(|request| request.url.path() == "/api/codex/ps/mcp")
|
||||||
.filter_map(|request| {
|
.filter_map(|request| {
|
||||||
let body: Value = serde_json::from_slice(&request.body).ok()?;
|
let body: Value = serde_json::from_slice(&request.body).ok()?;
|
||||||
let method = body.get("method")?.as_str()?.to_string();
|
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 server = responses::start_mock_server().await;
|
||||||
let untrusted_server = MockServer::start().await;
|
let untrusted_server = MockServer::start().await;
|
||||||
let untrusted_apps = AppsTestServer::mount(&untrusted_server).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 untrusted_chatgpt_base_url = untrusted_apps.chatgpt_base_url;
|
||||||
|
|
||||||
let fixture = test_codex()
|
let fixture = test_codex()
|
||||||
@@ -2545,7 +2545,7 @@ auth = "chatgpt"
|
|||||||
.await
|
.await
|
||||||
.expect("mock server should capture MCP startup requests")
|
.expect("mock server should capture MCP startup requests")
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|request| request.url.path() == "/api/codex/apps")
|
.filter(|request| request.url.path() == "/api/codex/ps/mcp")
|
||||||
.filter_map(|request| {
|
.filter_map(|request| {
|
||||||
let body: Value = serde_json::from_slice(&request.body).ok()?;
|
let body: Value = serde_json::from_slice(&request.body).ok()?;
|
||||||
let method = body.get("method")?.as_str()?.to_string();
|
let method = body.get("method")?.as_str()?.to_string();
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ async fn runtime_overlay_preserves_disabled_server() -> TestResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[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 codex_home = tempfile::tempdir()?;
|
||||||
let config = ConfigBuilder::default()
|
let config = ConfigBuilder::default()
|
||||||
.codex_home(codex_home.path().to_path_buf())
|
.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
|
let server = servers
|
||||||
.get(CODEX_APPS_MCP_SERVER_NAME)
|
.get(CODEX_APPS_MCP_SERVER_NAME)
|
||||||
.and_then(|server| server.configured_config())
|
.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 {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user