use std::collections::HashMap;use std::sync::Arc;use crate::config::Config;use codex_config::McpServerConfig;use codex_core_plugins::PluginsManager;use codex_extension_api::ExtensionDataInit;use codex_extension_api::ExtensionRegistry;use codex_extension_api::McpServerContribution;use codex_extension_api::McpServerContributionContext;use codex_login::CodexAuth;use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;use codex_mcp::EffectiveMcpServer;use codex_mcp::McpConfig;use codex_mcp::McpPluginAttribution;use codex_mcp::McpServerRegistration;use codex_mcp::codex_apps_mcp_server_config;use codex_mcp::configured_mcp_servers;use codex_mcp::effective_mcp_servers;const LEGACY_CODEX_APPS_REGISTRATION_ID: &str = "legacy_codex_apps";enum OrderedMcpOverlay { Set { contributor_id: &'static str, contribution_order: usize, name: String, config: Box<McpServerConfig>, }, Remove { contributor_id: &'static str, contribution_order: usize, name: String, },}#[derive(Clone)]pub struct McpManager { plugins_manager: Arc<PluginsManager>, extensions: Arc<ExtensionRegistry<Config>>,}impl McpManager { pub fn new(plugins_manager: Arc<PluginsManager>) -> Self { Self { plugins_manager, extensions: codex_extension_api::empty_extension_registry(), } } /// Creates a manager that resolves host-installed MCP contributions. pub fn new_with_extensions( plugins_manager: Arc<PluginsManager>, extensions: Arc<ExtensionRegistry<Config>>, ) -> Self { Self { plugins_manager, extensions, } } /// Returns the MCP config after applying compatibility built-ins and /// runtime-only extension overlays. pub async fn runtime_config(&self, config: &Config) -> McpConfig { self.runtime_config_with_context(config, /*thread_init*/ None) .await } pub(crate) async fn runtime_config_for_thread( &self, config: &Config, thread_init: &ExtensionDataInit, ) -> McpConfig { self.runtime_config_with_context(config, Some(thread_init)) .await } async fn runtime_config_with_context( &self, config: &Config, thread_init: Option<&ExtensionDataInit>, ) -> McpConfig { let context = match thread_init { Some(thread_init) => McpServerContributionContext::for_thread(config, thread_init), None => McpServerContributionContext::global(config), }; let mut selected_plugin_registrations = Vec::new(); let mut overlays = Vec::new(); // A contributor can emit multiple ordered actions, so order each action globally rather // than enumerating contributors. let mut contribution_order = 0; for contributor in self.extensions.mcp_server_contributors() { for contribution in contributor.contribute(context).await { match contribution { McpServerContribution::Set { name, config } => { overlays.push(OrderedMcpOverlay::Set { contributor_id: contributor.id(), contribution_order, name, config, }); } McpServerContribution::SelectedPlugin { name, plugin_id, plugin_display_name, selection_order, config, } => selected_plugin_registrations.push( McpServerRegistration::from_selected_plugin( name, McpPluginAttribution::new(plugin_id, plugin_display_name), selection_order, *config, ), ), McpServerContribution::Remove { name } => { overlays.push(OrderedMcpOverlay::Remove { contributor_id: contributor.id(), contribution_order, name, }); } } contribution_order += 1; } } let mut mcp_config = config .to_mcp_config_with_plugin_registrations( self.plugins_manager.as_ref(), selected_plugin_registrations, ) .await; let mut catalog = mcp_config.mcp_server_catalog.to_builder(); if mcp_config.apps_enabled { catalog.register(McpServerRegistration::from_compatibility( CODEX_APPS_MCP_SERVER_NAME.to_string(), LEGACY_CODEX_APPS_REGISTRATION_ID, codex_apps_mcp_server_config( &mcp_config.chatgpt_base_url, mcp_config.apps_mcp_product_sku.as_deref(), ), )); } else { catalog.remove_compatibility( CODEX_APPS_MCP_SERVER_NAME.to_string(), LEGACY_CODEX_APPS_REGISTRATION_ID, ); } for overlay in overlays { match overlay { OrderedMcpOverlay::Set { contributor_id, contribution_order, name, config, } => catalog.register(McpServerRegistration::from_extension( name, contributor_id, contribution_order, *config, )), OrderedMcpOverlay::Remove { contributor_id, contribution_order, name, } => catalog.remove_extension(name, contributor_id, contribution_order), } } let catalog = catalog.build(); for conflict in catalog.conflicts() { tracing::warn!( server = conflict.name, outcome = ?conflict.outcome, contenders = ?conflict.contenders, "conflicting MCP server actions; using resolved catalog outcome" ); } mcp_config.mcp_server_catalog = catalog; mcp_config } /// Returns config- and plugin-backed servers without runtime contributions. pub async fn configured_servers(&self, config: &Config) -> HashMap<String, McpServerConfig> { let mcp_config = config.to_mcp_config(self.plugins_manager.as_ref()).await; configured_mcp_servers(&mcp_config) } /// Returns configured and host-contributed servers before auth gating. pub async fn runtime_servers(&self, config: &Config) -> HashMap<String, McpServerConfig> { let mcp_config = self.runtime_config(config).await; configured_mcp_servers(&mcp_config) } /// Returns runtime servers after auth gating and compatibility built-ins. pub async fn effective_servers( &self, config: &Config, auth: Option<&CodexAuth>, ) -> HashMap<String, EffectiveMcpServer> { let mcp_config = self.runtime_config(config).await; effective_mcp_servers(&mcp_config, auth) }}