feat(mcp): support local apps endpoint override

This commit is contained in:
fbauer
2026-07-03 16:11:31 -07:00
parent 9732b406e7
commit c93bd54c7f
3 changed files with 99 additions and 8 deletions

View File

@@ -5,10 +5,33 @@ use codex_extension_api::McpServerContribution;
use codex_extension_api::McpServerContributionContext;
use codex_extension_api::McpServerContributor;
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
use codex_mcp::codex_apps_mcp_server_config;
use codex_mcp::hosted_plugin_runtime_mcp_server_config;
mod executor_plugin;
const CODEX_APPS_MCP_BASE_URL_ENV_VAR: &str = "CODEX_APPS_MCP_BASE_URL";
#[derive(Debug, Eq, PartialEq)]
enum AppsMcpServerTarget<'a> {
HostedPluginRuntime(&'a str),
CodexApps(&'a str),
}
fn apps_mcp_server_target<'a>(
chatgpt_base_url: &'a str,
apps_mcp_base_url_override: Option<&'a str>,
) -> AppsMcpServerTarget<'a> {
if let Some(apps_mcp_base_url) = apps_mcp_base_url_override
.map(str::trim)
.filter(|url| !url.is_empty())
{
return AppsMcpServerTarget::CodexApps(apps_mcp_base_url);
}
AppsMcpServerTarget::HostedPluginRuntime(chatgpt_base_url)
}
struct HostedPluginRuntimeExtension;
impl McpServerContributor<Config> for HostedPluginRuntimeExtension {
@@ -27,17 +50,32 @@ impl McpServerContributor<Config> for HostedPluginRuntimeExtension {
return vec![McpServerContribution::Remove { name }];
}
let apps_mcp_base_url_override = std::env::var(CODEX_APPS_MCP_BASE_URL_ENV_VAR).ok();
let apps_mcp_product_sku = config.apps_mcp_product_sku.as_deref();
let server_config = match apps_mcp_server_target(
&config.chatgpt_base_url,
apps_mcp_base_url_override.as_deref(),
) {
AppsMcpServerTarget::HostedPluginRuntime(base_url) => {
hosted_plugin_runtime_mcp_server_config(base_url, apps_mcp_product_sku)
}
AppsMcpServerTarget::CodexApps(base_url) => {
codex_apps_mcp_server_config(base_url, apps_mcp_product_sku)
}
};
vec![McpServerContribution::Set {
name,
config: Box::new(hosted_plugin_runtime_mcp_server_config(
&config.chatgpt_base_url,
config.apps_mcp_product_sku.as_deref(),
)),
config: Box::new(server_config),
}]
})
}
}
#[cfg(test)]
#[path = "lib_tests.rs"]
mod tests;
pub fn install(builder: &mut ExtensionRegistryBuilder<Config>) {
builder.mcp_server_contributor(std::sync::Arc::new(HostedPluginRuntimeExtension));
}

View File

@@ -0,0 +1,31 @@
use pretty_assertions::assert_eq;
use super::AppsMcpServerTarget;
use super::apps_mcp_server_target;
#[test]
fn default_uses_hosted_plugin_runtime() {
assert_eq!(
apps_mcp_server_target(
"https://chatgpt.com",
/*apps_mcp_base_url_override*/ None,
),
AppsMcpServerTarget::HostedPluginRuntime("https://chatgpt.com"),
);
}
#[test]
fn empty_override_uses_hosted_plugin_runtime() {
assert_eq!(
apps_mcp_server_target("https://chatgpt.com", Some(" ")),
AppsMcpServerTarget::HostedPluginRuntime("https://chatgpt.com"),
);
}
#[test]
fn explicit_override_uses_local_codex_apps_endpoint() {
assert_eq!(
apps_mcp_server_target("https://chatgpt.com", Some("http://127.0.0.1:8061"),),
AppsMcpServerTarget::CodexApps("http://127.0.0.1:8061"),
);
}

View File

@@ -11,12 +11,14 @@ use codex_extension_api::McpServerContributionContext;
use codex_extension_api::McpServerContributor;
use codex_login::CodexAuth;
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
use codex_mcp::codex_apps_mcp_server_config;
use codex_mcp::hosted_plugin_runtime_mcp_server_config;
use pretty_assertions::assert_eq;
type TestResult = Result<(), Box<dyn std::error::Error>>;
#[tokio::test]
async fn contributes_hosted_plugin_runtime_without_an_executor() -> TestResult {
async fn contributes_configured_apps_mcp_without_an_executor() -> TestResult {
let codex_home = tempfile::tempdir()?;
let config = ConfigBuilder::default()
.codex_home(codex_home.path().to_path_buf())
@@ -34,11 +36,31 @@ async fn contributes_hosted_plugin_runtime_without_an_executor() -> TestResult {
let server = servers
.get(CODEX_APPS_MCP_SERVER_NAME)
.and_then(|server| server.configured_config())
.ok_or("hosted plugin runtime should be contributed as a configured server")?;
.ok_or("Apps MCP should be contributed as a configured server")?;
let McpServerTransportConfig::StreamableHttp { url, .. } = &server.transport else {
panic!("hosted plugin runtime should use streamable HTTP");
panic!("Apps MCP should use streamable HTTP");
};
assert_eq!(url, "https://chatgpt.com/backend-api/ps/mcp");
let apps_mcp_base_url_override = std::env::var("CODEX_APPS_MCP_BASE_URL").ok();
let expected_config = match apps_mcp_base_url_override
.as_deref()
.map(str::trim)
.filter(|url| !url.is_empty())
{
Some(base_url) => {
codex_apps_mcp_server_config(base_url, /*apps_mcp_product_sku*/ None)
}
None => hosted_plugin_runtime_mcp_server_config(
"https://chatgpt.com",
/*apps_mcp_product_sku*/ None,
),
};
let McpServerTransportConfig::StreamableHttp {
url: expected_url, ..
} = expected_config.transport
else {
panic!("expected Apps MCP config should use streamable HTTP");
};
assert_eq!(url, &expected_url);
Ok(())
}