mirror of
https://github.com/openai/codex.git
synced 2026-11-13 21:30:47 +00:00
## 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
71 lines
2.6 KiB
Rust
71 lines
2.6 KiB
Rust
//! Host-provided metadata for a specific tool on a specific MCP registration.
|
||
|
||
use std::sync::Arc;
|
||
|
||
use crate::McpServerSource;
|
||
use crate::PreparedMcpCall;
|
||
use anyhow::Context;
|
||
use codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID;
|
||
use codex_config::McpServerTransportConfig;
|
||
use codex_protocol::ThreadId;
|
||
use futures::future::BoxFuture;
|
||
use serde_json::Value;
|
||
|
||
/// Supplies one MCP request metadata value for a live tool invocation. An error
|
||
/// fails the tool call before dispatch and is reported to the tool caller.
|
||
pub trait McpToolRequestMetaProvider: Send + Sync {
|
||
fn value<'a>(
|
||
&'a self,
|
||
thread_id: ThreadId,
|
||
turn_id: &'a str,
|
||
call_id: &'a str,
|
||
) -> BoxFuture<'a, anyhow::Result<Value>>;
|
||
}
|
||
|
||
/// Install in thread extension data to add a private `_meta` field to one remote
|
||
/// tool on the selected local MCP registration. Bind the source and full transport
|
||
/// supplied by the trusted host, including routing headers for a proxied endpoint.
|
||
pub struct McpToolRequestMetaHook {
|
||
pub server_name: String,
|
||
pub source: McpServerSource,
|
||
pub transport: McpServerTransportConfig,
|
||
pub tool_name: String,
|
||
/// A metadata key owned by the provider. Existing metadata cannot be overwritten.
|
||
pub key: String,
|
||
pub provider: Arc<dyn McpToolRequestMetaProvider>,
|
||
}
|
||
|
||
impl McpToolRequestMetaHook {
|
||
/// Adds the provider’s value only when the immutable call matches the selected registration.
|
||
pub async fn apply(
|
||
&self,
|
||
call: &PreparedMcpCall,
|
||
thread_id: ThreadId,
|
||
turn_id: &str,
|
||
call_id: &str,
|
||
meta: &mut Option<Value>,
|
||
) -> anyhow::Result<()> {
|
||
if call.server_name() == self.server_name
|
||
&& call.tool_info().tool.name.as_ref() == self.tool_name
|
||
&& call.server_environment_id() == DEFAULT_MCP_SERVER_ENVIRONMENT_ID
|
||
&& let Some(server) = call.config().mcp_server_catalog.server(call.server_key())
|
||
&& server.config().is_local_environment()
|
||
&& server.source() == &self.source
|
||
&& server.config().transport == self.transport
|
||
{
|
||
let meta = meta
|
||
.as_mut()
|
||
.and_then(Value::as_object_mut)
|
||
.context("missing MCP request metadata object")?;
|
||
anyhow::ensure!(
|
||
!meta.contains_key(&self.key),
|
||
"MCP request metadata already contains {}",
|
||
self.key
|
||
);
|
||
let value = self.provider.value(thread_id, turn_id, call_id).await?;
|
||
meta.insert(self.key.clone(), value);
|
||
}
|
||
Ok(())
|
||
}
|
||
}
|