Files
codex/codex-rs/codex-mcp/src/rmcp_client/status.rs
T
Eric Traut 343074d420 Report runtime MCP connection status (#40068)
## Why

MCP inventory can be cached or collected separately from a thread's live
connections, so tool availability alone does not describe the current runtime
state.

## What changed

- Add a nullable `runtimeStatus` to `mcpServerStatus/list` for thread-scoped
  requests, covering not-started, starting, connected, authentication-required,
  failed, cancelled, and disabled connections.
- Observe published connection state without starting or reconnecting servers,
  and return an unknown status when no thread is supplied or the active
  configuration no longer matches the published registration.
- Show connection state and tool counts in the compact `/mcp` view while
  retaining the detailed inventory in `/mcp verbose` and compatibility with
  servers that omit `runtimeStatus`.

## Testing

- Cover runtime status transitions, deferred and disabled servers, closed
  transports, configuration changes, protocol compatibility, and TUI rendering.

GitOrigin-RevId: e3bb6efe652f0fa8b3c97d5c53e4729b3a87cd91
2026-08-22 05:54:43 +00:00

47 lines
1.6 KiB
Rust

//! Observe the latest startup attempt without polling it or initiating a retry.
use codex_protocol::mcp::McpServerConnectionStatus as Status;
use super::AsyncManagedClient;
use super::StartupOutcomeError;
impl AsyncManagedClient {
pub(crate) async fn connection_status(&self) -> Status {
if self.cancel_token.is_cancelled() {
return Status::Cancelled;
}
let reconnect_outcome = if let Some(reconnect) = &self.startup_reconnect {
let state = reconnect
.state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if state.reconnect_in_flight {
return Status::Starting;
}
state
.current_client
.clone()
.map(Ok)
.or_else(|| state.last_error.clone().map(Err))
} else {
None
};
let outcome = reconnect_outcome.or_else(|| self.client.peek().cloned());
match outcome {
Some(Ok(client)) => {
if client.client.is_closed().await {
Status::Failed
} else {
Status::Connected
}
}
Some(Err(error)) if error.is_authentication_required() => {
Status::AuthenticationRequired
}
Some(Err(StartupOutcomeError::Failed { .. })) => Status::Failed,
Some(Err(StartupOutcomeError::Cancelled)) => Status::Cancelled,
None => Status::Starting,
}
}
}