diff --git a/codex-rs/codex-mcp/src/connection_manager.rs b/codex-rs/codex-mcp/src/connection_manager.rs index 305716111..53e9efc6d 100644 --- a/codex-rs/codex-mcp/src/connection_manager.rs +++ b/codex-rs/codex-mcp/src/connection_manager.rs @@ -391,7 +391,7 @@ impl McpConnectionManager { } pub fn is_host_owned_codex_apps_server(&self, server_name: &str) -> bool { - server_name == CODEX_APPS_MCP_SERVER_NAME + server_name == CODEX_APPS_MCP_SERVER_NAME && self.server_metadata.contains_key(server_name) } pub fn set_approval_policy(&self, approval_policy: &Constrained) { diff --git a/codex-rs/codex-mcp/src/connection_manager_tests.rs b/codex-rs/codex-mcp/src/connection_manager_tests.rs index 455697392..887a9b3d4 100644 --- a/codex-rs/codex-mcp/src/connection_manager_tests.rs +++ b/codex-rs/codex-mcp/src/connection_manager_tests.rs @@ -1177,6 +1177,41 @@ fn server_metadata_preserves_tool_approval_policy() { ); } +#[test] +fn host_owned_codex_apps_requires_server_metadata() { + let approval_policy = Constrained::allow_any(AskForApproval::OnFailure); + let permission_profile = Constrained::allow_any(PermissionProfile::default()); + let manager = McpConnectionManager::new_uninitialized( + &approval_policy, + &permission_profile, + /*prefix_mcp_tool_names*/ true, + ); + + assert!(!manager.is_host_owned_codex_apps_server(CODEX_APPS_MCP_SERVER_NAME)); +} + +#[test] +fn host_owned_codex_apps_matches_reserved_name_with_server_metadata() { + let approval_policy = Constrained::allow_any(AskForApproval::OnFailure); + let permission_profile = Constrained::allow_any(PermissionProfile::default()); + let mut manager = McpConnectionManager::new_uninitialized( + &approval_policy, + &permission_profile, + /*prefix_mcp_tool_names*/ true, + ); + let server = EffectiveMcpServer::configured(crate::codex_apps_mcp_server_config( + "https://chatgpt.com", + /*apps_mcp_product_sku*/ None, + )); + manager.server_metadata.insert( + CODEX_APPS_MCP_SERVER_NAME.to_string(), + McpServerMetadata::from(&server), + ); + + assert!(manager.is_host_owned_codex_apps_server(CODEX_APPS_MCP_SERVER_NAME)); + assert!(!manager.is_host_owned_codex_apps_server("docs")); +} + #[tokio::test] async fn no_local_runtime_fails_local_stdio_but_keeps_local_http_server() { let approval_policy = Constrained::allow_any(AskForApproval::OnFailure); diff --git a/codex-rs/core/src/mcp_tool_call_tests.rs b/codex-rs/core/src/mcp_tool_call_tests.rs index 2a951bfe5..f8b94de5a 100644 --- a/codex-rs/core/src/mcp_tool_call_tests.rs +++ b/codex-rs/core/src/mcp_tool_call_tests.rs @@ -1335,15 +1335,25 @@ fn codex_apps_auth_failure_metadata() -> McpToolApprovalMetadata { async fn install_host_owned_codex_apps_manager(session: &Session, turn_context: &TurnContext) { let auth = session.services.auth_manager.auth().await; + let startup_cancellation_token = CancellationToken::new(); + startup_cancellation_token.cancel(); + let (tx_event, _rx_event) = async_channel::unbounded(); + let mcp_servers = HashMap::from([( + CODEX_APPS_MCP_SERVER_NAME.to_string(), + codex_mcp::EffectiveMcpServer::configured(codex_mcp::codex_apps_mcp_server_config( + "https://chatgpt.com", + /*apps_mcp_product_sku*/ None, + )), + )]); let manager = codex_mcp::McpConnectionManager::new( - &HashMap::new(), + &mcp_servers, turn_context.config.mcp_oauth_credentials_store_mode, turn_context.config.auth_keyring_backend_kind(), HashMap::new(), &turn_context.approval_policy, turn_context.sub_id.clone(), - session.get_tx_event(), - CancellationToken::new(), + tx_event, + startup_cancellation_token, turn_context.permission_profile(), codex_mcp::McpRuntimeContext::new( session.services.turn_environments.environment_manager(),