Codex Handbook
ext/mcp/src/executor_plugin/provider.rs 120 lines
use codex_config::McpServerConfig;use codex_config::McpServerTransportConfig;use codex_core_plugins::ResolvedExecutorPlugin;use codex_exec_server::ExecutorFileSystem;use codex_mcp::PluginMcpServerPlacement;use codex_mcp::parse_plugin_mcp_config;use codex_plugin::PluginResourceLocator;use codex_plugin::ResolvedPlugin;use codex_plugin::ResolvedPluginLocation;use codex_utils_absolute_path::AbsolutePathBuf;use codex_utils_path_uri::PathUri;use std::io;use thiserror::Error;const DEFAULT_MCP_CONFIG_FILE: &str = ".mcp.json";/// Loads MCP declarations from resolved plugins through their owning executor.#[derive(Clone, Copy, Debug, Default)]pub(super) struct ExecutorPluginMcpProvider;/// Failure to load an executor plugin's MCP declarations.#[derive(Debug, Error)]pub(super) enum ExecutorPluginMcpProviderError {    #[error("failed to read MCP config for selected plugin `{plugin_id}` at `{path}`: {source}")]    ReadConfig {        plugin_id: String,        path: AbsolutePathBuf,        #[source]        source: io::Error,    },    #[error("failed to parse MCP config for selected plugin `{plugin_id}` at `{path}`: {source}")]    ParseConfig {        plugin_id: String,        path: AbsolutePathBuf,        #[source]        source: serde_json::Error,    },}impl ExecutorPluginMcpProvider {    /// Returns stdio servers declared by `plugin`, bound to its environment.    pub(super) async fn load(        &self,        plugin: &ResolvedExecutorPlugin,    ) -> Result<Vec<(String, McpServerConfig)>, ExecutorPluginMcpProviderError> {        let ResolvedPluginLocation::Environment { root, .. } = plugin.plugin().location();        load_from_file_system(plugin.plugin(), root, plugin.file_system()).await    }}async fn load_from_file_system(    plugin: &ResolvedPlugin,    plugin_root: &AbsolutePathBuf,    file_system: &dyn ExecutorFileSystem,) -> Result<Vec<(String, McpServerConfig)>, ExecutorPluginMcpProviderError> {    let ResolvedPluginLocation::Environment { environment_id, .. } = plugin.location();    let plugin_id = plugin.selected_root_id();    let (config_path, is_default) = match plugin.manifest().paths.mcp_servers.as_ref() {        Some(PluginResourceLocator::Environment { path, .. }) => (path.clone(), false),        None => (plugin_root.join(DEFAULT_MCP_CONFIG_FILE), true),    };    let config_uri = PathUri::from_abs_path(&config_path);    let contents = match file_system        .read_file_text(&config_uri, /*sandbox*/ None)        .await    {        Ok(contents) => contents,        Err(source) if is_default && source.kind() == io::ErrorKind::NotFound => {            return Ok(Vec::new());        }        Err(source) => {            return Err(ExecutorPluginMcpProviderError::ReadConfig {                plugin_id: plugin_id.to_string(),                path: config_path.clone(),                source,            });        }    };    let parsed = parse_plugin_mcp_config(        plugin_root.as_path(),        &contents,        PluginMcpServerPlacement::Environment { environment_id },    )    .map_err(|source| ExecutorPluginMcpProviderError::ParseConfig {        plugin_id: plugin_id.to_string(),        path: config_path,        source,    })?;    for error in parsed.errors {        tracing::warn!(            plugin = plugin_id,            server = error.name,            error = error.message,            "ignoring invalid executor plugin MCP server"        );    }    Ok(parsed        .servers        .into_iter()        .filter_map(|(name, config)| match &config.transport {            McpServerTransportConfig::Stdio { .. } => Some((name, config)),            McpServerTransportConfig::StreamableHttp { .. } => {                tracing::warn!(                    plugin = plugin_id,                    server = name,                    "ignoring HTTP MCP server from executor plugin"                );                None            }        })        .collect())}#[cfg(test)]#[path = "provider_tests.rs"]mod tests;