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;