use std::collections::HashMap;use std::time::Duration;use codex_config::AppToolApproval;use codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID;use codex_config::McpServerConfig;use codex_config::McpServerToolConfig;use codex_config::McpServerTransportConfig;use pretty_assertions::assert_eq;use super::McpPluginAttribution;use super::McpServerConflict;use super::McpServerConflictAction;use super::McpServerRegistration;use super::McpServerSource;use super::ResolvedMcpCatalog;fn server(url: &str) -> McpServerConfig { McpServerConfig { transport: McpServerTransportConfig::StreamableHttp { url: url.to_string(), bearer_token_env_var: None, http_headers: None, env_http_headers: None, }, environment_id: DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(), enabled: true, required: true, supports_parallel_tool_calls: true, disabled_reason: None, startup_timeout_sec: Some(Duration::from_secs(7)), tool_timeout_sec: Some(Duration::from_secs(11)), default_tools_approval_mode: Some(AppToolApproval::Prompt), enabled_tools: Some(vec!["read".to_string()]), disabled_tools: Some(vec!["write".to_string()]), scopes: None, oauth: None, oauth_resource: None, tools: HashMap::from([( "read".to_string(), McpServerToolConfig { approval_mode: Some(AppToolApproval::Approve), }, )]), }}fn plugin(plugin_id: &str) -> McpPluginAttribution { McpPluginAttribution::new(plugin_id.to_string(), plugin_id.to_string())}fn plugin_source(plugin_id: &str) -> McpServerSource { McpServerSource::Plugin(plugin(plugin_id))}fn selected_plugin_source(plugin_id: &str) -> McpServerSource { McpServerSource::SelectedPlugin(plugin(plugin_id))}fn compatibility_source(id: &str) -> McpServerSource { McpServerSource::Compatibility { id: id.to_string() }}fn extension_source(id: &str) -> McpServerSource { McpServerSource::Extension { id: id.to_string() }}fn register(source: McpServerSource) -> McpServerConflictAction { McpServerConflictAction::Register(source)}fn remove(source: McpServerSource) -> McpServerConflictAction { McpServerConflictAction::Remove(source)}#[test]fn source_precedence_preserves_the_winning_registration() { let extension = server("https://extension.example/mcp"); let mut plugin_server = server("https://plugin.example/mcp"); plugin_server.enabled = false; let mut builder = ResolvedMcpCatalog::builder(); builder.register(McpServerRegistration::from_extension( "docs".to_string(), "hosted", /*contribution_order*/ 0, extension.clone(), )); builder.register(McpServerRegistration::from_plugin( "docs".to_string(), plugin("plugin@test"), /*plugin_order*/ 0, plugin_server, )); builder.register(McpServerRegistration::from_plugin( "docs".to_string(), plugin("other-plugin@test"), /*plugin_order*/ 1, server("https://other-plugin.example/mcp"), )); builder.register(McpServerRegistration::from_compatibility( "docs".to_string(), "legacy", server("https://compatibility.example/mcp"), )); builder.register(McpServerRegistration::from_config( "docs".to_string(), server("https://config.example/mcp"), )); let catalog = builder.build(); let resolved = catalog.server("docs").expect("resolved server"); assert_eq!( resolved.source(), &McpServerSource::Extension { id: "hosted".to_string(), } ); assert_eq!(resolved.config(), &extension); assert!(catalog.plugin_attributions_by_server_name().is_empty()); assert_eq!( catalog.conflicts(), &[McpServerConflict { name: "docs".to_string(), outcome: register(extension_source("hosted")), contenders: vec![ register(plugin_source("other-plugin@test")), register(plugin_source("plugin@test")), ], }] );}#[test]fn disabled_veto_only_disables_the_winning_registration() { let extension = server("https://extension.example/mcp"); let mut expected = extension.clone(); expected.enabled = false; let mut builder = ResolvedMcpCatalog::builder(); builder.register(McpServerRegistration::from_extension( "docs".to_string(), "hosted", /*contribution_order*/ 0, extension, )); builder.disable("docs".to_string()); let actual = builder .build() .server("docs") .expect("resolved server") .config() .clone(); assert_eq!(actual, expected);}#[test]fn disabled_winner_remains_a_veto_when_the_catalog_is_extended() { let mut disabled = server("https://config.example/mcp"); disabled.enabled = false; let mut expected = server("https://extension.example/mcp"); expected.enabled = false; let mut builder = ResolvedMcpCatalog::builder(); builder.register(McpServerRegistration::from_config( "docs".to_string(), disabled, )); let mut builder = builder.build().to_builder(); builder.register(McpServerRegistration::from_extension( "docs".to_string(), "hosted", /*contribution_order*/ 0, server("https://extension.example/mcp"), )); let resolved = builder.build(); assert_eq!( resolved.server("docs"), Some(&super::ResolvedMcpServer { source: extension_source("hosted"), config: expected, }) );}#[test]fn disabled_discovered_plugin_remains_a_veto_for_runtime_overlays() { let mut disabled = server("https://plugin.example/mcp"); disabled.enabled = false; let mut expected = server("https://extension.example/mcp"); expected.enabled = false; let mut builder = ResolvedMcpCatalog::builder(); builder.register(McpServerRegistration::from_plugin( "docs".to_string(), plugin("plugin@test"), /*plugin_order*/ 0, disabled, )); let mut builder = builder.build().to_builder(); builder.register(McpServerRegistration::from_extension( "docs".to_string(), "hosted", /*contribution_order*/ 0, server("https://extension.example/mcp"), )); let resolved = builder.build(); assert_eq!( resolved.server("docs"), Some(&super::ResolvedMcpServer { source: extension_source("hosted"), config: expected, }) );}#[test]fn earlier_plugin_wins_with_an_explicit_conflict() { let mut builder = ResolvedMcpCatalog::builder(); builder.register(McpServerRegistration::from_plugin( "docs".to_string(), plugin("alpha@test"), /*plugin_order*/ 0, server("https://alpha.example/mcp"), )); builder.register(McpServerRegistration::from_plugin( "docs".to_string(), plugin("beta@test"), /*plugin_order*/ 1, server("https://beta.example/mcp"), )); let catalog = builder.build(); assert_eq!( catalog.plugin_attributions_by_server_name(), HashMap::from([("docs".to_string(), plugin("alpha@test"))]) ); assert_eq!( catalog.conflicts(), &[McpServerConflict { name: "docs".to_string(), outcome: register(plugin_source("alpha@test")), contenders: vec![ register(plugin_source("beta@test")), register(plugin_source("alpha@test")), ], }] );}#[test]fn selected_plugins_override_discovered_plugins_but_not_config() { let selected = server("https://selected-alpha.example/mcp"); let mut discovered = server("https://local.example/mcp"); discovered.enabled = false; discovered.default_tools_approval_mode = Some(AppToolApproval::Auto); let mut builder = ResolvedMcpCatalog::builder(); builder.register(McpServerRegistration::from_plugin( "docs".to_string(), plugin("local@test"), /*plugin_order*/ 0, discovered, )); builder.register(McpServerRegistration::from_selected_plugin( "docs".to_string(), plugin("selected-beta"), /*selection_order*/ 1, server("https://selected-beta.example/mcp"), )); builder.register(McpServerRegistration::from_selected_plugin( "docs".to_string(), plugin("selected-alpha"), /*selection_order*/ 0, selected.clone(), )); let catalog = builder.build(); assert_eq!( catalog.server("docs"), Some(&super::ResolvedMcpServer { source: selected_plugin_source("selected-alpha"), config: selected, }) ); assert_eq!( catalog.plugin_attributions_by_server_name(), HashMap::from([("docs".to_string(), plugin("selected-alpha"))]) ); assert_eq!( catalog.conflicts(), &[McpServerConflict { name: "docs".to_string(), outcome: register(selected_plugin_source("selected-alpha")), contenders: vec![ register(selected_plugin_source("selected-beta")), register(selected_plugin_source("selected-alpha")), ], }] ); let mut builder = catalog.to_builder(); let configured = server("https://config.example/mcp"); builder.register(McpServerRegistration::from_config( "docs".to_string(), configured.clone(), )); let catalog = builder.build(); assert_eq!( catalog.server("docs"), Some(&super::ResolvedMcpServer { source: McpServerSource::Config, config: configured, }) );}#[test]fn disabled_selected_plugin_does_not_veto_runtime_overlays() { let mut disabled = server("https://selected.example/mcp"); disabled.enabled = false; let extension = server("https://extension.example/mcp"); let mut builder = ResolvedMcpCatalog::builder(); builder.register(McpServerRegistration::from_selected_plugin( "docs".to_string(), plugin("selected"), /*selection_order*/ 0, disabled, )); let mut builder = builder.build().to_builder(); builder.register(McpServerRegistration::from_extension( "docs".to_string(), "hosted", /*contribution_order*/ 0, extension.clone(), )); let resolved = builder.build(); assert_eq!( resolved.server("docs"), Some(&super::ResolvedMcpServer { source: extension_source("hosted"), config: extension, }) );}#[test]fn equal_precedence_uses_insertion_order_not_source_identity() { let mut builder = ResolvedMcpCatalog::builder(); builder.register(McpServerRegistration::from_compatibility( "docs".to_string(), "z-first", server("https://first.example/mcp"), )); builder.register(McpServerRegistration::from_compatibility( "docs".to_string(), "a-second", server("https://second.example/mcp"), )); let catalog = builder.build(); assert_eq!( catalog.server("docs"), Some(&super::ResolvedMcpServer { source: compatibility_source("a-second"), config: server("https://second.example/mcp"), }) ); let mut builder = catalog.to_builder(); builder.remove_compatibility("docs".to_string(), "remove-last"); let catalog = builder.build(); assert_eq!(catalog.server("docs"), None); assert_eq!( catalog.conflicts(), &[McpServerConflict { name: "docs".to_string(), outcome: remove(compatibility_source("remove-last")), contenders: vec![ register(compatibility_source("z-first")), register(compatibility_source("a-second")), remove(compatibility_source("remove-last")), ], }] );}