[codex] Preserve disabled MCP servers across runtime overlays (#27414)

## Why

Recent MCP runtime overlay changes replace same-name configured server
entries with compatibility or extension-provided configs. Those
replacement configs default to enabled, so an MCP server explicitly
configured with `enabled = false` could be initialized anyway.

The connection manager still filters disabled servers correctly, but the
configured disabled state was lost before initialization reached that
filter.

## What changed

- Remember MCP servers that are disabled in the configured view before
applying runtime fallbacks and extension overlays.
- Restore `enabled = false` for those servers after overlays, while
leaving all other overlay fields and `Remove` precedence unchanged.
- Add focused extension-backed regression coverage for a disabled
`codex_apps` server.

## Testing

- `just fmt`
- `just test -p codex-mcp-extension`
- `just fix -p codex-core`
- `just fix -p codex-mcp-extension`

The full workspace `just test` suite was not run.
This commit is contained in:
e-provencher
2026-06-10 16:11:20 -04:00
committed by GitHub
Unverified
parent 7011044c1c
commit 980f60b664
2 changed files with 39 additions and 0 deletions
+11
View File
@@ -45,6 +45,12 @@ impl McpManager {
/// runtime-only extension overlays.
pub async fn runtime_config(&self, config: &Config) -> McpConfig {
let mut mcp_config = config.to_mcp_config(self.plugins_manager.as_ref()).await;
let disabled_server_names = mcp_config
.configured_mcp_servers
.iter()
.filter(|(_, server)| !server.enabled)
.map(|(name, _)| name.clone())
.collect::<Vec<_>>();
if mcp_config.apps_enabled {
mcp_config.configured_mcp_servers.insert(
CODEX_APPS_MCP_SERVER_NAME.to_string(),
@@ -60,6 +66,11 @@ impl McpManager {
}
let contributions = self.contributions(config).await;
Self::apply_to_configured_servers(&contributions, &mut mcp_config.configured_mcp_servers);
for name in disabled_server_names {
if let Some(server) = mcp_config.configured_mcp_servers.get_mut(&name) {
server.enabled = false;
}
}
mcp_config
}
+28
View File
@@ -42,6 +42,34 @@ async fn contributes_hosted_plugin_runtime_without_an_executor() -> TestResult {
Ok(())
}
#[tokio::test]
async fn runtime_overlay_preserves_disabled_server() -> TestResult {
let codex_home = tempfile::tempdir()?;
let config = ConfigBuilder::default()
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(codex_home.path().to_path_buf()))
.cli_overrides(vec![
("features.apps".to_string(), true.into()),
(
"mcp_servers.codex_apps.url".to_string(),
"https://example.com/mcp".into(),
),
("mcp_servers.codex_apps.enabled".to_string(), false.into()),
])
.build()
.await?;
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let manager = installed_manager(&config);
let servers = manager.effective_servers(&config, Some(&auth)).await;
let server = servers
.get(CODEX_APPS_MCP_SERVER_NAME)
.ok_or("hosted plugin runtime should remain configured")?;
assert!(!server.enabled());
Ok(())
}
#[tokio::test]
async fn legacy_fallback_overwrites_reserved_config_without_an_extension() -> TestResult {
let codex_home = tempfile::tempdir()?;