Files
codex/codex-rs/core/src/plugins/injection.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

69 lines
2.4 KiB
Rust

use std::collections::BTreeSet;
use codex_connectors::metadata::connector_display_label;
use codex_features::Features;
use codex_protocol::models::ResponseItem;
use crate::connectors;
use crate::context::ContextualUserFragment;
use crate::context::PluginInstructions;
use crate::plugins::PluginCapabilitySummary;
use crate::plugins::render_explicit_plugin_instructions;
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
use codex_mcp::ToolInfo;
pub(crate) fn build_plugin_injections(
mentioned_plugins: &[PluginCapabilitySummary],
mcp_tools: &[ToolInfo],
available_connectors: &[connectors::AppInfo],
features: &Features,
model_messages: codex_prompts::ResolvedModelMessages<'_>,
) -> Vec<ResponseItem> {
if mentioned_plugins.is_empty() {
return Vec::new();
}
let messages = model_messages.selected_plugin();
// Turn each explicit plugin mention with available capabilities into a developer hint.
mentioned_plugins
.iter()
.filter_map(|plugin| {
let available_mcp_servers = mcp_tools
.iter()
.filter(|tool| {
tool.server_name() != CODEX_APPS_MCP_SERVER_NAME
&& tool
.plugin_display_names
.iter()
.any(|plugin_name| plugin_name == &plugin.display_name)
})
.map(|tool| tool.server_name().to_owned())
.collect::<BTreeSet<String>>()
.into_iter()
.collect::<Vec<_>>();
let available_apps = available_connectors
.iter()
.filter(|connector| {
connector.is_enabled
&& connector
.plugin_display_names
.iter()
.any(|plugin_name| plugin_name == &plugin.display_name)
})
.map(connector_display_label)
.collect::<BTreeSet<String>>()
.into_iter()
.collect::<Vec<_>>();
render_explicit_plugin_instructions(
plugin,
&available_mcp_servers,
&available_apps,
features,
&messages,
)
.map(PluginInstructions::new)
.map(ContextualUserFragment::into)
})
.collect()
}