use anyhow::Result; use anyhow::anyhow; use codex_protocol::protocol::McpStartupFailure; use tracing::Instrument; use tracing::info_span; use super::McpConnectionSet; use crate::rmcp_client::StartupOutcomeError; impl McpConnectionSet { /// Waits for every required server and reports their startup failures together. /// /// The manager must already be reachable through [`crate::McpRuntime`] so /// startup-time elicitation can resolve while validation waits. pub(crate) async fn validate_required_servers(&self) -> Result<()> { let failures = async { let mut failures = Vec::new(); for server_name in &self.required_servers { let Some(view) = self.servers.get(server_name) else { failures.push(McpStartupFailure { server: server_name.clone(), error: format!("required MCP server `{server_name}` was not initialized"), }); continue; }; if view.connection.startup_is_dormant() && view.connection.client.has_cached_tools() { continue; } match view.connection.client().await { Ok(_) => {} Err(error) => failures.push(McpStartupFailure { server: server_name.clone(), error: startup_outcome_error_message(error), }), } } failures } .instrument(info_span!( "session_init.required_mcp_wait", otel.name = "session_init.required_mcp_wait", session_init.required_mcp_server_count = self.required_servers.len(), )) .await; if failures.is_empty() { return Ok(()); } let details = failures .iter() .map(|failure| format!("{}: {}", failure.server, failure.error)) .collect::>() .join("; "); Err(anyhow!( "required MCP servers failed to initialize: {details}" )) } } fn startup_outcome_error_message(error: StartupOutcomeError) -> String { match error { StartupOutcomeError::Cancelled => "MCP startup cancelled".to_string(), StartupOutcomeError::Failed { error, .. } => error, } }