[apps] Add apps MCP path override (#20231)

Summary

- Add `[features.apps_mcp_path_override]` config with a `path` field for
overriding only the built-in apps MCP path.
- Keep existing host/base URL derivation unchanged and append the
configured path after that base.
- Regenerate the config schema with the custom feature-config case.

Test Plan

- Not run for latest revision; only `just fmt` and `just
write-config-schema` were run.
- Earlier revision: `cargo test -p codex-features`
- Earlier revision: `cargo test -p codex-mcp`
This commit is contained in:
Alex Daley
2026-04-29 18:08:06 -04:00
committed by GitHub
Unverified
parent 8d5da3ffe5
commit f63b19bedd
9 changed files with 183 additions and 11 deletions
+16 -7
View File
@@ -93,6 +93,8 @@ pub fn mcp_permission_prompt_is_auto_approved(
pub struct McpConfig {
/// Base URL for ChatGPT-hosted app MCP servers, copied from the root config.
pub chatgpt_base_url: String,
/// Optional path override for the built-in apps MCP server.
pub apps_mcp_path_override: Option<String>,
/// Codex home directory used for MCP OAuth state and app-tool cache files.
pub codex_home: PathBuf,
/// Preferred credential store for MCP OAuth tokens.
@@ -333,7 +335,10 @@ pub async fn collect_mcp_snapshot_from_manager(
}
pub(crate) fn codex_apps_mcp_url(config: &McpConfig) -> String {
codex_apps_mcp_url_for_base_url(&config.chatgpt_base_url)
codex_apps_mcp_url_for_base_url(
&config.chatgpt_base_url,
config.apps_mcp_path_override.as_deref(),
)
}
/// The Responses API requires tool names to match `^[a-zA-Z0-9_-]+$`.
@@ -376,15 +381,19 @@ fn normalize_codex_apps_base_url(base_url: &str) -> String {
base_url
}
fn codex_apps_mcp_url_for_base_url(base_url: &str) -> String {
fn codex_apps_mcp_url_for_base_url(base_url: &str, apps_mcp_path_override: Option<&str>) -> String {
let base_url = normalize_codex_apps_base_url(base_url);
if base_url.contains("/backend-api") {
format!("{base_url}/wham/apps")
let (base_url, default_path) = if base_url.contains("/backend-api") {
(base_url, "wham/apps")
} else if base_url.contains("/api/codex") {
format!("{base_url}/apps")
(base_url, "apps")
} else {
format!("{base_url}/api/codex/apps")
}
(format!("{base_url}/api/codex"), "apps")
};
let path = apps_mcp_path_override
.unwrap_or(default_path)
.trim_start_matches('/');
format!("{base_url}/{path}")
}
fn codex_apps_mcp_server_config(config: &McpConfig) -> McpServerConfig {
+36 -4
View File
@@ -14,6 +14,7 @@ use std::path::PathBuf;
fn test_mcp_config(codex_home: PathBuf) -> McpConfig {
McpConfig {
chatgpt_base_url: "https://chatgpt.com".to_string(),
apps_mcp_path_override: None,
codex_home,
mcp_oauth_credentials_store_mode: OAuthCredentialsStoreMode::default(),
mcp_oauth_callback_port: None,
@@ -109,19 +110,31 @@ fn tool_plugin_provenance_collects_app_and_mcp_sources() {
#[test]
fn codex_apps_mcp_url_for_base_url_keeps_existing_paths() {
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",
/*apps_mcp_path_override*/ None,
),
"https://chatgpt.com/backend-api/wham/apps"
);
assert_eq!(
codex_apps_mcp_url_for_base_url("https://chat.openai.com"),
codex_apps_mcp_url_for_base_url(
"https://chat.openai.com",
/*apps_mcp_path_override*/ None,
),
"https://chat.openai.com/backend-api/wham/apps"
);
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",
/*apps_mcp_path_override*/ None,
),
"http://localhost:8080/api/codex/apps"
);
assert_eq!(
codex_apps_mcp_url_for_base_url("http://localhost:8080"),
codex_apps_mcp_url_for_base_url(
"http://localhost:8080",
/*apps_mcp_path_override*/ None,
),
"http://localhost:8080/api/codex/apps"
);
}
@@ -158,6 +171,25 @@ fn codex_apps_server_config_uses_legacy_codex_apps_path() {
assert_eq!(url, "https://chatgpt.com/backend-api/wham/apps");
}
#[test]
fn codex_apps_server_config_uses_configured_apps_mcp_path_override() {
let mut config = test_mcp_config(PathBuf::from("/tmp"));
config.apps_mcp_path_override = Some("/custom/mcp".to_string());
config.apps_enabled = true;
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let servers = with_codex_apps_mcp(HashMap::new(), Some(&auth), &config);
let server = servers
.get(CODEX_APPS_MCP_SERVER_NAME)
.expect("codex apps should be present when apps is enabled");
let url = match &server.transport {
McpServerTransportConfig::StreamableHttp { url, .. } => url,
_ => panic!("expected streamable http transport for codex apps"),
};
assert_eq!(url, "https://chatgpt.com/backend-api/custom/mcp");
}
#[tokio::test]
async fn effective_mcp_servers_preserve_user_servers_and_add_codex_apps() {
let codex_home = tempfile::tempdir().expect("tempdir");