Download codex-rs/codex-mcp/src/mcp/mod_tests.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 24.8 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/codex-mcp/src/mcp/mod_tests.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/codex-mcp/src/mcp/mod_tests.rs
-
curl -L -o mod_tests.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/codex-mcp/src/mcp/mod_tests.rs
24.8 kB
| use super::*; | |
| use crate::McpPluginAttribution; | |
| use crate::McpServerRegistration; | |
| use crate::connection_manager::tests::create_ready_async_managed_client; | |
| use crate::mcp::auth::McpAuthStatusEntry; | |
| use crate::rmcp_client::StartupOutcomeError; | |
| use codex_config::Constrained; | |
| use codex_config::types::AppToolApproval; | |
| use codex_config::types::AuthKeyringBackendKind; | |
| use codex_login::CodexAuth; | |
| use codex_plugin::AppConnectorId; | |
| use codex_plugin::PluginCapabilitySummary; | |
| use codex_protocol::models::ManagedFileSystemPermissions; | |
| use codex_protocol::models::PermissionProfile; | |
| use codex_protocol::permissions::NetworkSandboxPolicy; | |
| use codex_protocol::protocol::AskForApproval; | |
| use codex_protocol::protocol::GranularApprovalConfig; | |
| use codex_rmcp_client::McpAuthState; | |
| use futures::FutureExt; | |
| use pretty_assertions::assert_eq; | |
| use std::collections::HashMap; | |
| use std::collections::HashSet; | |
| use std::path::PathBuf; | |
| use std::sync::Arc; | |
| async fn status_snapshot_only_downgrades_oauth_authentication_failures() { | |
| let auth_failure = StartupOutcomeError::Failed { | |
| error: "OAuth refresh token was rejected".to_string(), | |
| is_authentication_required: true, | |
| }; | |
| let mut manager = McpConnectionSet::empty(/*prefix_mcp_tool_names*/ true); | |
| let mut auth_status_entries = HashMap::new(); | |
| for (name, auth_state, startup_error) in [ | |
| ( | |
| "oauth-failed", | |
| McpAuthState::OAuth, | |
| Some(auth_failure.clone()), | |
| ), | |
| ("oauth-ready", McpAuthState::OAuth, None), | |
| ( | |
| "oauth-provider-error", | |
| McpAuthState::OAuth, | |
| Some(StartupOutcomeError::Failed { | |
| error: "provider temporarily unavailable".to_string(), | |
| is_authentication_required: false, | |
| }), | |
| ), | |
| ("bearer", McpAuthState::BearerToken, Some(auth_failure)), | |
| ] { | |
| let mut client = create_ready_async_managed_client(Vec::new()).await; | |
| if let Some(error) = startup_error { | |
| client.client = futures::future::ready(Err(error)).boxed().shared(); | |
| } | |
| manager.insert_test_client(name, client); | |
| auth_status_entries.insert( | |
| name.to_string(), | |
| McpAuthStatusEntry { | |
| config: None, | |
| auth_state, | |
| }, | |
| ); | |
| } | |
| let server_names = auth_status_entries.keys().cloned().collect(); | |
| let snapshot = collect_mcp_server_status_snapshot_from_manager( | |
| &manager, | |
| auth_status_entries, | |
| server_names, | |
| McpSnapshotDetail::ToolsAndAuthOnly, | |
| ) | |
| .await; | |
| assert_eq!( | |
| snapshot.auth_statuses, | |
| HashMap::from([ | |
| ("oauth-failed".to_string(), McpAuthStatus::NotLoggedIn), | |
| ("oauth-ready".to_string(), McpAuthStatus::OAuth), | |
| ("oauth-provider-error".to_string(), McpAuthStatus::OAuth), | |
| ("bearer".to_string(), McpAuthStatus::BearerToken), | |
| ]), | |
| ); | |
| } | |
| pub(crate) fn test_mcp_config(codex_home: PathBuf) -> McpConfig { | |
| McpConfig { | |
| chatgpt_base_url: "https://chatgpt.com".to_string(), | |
| apps_mcp_product_sku: None, | |
| codex_home, | |
| mcp_enterprise_managed_auth: None, | |
| xaa_enabled: false, | |
| mcp_oauth_credentials_store_mode: OAuthCredentialsStoreMode::default(), | |
| oauth_refresh_mode: McpOAuthRefreshMode::Legacy, | |
| auth_keyring_backend_kind: AuthKeyringBackendKind::default(), | |
| mcp_oauth_callback_port: None, | |
| mcp_oauth_callback_url: None, | |
| optional_mcp_startup_grace: DEFAULT_OPTIONAL_MCP_STARTUP_GRACE, | |
| skill_mcp_dependency_install_enabled: true, | |
| approval_policy: Constrained::allow_any(AskForApproval::OnRequest), | |
| permission_profile: PermissionProfile::default(), | |
| config_layer_stack: codex_config::ConfigLayerStack::default(), | |
| approvals_reviewer: codex_config::types::ApprovalsReviewer::default(), | |
| environment_cwds: HashMap::new(), | |
| server_permission_profiles: HashMap::new(), | |
| codex_linux_sandbox_exe: None, | |
| use_legacy_landlock: false, | |
| apps_enabled: false, | |
| prefix_mcp_tool_names: true, | |
| non_prefixed_mcp_tool_servers: Vec::new(), | |
| protocol_mode: McpProtocolMode::Legacy, | |
| host_owned_apps_protocol_mode: McpProtocolMode::Legacy, | |
| client_elicitation_capability: ElicitationCapability::default(), | |
| mcp_server_catalog: ResolvedMcpCatalog::default(), | |
| connector_snapshot: codex_connectors::ConnectorSnapshot::default(), | |
| } | |
| } | |
| pub(crate) fn test_elicitation_config( | |
| server_name: &str, | |
| approval_policy: AskForApproval, | |
| permission_profile: PermissionProfile, | |
| ) -> Arc<McpConfig> { | |
| let mut config = test_mcp_config(PathBuf::new()); | |
| config.approval_policy = Constrained::allow_any(approval_policy); | |
| config.permission_profile = permission_profile.clone(); | |
| config | |
| .server_permission_profiles | |
| .insert(server_name.to_string(), permission_profile); | |
| Arc::new(config) | |
| } | |
| fn ema_catalog_supports_configured_installed_and_selected_plugins_without_widening_policy() { | |
| let server: McpServerConfig = serde_json::from_value(serde_json::json!({ | |
| "url": "https://resource.example/mcp", "auth": "ema_auth", | |
| "oauth": { "client_id": "resource-client", "authorization_server_issuer": "https://as.example" } | |
| })).unwrap(); | |
| let plugin = McpPluginAttribution::agent_plugin("plugin@test".into(), "Plugin".into()); | |
| let mut catalog = ResolvedMcpCatalog::builder(); | |
| catalog.register(McpServerRegistration::from_config( | |
| "configured".into(), | |
| server.clone(), | |
| )); | |
| catalog.register(McpServerRegistration::from_plugin( | |
| "installed".into(), | |
| plugin.clone(), | |
| /*plugin_order*/ 0, | |
| server.clone(), | |
| )); | |
| catalog.register(McpServerRegistration::from_selected_plugin( | |
| "selected".into(), | |
| plugin, | |
| /*selection_order*/ 0, | |
| server, | |
| )); | |
| let mut config = test_mcp_config(PathBuf::new()); | |
| let idp = codex_config::McpServerIdpOAuthConfig { | |
| issuer: "https://idp.example".into(), | |
| client_id: "enterprise-client".into(), | |
| }; | |
| let deny_all = codex_protocol::mcp_policy::EnvironmentMcpPolicy { | |
| servers: Some(Default::default()), | |
| plugins: None, | |
| }; | |
| for (xaa_enabled, denied) in [(true, false), (true, true), (false, false)] { | |
| let mut catalog = catalog.clone(); | |
| if xaa_enabled { | |
| catalog.enable_ema(idp.clone()); | |
| } | |
| config.mcp_server_catalog = catalog.build_with_environment_authority(|_| { | |
| if denied { | |
| crate::McpEnvironmentAuthority::Restricted(&deny_all) | |
| } else { | |
| crate::McpEnvironmentAuthority::Unrestricted | |
| } | |
| }); | |
| let servers = effective_mcp_servers(&config, /*auth*/ None); | |
| for name in ["configured", "installed", "selected"] { | |
| assert_eq!(servers[name].enabled(), xaa_enabled && !denied, "{name}"); | |
| assert_eq!( | |
| servers[name].config().oauth_idp(), | |
| xaa_enabled.then_some(&idp) | |
| ); | |
| assert_eq!(servers[name].config().auth, McpServerAuth::EmaAuth); | |
| } | |
| } | |
| } | |
| fn qualified_mcp_tool_name_prefix_sanitizes_server_names_without_lowercasing() { | |
| assert_eq!( | |
| qualified_mcp_tool_name_prefix("Some-Server"), | |
| "mcp__Some_Server__".to_string() | |
| ); | |
| } | |
| fn mcp_server_permissions_handle_unattached_and_threadless_servers() { | |
| let mut config = test_mcp_config(PathBuf::new()); | |
| config.permission_profile = PermissionProfile::Disabled; | |
| let mut missing_server = codex_apps_mcp_server_config( | |
| "https://example.com", | |
| /*apps_mcp_product_sku*/ None, | |
| /*originator*/ None, | |
| ); | |
| missing_server.environment_id = "missing".to_string(); | |
| let mut selected_server = missing_server.clone(); | |
| selected_server.environment_id = "unattached".to_string(); | |
| let mut catalog = ResolvedMcpCatalog::builder(); | |
| catalog.register(McpServerRegistration::from_config( | |
| "missing".to_string(), | |
| missing_server, | |
| )); | |
| catalog.register(McpServerRegistration::from_selected_plugin( | |
| "selected".to_string(), | |
| McpPluginAttribution::new("selected@test".to_string(), "Selected".to_string()), | |
| /*selection_order*/ 0, | |
| selected_server, | |
| )); | |
| config.mcp_server_catalog = catalog.build(); | |
| let servers = effective_mcp_servers(&config, /*auth*/ None); | |
| assert_eq!(config.permission_profile_for_server("selected"), None); | |
| config.set_server_permission_profiles(&servers, std::iter::empty()); | |
| assert_eq!( | |
| config.permission_profile_for_server("selected"), | |
| Some(&PermissionProfile::Disabled) | |
| ); | |
| assert_eq!(config.permission_profile_for_server("missing"), None); | |
| let config = config.for_threadless_operations(&servers); | |
| assert_eq!( | |
| config.permission_profile_for_server("selected"), | |
| Some(&PermissionProfile::default()) | |
| ); | |
| } | |
| fn mcp_prompt_auto_approval_honors_unrestricted_managed_profiles() { | |
| assert!(mcp_permission_prompt_is_auto_approved( | |
| AskForApproval::Never, | |
| &PermissionProfile::Managed { | |
| file_system: ManagedFileSystemPermissions::Unrestricted, | |
| network: NetworkSandboxPolicy::Enabled, | |
| }, | |
| McpPermissionPromptAutoApproveContext::default(), | |
| )); | |
| assert!(mcp_permission_prompt_is_auto_approved( | |
| AskForApproval::Never, | |
| &PermissionProfile::Managed { | |
| file_system: ManagedFileSystemPermissions::Unrestricted, | |
| network: NetworkSandboxPolicy::Restricted, | |
| }, | |
| McpPermissionPromptAutoApproveContext::default(), | |
| )); | |
| assert!(!mcp_permission_prompt_is_auto_approved( | |
| AskForApproval::Never, | |
| &PermissionProfile::read_only(), | |
| McpPermissionPromptAutoApproveContext::default(), | |
| )); | |
| assert!(!mcp_permission_prompt_is_auto_approved( | |
| AskForApproval::OnRequest, | |
| &PermissionProfile::Managed { | |
| file_system: ManagedFileSystemPermissions::Unrestricted, | |
| network: NetworkSandboxPolicy::Enabled, | |
| }, | |
| McpPermissionPromptAutoApproveContext::default(), | |
| )); | |
| } | |
| fn mcp_prompt_auto_approval_honors_approved_tools_in_all_permission_modes() { | |
| for approval_policy in [ | |
| AskForApproval::UnlessTrusted, | |
| AskForApproval::OnRequest, | |
| AskForApproval::Granular(GranularApprovalConfig { | |
| sandbox_approval: true, | |
| rules: true, | |
| skill_approval: true, | |
| request_permissions: true, | |
| mcp_elicitations: true, | |
| }), | |
| AskForApproval::Never, | |
| ] { | |
| assert!(mcp_permission_prompt_is_auto_approved( | |
| approval_policy, | |
| &PermissionProfile::read_only(), | |
| McpPermissionPromptAutoApproveContext { | |
| tool_approval_mode: Some(AppToolApproval::Approve), | |
| }, | |
| )); | |
| } | |
| assert!(!mcp_permission_prompt_is_auto_approved( | |
| AskForApproval::OnRequest, | |
| &PermissionProfile::read_only(), | |
| McpPermissionPromptAutoApproveContext { | |
| tool_approval_mode: Some(AppToolApproval::Auto), | |
| }, | |
| )); | |
| } | |
| fn mcp_prompt_auto_approval_rejects_auto_mode_in_default_permission_mode() { | |
| assert!(!mcp_permission_prompt_is_auto_approved( | |
| AskForApproval::OnRequest, | |
| &PermissionProfile::read_only(), | |
| McpPermissionPromptAutoApproveContext { | |
| tool_approval_mode: Some(AppToolApproval::Auto), | |
| }, | |
| )); | |
| } | |
| fn tool_plugin_context_collects_app_and_mcp_sources() { | |
| let mut config = test_mcp_config(PathBuf::new()); | |
| let mut catalog = ResolvedMcpCatalog::builder(); | |
| catalog.register(McpServerRegistration::from_plugin( | |
| "alpha".to_string(), | |
| McpPluginAttribution::new("alpha@test".to_string(), "alpha-plugin".to_string()), | |
| /*plugin_order*/ 0, | |
| codex_apps_mcp_server_config( | |
| "https://alpha.example", | |
| /*apps_mcp_product_sku*/ None, | |
| /*originator*/ None, | |
| ), | |
| )); | |
| config.mcp_server_catalog = catalog.build(); | |
| config.connector_snapshot = | |
| codex_connectors::ConnectorSnapshot::from_plugin_capability_summaries(&[ | |
| PluginCapabilitySummary { | |
| config_name: "alpha@test".to_string(), | |
| display_name: "alpha-plugin".to_string(), | |
| plugin_namespace: None, | |
| app_connector_ids: vec![AppConnectorId("connector_example".to_string())], | |
| mcp_server_names: vec!["alpha".to_string()], | |
| ..PluginCapabilitySummary::default() | |
| }, | |
| PluginCapabilitySummary { | |
| config_name: "beta@test".to_string(), | |
| display_name: "beta-plugin".to_string(), | |
| plugin_namespace: None, | |
| app_connector_ids: vec![ | |
| AppConnectorId("connector_example".to_string()), | |
| AppConnectorId("connector_gmail".to_string()), | |
| ], | |
| mcp_server_names: vec!["beta".to_string()], | |
| ..PluginCapabilitySummary::default() | |
| }, | |
| ]); | |
| let provenance = tool_plugin_context(&config); | |
| assert_eq!( | |
| provenance, | |
| ToolPluginContext { | |
| disabled_connector_ids: HashSet::new(), | |
| plugin_display_names_by_connector_id: HashMap::from([ | |
| ( | |
| "connector_example".to_string(), | |
| vec!["alpha-plugin".to_string(), "beta-plugin".to_string()], | |
| ), | |
| ( | |
| "connector_gmail".to_string(), | |
| vec!["beta-plugin".to_string()], | |
| ), | |
| ]), | |
| plugin_display_names_by_mcp_server_name: HashMap::from([( | |
| "alpha".to_string(), | |
| vec!["alpha-plugin".to_string()], | |
| )]), | |
| plugin_ids_by_mcp_server_name: HashMap::from([( | |
| "alpha".to_string(), | |
| "alpha@test".to_string(), | |
| )]), | |
| selected_plugin_mcp_server_names: HashSet::new(), | |
| } | |
| ); | |
| assert_eq!( | |
| provenance.plugin_id_for_mcp_server_name("alpha"), | |
| Some("alpha@test") | |
| ); | |
| assert_eq!(provenance.plugin_id_for_mcp_server_name("beta"), None); | |
| } | |
| fn selected_mcp_attribution_does_not_join_an_unrelated_local_summary() { | |
| let mut config = test_mcp_config(PathBuf::new()); | |
| let mut catalog = ResolvedMcpCatalog::builder(); | |
| catalog.register(McpServerRegistration::from_selected_plugin( | |
| "github".to_string(), | |
| McpPluginAttribution::new( | |
| "shared-plugin-id".to_string(), | |
| "Executor GitHub".to_string(), | |
| ), | |
| /*selection_order*/ 0, | |
| codex_apps_mcp_server_config( | |
| "https://github.example", | |
| /*apps_mcp_product_sku*/ None, | |
| /*originator*/ None, | |
| ), | |
| )); | |
| config.mcp_server_catalog = catalog.build(); | |
| config.connector_snapshot = | |
| codex_connectors::ConnectorSnapshot::from_plugin_capability_summaries(&[ | |
| PluginCapabilitySummary { | |
| config_name: "shared-plugin-id".to_string(), | |
| display_name: "Local GitHub".to_string(), | |
| plugin_namespace: None, | |
| mcp_server_names: vec!["github".to_string()], | |
| ..PluginCapabilitySummary::default() | |
| }, | |
| ]); | |
| let provenance = tool_plugin_context(&config); | |
| assert_eq!( | |
| provenance, | |
| ToolPluginContext { | |
| disabled_connector_ids: HashSet::new(), | |
| plugin_display_names_by_connector_id: HashMap::new(), | |
| plugin_display_names_by_mcp_server_name: HashMap::from([( | |
| "github".to_string(), | |
| vec!["Executor GitHub".to_string()], | |
| )]), | |
| plugin_ids_by_mcp_server_name: HashMap::from([( | |
| "github".to_string(), | |
| "shared-plugin-id".to_string(), | |
| )]), | |
| selected_plugin_mcp_server_names: HashSet::from(["github".to_string()]), | |
| } | |
| ); | |
| assert!(provenance.is_selected_plugin_mcp_server("github")); | |
| } | |
| fn codex_apps_mcp_url_for_base_url_uses_plugin_service_paths() { | |
| assert_eq!( | |
| codex_apps_mcp_url_for_base_url("https://chatgpt.com/backend-api"), | |
| "https://chatgpt.com/backend-api/ps/mcp" | |
| ); | |
| assert_eq!( | |
| codex_apps_mcp_url_for_base_url("https://chat.openai.com"), | |
| "https://chat.openai.com/backend-api/ps/mcp" | |
| ); | |
| assert_eq!( | |
| codex_apps_mcp_url_for_base_url("http://localhost:8080/api/codex"), | |
| "http://localhost:8080/api/codex/ps/mcp" | |
| ); | |
| assert_eq!( | |
| codex_apps_mcp_url_for_base_url("http://localhost:8080"), | |
| "http://localhost:8080/api/codex/ps/mcp" | |
| ); | |
| } | |
| fn codex_apps_server_config_uses_plugin_service_path() { | |
| let config = codex_apps_mcp_server_config( | |
| "https://chatgpt.com", | |
| /*apps_mcp_product_sku*/ None, | |
| /*originator*/ None, | |
| ); | |
| let url = match &config.transport { | |
| McpServerTransportConfig::StreamableHttp { url, .. } => url, | |
| _ => panic!("expected streamable http transport for codex apps"), | |
| }; | |
| assert_eq!(url, "https://chatgpt.com/backend-api/ps/mcp"); | |
| } | |
| fn codex_apps_server_config_forwards_thread_originator_header() { | |
| let config = codex_apps_mcp_server_config( | |
| "https://chatgpt.com", | |
| /*apps_mcp_product_sku*/ None, | |
| Some("thread_originator"), | |
| ); | |
| match &config.transport { | |
| McpServerTransportConfig::StreamableHttp { | |
| http_headers, | |
| env_http_headers, | |
| .. | |
| } => { | |
| assert_eq!( | |
| http_headers, | |
| &Some(HashMap::from([ | |
| ("originator".to_string(), "thread_originator".to_string()), | |
| ("X-OpenAI-Product-Sku".to_string(), "codex".to_string()), | |
| ])) | |
| ); | |
| assert!(env_http_headers.is_none()); | |
| } | |
| other => panic!("expected streamable http transport, got {other:?}"), | |
| } | |
| } | |
| fn codex_apps_server_config_sets_product_sku_header() { | |
| for (configured_product_sku, expected_product_sku) in [(None, "codex"), (Some("tpp"), "tpp")] { | |
| let config = codex_apps_mcp_server_config( | |
| "https://chatgpt.com", | |
| configured_product_sku, | |
| /*originator*/ None, | |
| ); | |
| match &config.transport { | |
| McpServerTransportConfig::StreamableHttp { | |
| http_headers, | |
| env_http_headers, | |
| .. | |
| } => { | |
| assert_eq!( | |
| http_headers, | |
| &Some(HashMap::from([( | |
| "X-OpenAI-Product-Sku".to_string(), | |
| expected_product_sku.to_string(), | |
| )])) | |
| ); | |
| assert!(env_http_headers.is_none()); | |
| } | |
| other => panic!("expected streamable http transport, got {other:?}"), | |
| } | |
| } | |
| } | |
| fn codex_apps_server_config_forwards_originator_and_configured_product_sku_headers() { | |
| let config = codex_apps_mcp_server_config( | |
| "https://chatgpt.com", | |
| Some("tpp"), | |
| Some("thread_originator"), | |
| ); | |
| match &config.transport { | |
| McpServerTransportConfig::StreamableHttp { | |
| http_headers, | |
| env_http_headers, | |
| .. | |
| } => { | |
| assert_eq!( | |
| http_headers, | |
| &Some(HashMap::from([ | |
| ("originator".to_string(), "thread_originator".to_string()), | |
| ("X-OpenAI-Product-Sku".to_string(), "tpp".to_string()), | |
| ])) | |
| ); | |
| assert!(env_http_headers.is_none()); | |
| } | |
| other => panic!("expected streamable http transport, got {other:?}"), | |
| } | |
| } | |
| fn effective_mcp_servers_preserve_chatgpt_auth_for_staging() { | |
| for url in [ | |
| "https://chatgpt-staging.com", | |
| "https://preview.chatgpt-staging.com", | |
| ] { | |
| let mut config = test_mcp_config(PathBuf::new()); | |
| config.chatgpt_base_url = url.to_string(); | |
| let server = codex_apps_mcp_server_config( | |
| url, /*apps_mcp_product_sku*/ None, /*originator*/ None, | |
| ); | |
| let configured = HashMap::from([("staging".to_string(), server)]); | |
| let effective = | |
| effective_mcp_servers_from_configured(configured, &config, /*auth*/ None); | |
| assert_eq!(effective["staging"].config().auth, McpServerAuth::ChatGpt); | |
| } | |
| } | |
| async fn effective_mcp_servers_preserve_runtime_servers() { | |
| let codex_home = tempfile::tempdir().expect("tempdir"); | |
| let mut config = test_mcp_config(codex_home.path().to_path_buf()); | |
| config.apps_enabled = true; | |
| let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); | |
| let mut catalog = ResolvedMcpCatalog::builder(); | |
| catalog.register(McpServerRegistration::from_config( | |
| "sample".to_string(), | |
| McpServerConfig { | |
| auth: Default::default(), | |
| transport: McpServerTransportConfig::StreamableHttp { | |
| url: "https://user.example/mcp".to_string(), | |
| bearer_token_env_var: None, | |
| http_headers: None, | |
| env_http_headers: None, | |
| http_headers_helper: None, | |
| }, | |
| environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(), | |
| enabled: true, | |
| required: false, | |
| supports_parallel_tool_calls: false, | |
| omit_tools_from: None, | |
| disabled_reason: None, | |
| startup_timeout_sec: None, | |
| tool_timeout_sec: None, | |
| default_tools_approval_mode: None, | |
| enabled_tools: None, | |
| disabled_tools: None, | |
| scopes: None, | |
| oauth: None, | |
| oauth_resource: None, | |
| tools: HashMap::new(), | |
| }, | |
| )); | |
| catalog.register(McpServerRegistration::from_config( | |
| "docs".to_string(), | |
| McpServerConfig { | |
| auth: Default::default(), | |
| transport: McpServerTransportConfig::StreamableHttp { | |
| url: "https://docs.example/mcp".to_string(), | |
| bearer_token_env_var: None, | |
| http_headers: None, | |
| env_http_headers: None, | |
| http_headers_helper: None, | |
| }, | |
| environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(), | |
| enabled: true, | |
| required: false, | |
| supports_parallel_tool_calls: false, | |
| omit_tools_from: None, | |
| disabled_reason: None, | |
| startup_timeout_sec: None, | |
| tool_timeout_sec: None, | |
| default_tools_approval_mode: None, | |
| enabled_tools: None, | |
| disabled_tools: None, | |
| scopes: None, | |
| oauth: None, | |
| oauth_resource: None, | |
| tools: HashMap::new(), | |
| }, | |
| )); | |
| catalog.register(McpServerRegistration::from_config( | |
| CODEX_APPS_MCP_SERVER_NAME.to_string(), | |
| codex_apps_mcp_server_config( | |
| &config.chatgpt_base_url, | |
| config.apps_mcp_product_sku.as_deref(), | |
| /*originator*/ None, | |
| ), | |
| )); | |
| config.mcp_server_catalog = catalog.build(); | |
| let effective = effective_mcp_servers(&config, Some(&auth)); | |
| let sample = effective.get("sample").expect("user server should exist"); | |
| let docs = effective | |
| .get("docs") | |
| .expect("configured server should exist"); | |
| let codex_apps = effective | |
| .get(CODEX_APPS_MCP_SERVER_NAME) | |
| .expect("codex apps server should exist"); | |
| let sample = sample.config(); | |
| let docs = docs.config(); | |
| let codex_apps = codex_apps.config(); | |
| match &sample.transport { | |
| McpServerTransportConfig::StreamableHttp { url, .. } => { | |
| assert_eq!(url, "https://user.example/mcp"); | |
| } | |
| other => panic!("expected streamable http transport, got {other:?}"), | |
| } | |
| match &docs.transport { | |
| McpServerTransportConfig::StreamableHttp { url, .. } => { | |
| assert_eq!(url, "https://docs.example/mcp"); | |
| } | |
| other => panic!("expected streamable http transport, got {other:?}"), | |
| } | |
| match &codex_apps.transport { | |
| McpServerTransportConfig::StreamableHttp { url, .. } => { | |
| assert_eq!(url, "https://chatgpt.com/backend-api/ps/mcp"); | |
| } | |
| other => panic!("expected streamable http transport, got {other:?}"), | |
| } | |
| } | |