[codex] Make MCP connection startup fallible (#27261)

## Why

Required MCP server startup was enforced in `Session::new` after
`McpConnectionManager` had already created the clients. That split let
other manager construction paths bypass the same requirement and exposed
manager internals solely so the session could validate them. Keeping
required-server readiness in the constructor gives every caller one
consistent startup contract.

## What changed

- make `McpConnectionManager::new` return `anyhow::Result<Self>` and
fail when an enabled, required server cannot initialize
- pass the startup cancellation token into the constructor so
required-server waits remain cancellable
- propagate constructor failures through resource reads, connector
discovery, and MCP status collection
- preserve the active manager and cancellation token when a refreshed
replacement fails
- keep required-startup failure collection private and cover the
constructor error contract directly

## Validation

- updated the focused connection-manager test to assert the complete
required-server startup error
- local tests not run; relying on CI
This commit is contained in:
Ahmed Ibrahim authored and GitHub committed 2026-06-10 00:17:58 -07:00
1 parent 00a25e1e0c
commit a7b6baecc6
8 files changed
+129 -107

No files matched your search

@@ -1167,13 +1167,15 @@ async fn no_local_runtime_fails_local_stdio_but_keeps_local_http_server() {
),
]);
let (manager, cancel_token) = McpConnectionManager::new(
let cancel_token = CancellationToken::new();
let manager = McpConnectionManager::new(
&mcp_servers,
OAuthCredentialsStoreMode::default(),
HashMap::new(),
&approval_policy,
String::new(),
tx_event,
cancel_token.clone(),
PermissionProfile::default(),
McpRuntimeContext::new(
Arc::new(EnvironmentManager::without_environments()),
@@ -1201,13 +1203,18 @@ async fn no_local_runtime_fails_local_stdio_but_keeps_local_http_server() {
.wait_for_server_ready("stdio", Duration::from_millis(10))
.await
);
let failures = manager
.required_startup_failures(&["stdio".to_string()])
.await;
assert_eq!(failures.len(), 1);
assert_eq!(failures[0].server, "stdio");
let error = match manager
.clients
.get("stdio")
.expect("stdio client")
.client()
.await
{
Ok(_) => panic!("local stdio MCP startup should fail"),
Err(error) => error,
};
assert_eq!(
failures[0].error,
startup_outcome_error_message(error),
"local stdio MCP server `stdio` requires a local environment"
);
cancel_token.cancel();