Files
codex/codex-rs/codex-mcp/src/runtime/status.rs
T
sayan-oai 561f0f5cf0 Keep same-named MCP servers separate across environments (#60905)
## Why

MCP servers discovered on different executors can share a name. Resolving them by name alone exposes only one server and prevents tools and resources from being addressed independently.

## What changed

- Add the disabled-by-default `multi_environment_mcp` feature to expose environment-scoped servers, including multiple installations of the same plugin.
- Carry exact `McpServerKey` identities through catalogs, connections, tool calls, resources, and approval tracking. Give environment-scoped tools stable namespaces derived from their environment.
- Include `environment_id` in scoped resource listings and accept it in resource requests. Require it for environment-owned servers when multiple environments are selected.
- Preserve server ownership through hooks and use the owning environment's permissions for scoped elicitation review.

## Testing

Add coverage for same-named servers across environments, independent calls and approvals, resource selection, plugin discovery, and rejection of hook changes to the tool environment.

GitOrigin-RevId: 5593372b06a15c16aa3220faa3b19f5d3b20e67e
2026-11-11 23:36:19 +00:00

78 lines
2.9 KiB
Rust

//! Read selected-server metadata from one published thread runtime.
use std::sync::Arc;
use super::McpRuntime;
use super::McpRuntimeContext;
use crate::McpConfig;
use crate::McpServerStatusSnapshot;
use crate::McpSnapshotDetail;
use crate::ResolvedMcpServer;
use crate::mcp::collect_mcp_server_status_snapshot_from_manager;
use crate::mcp::compute_auth_statuses;
use crate::mcp::effective_mcp_servers;
impl McpRuntime {
/// Reuses the selected connection and catalog without starting unrelated servers.
/// Configuration and metadata belong to the same captured runtime publication.
/// An expected registration excludes mismatched connections before discovery.
pub async fn server_status_snapshot(
&self,
server: &str,
detail: McpSnapshotDetail,
expected_registration: Option<&ResolvedMcpServer>,
runtime_context: &McpRuntimeContext,
) -> anyhow::Result<(Arc<McpConfig>, McpServerStatusSnapshot)> {
let current = self.current.load_full();
let runtime_context = runtime_context.clone().with_selected_environments(
Arc::clone(&current.environment_selections),
current.ready_environments.clone(),
);
let config = Arc::clone(
current
.config
.as_ref()
.ok_or_else(|| anyhow::anyhow!("MCP runtime is not configured"))?,
);
let mut servers = effective_mcp_servers(&config, current.auth.as_ref());
servers.retain(|name, _| {
Some(name) == config.mcp_server_catalog.resolve_server_name(server)
&& expected_registration
.is_none_or(|expected| config.mcp_server_catalog.server(name) == Some(expected))
});
let servers = servers
.into_values()
.map(|value| (server.to_owned(), value))
.collect::<std::collections::HashMap<_, _>>();
if config.xaa_enabled
&& servers
.values()
.any(|server| matches!(server.config().auth, codex_config::McpServerAuth::EmaAuth))
{
// Finish selected startup and any rejected-grant cleanup before reading auth.
current
.connections
.list_available_server_infos(|name| name == server)
.await;
}
let auth_statuses = compute_auth_statuses(
servers.iter(),
config.mcp_oauth_credentials_store_mode,
config.auth_keyring_backend_kind,
current.auth.as_ref(),
current.auth_manager.as_ref(),
&config.ema_state,
&runtime_context,
)
.await;
let snapshot = collect_mcp_server_status_snapshot_from_manager(
&current.connections,
auth_statuses,
servers.into_keys().collect(),
detail,
)
.await;
Ok((config, snapshot))
}
}