Codex Handbook
core/src/mcp_tool_exposure.rs 109 lines
use std::collections::HashSet;use codex_connectors::AppToolPolicyEvaluator;use codex_connectors::AppToolPolicyInput;use codex_features::Feature;use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;use codex_mcp::ToolInfo as McpToolInfo;use codex_mcp::tool_is_model_visible;use tracing::instrument;use crate::config::Config;use crate::connectors;pub(crate) const DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD: usize = 100;pub(crate) struct McpToolExposure {    pub(crate) direct_tools: Vec<McpToolInfo>,    pub(crate) deferred_tools: Option<Vec<McpToolInfo>>,}#[instrument(level = "trace", skip_all)]pub(crate) fn build_mcp_tool_exposure(    all_mcp_tools: &[McpToolInfo],    connectors: Option<&[connectors::AppInfo]>,    config: &Config,    search_tool_enabled: bool,) -> McpToolExposure {    let mut deferred_tools = filter_non_codex_apps_mcp_tools_only(all_mcp_tools);    if let Some(connectors) = connectors {        deferred_tools.extend(filter_codex_apps_mcp_tools(            all_mcp_tools,            connectors,            config,        ));    }    let should_defer = search_tool_enabled        && (config            .features            .enabled(Feature::ToolSearchAlwaysDeferMcpTools)            || deferred_tools.len() >= DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD);    if !should_defer {        return McpToolExposure {            direct_tools: deferred_tools,            deferred_tools: None,        };    }    McpToolExposure {        direct_tools: Vec::new(),        deferred_tools: (!deferred_tools.is_empty()).then_some(deferred_tools),    }}fn filter_non_codex_apps_mcp_tools_only(mcp_tools: &[McpToolInfo]) -> Vec<McpToolInfo> {    mcp_tools        .iter()        .filter(|tool| {            tool.server_name != CODEX_APPS_MCP_SERVER_NAME && tool_is_model_visible(tool)        })        .cloned()        .collect()}fn filter_codex_apps_mcp_tools(    mcp_tools: &[McpToolInfo],    connectors: &[connectors::AppInfo],    config: &Config,) -> Vec<McpToolInfo> {    let allowed: HashSet<&str> = connectors        .iter()        .map(|connector| connector.id.as_str())        .collect();    let app_tool_policy = AppToolPolicyEvaluator::new(&config.config_layer_stack);    mcp_tools        .iter()        .filter(|tool| {            if tool.server_name != CODEX_APPS_MCP_SERVER_NAME {                return false;            }            if !tool_is_model_visible(tool) {                return false;            }            let Some(connector_id) = tool.connector_id.as_deref() else {                return false;            };            let annotations = tool.tool.annotations.as_ref();            allowed.contains(connector_id)                && app_tool_policy                    .policy(AppToolPolicyInput {                        connector_id: Some(connector_id),                        tool_name: &tool.tool.name,                        tool_title: tool.tool.title.as_deref(),                        destructive_hint: annotations                            .and_then(|annotations| annotations.destructive_hint),                        open_world_hint: annotations                            .and_then(|annotations| annotations.open_world_hint),                    })                    .enabled        })        .cloned()        .collect()}#[cfg(test)]#[path = "mcp_tool_exposure_test.rs"]mod tests;