mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Move MCP tool naming mode into manager (#21576)
## Why The `non_prefixed_mcp_tool_names` feature should be applied where MCP tools become model-visible, not by remapping names later in core. Keeping the decision in `McpConnectionManager` construction makes `ToolInfo` the single shaped view that spec building, deferred tool search, routing, and unavailable-tool placeholders can consume directly. This also preserves the existing external behavior while the feature is off, and keeps the feature-on behavior for code mode and hooks explicit at the manager boundary. ## What Changed - Add `McpToolNameMode` to `codex-mcp` and flow it through `McpConfig` into `McpConnectionManager::new`. - Normalize MCP `ToolInfo` names in the manager using either legacy-prefixed namespaces or non-prefixed namespaces; the legacy path adds `mcp__` without restoring the old trailing namespace suffix. - Remove the core-side MCP name remapping path so specs, tool search, session resolution, and unavailable-tool placeholder construction use the manager-provided `ToolName` values directly. - Keep code mode flattening on the `__` namespace separator. - Preserve hook compatibility by giving non-prefixed MCP hook names legacy `mcp__...` matcher aliases. - Add/adjust integration and unit coverage for non-prefixed code-mode behavior, hook matching with the feature on and off, and manager-level legacy prefixing. ## Testing - `cargo test -p codex-mcp --lib` - `cargo test -p codex-core --lib tools::spec::tests -- --nocapture` - `cargo test -p codex-core --lib mcp_tools -- --nocapture` - `cargo test -p codex-core --lib mcp_tool_exposure -- --nocapture` - `cargo test -p codex-core --test all mcp_tool -- --nocapture` - `cargo test -p codex-core --test all search_tool -- --nocapture` - `cargo test -p codex-core --test all hooks_mcp -- --nocapture` - `cargo test -p codex-core --test all code_mode_uses_non_prefixed_mcp_tool_names_when_feature_enabled -- --nocapture` - `cargo test -p codex-tools` - `cargo test -p codex-features`
This commit is contained in:
1 parent
b637fd26aa
commit
ff7513cd83
30 files changed
+611
-146
No files matched your search
@@ -25,6 +25,8 @@ use crate::mcp::sanitize_responses_api_tool_name;
|
||||
pub(crate) const MCP_TOOLS_CACHE_WRITE_DURATION_METRIC: &str =
|
||||
"codex.mcp.tools.cache_write.duration_ms";
|
||||
|
||||
const LEGACY_MCP_TOOL_NAME_PREFIX: &str = "mcp__";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolInfo {
|
||||
/// Raw MCP server name used for routing the tool call.
|
||||
@@ -141,7 +143,13 @@ pub(crate) fn filter_tools(tools: Vec<ToolInfo>, filter: &ToolFilter) -> Vec<Too
|
||||
/// Raw MCP server/tool names are kept on each [`ToolInfo`] for protocol calls, while
|
||||
/// `callable_namespace` / `callable_name` are sanitized and, when necessary, hashed so
|
||||
/// every model-visible name is unique and <= 64 bytes.
|
||||
pub(crate) fn normalize_tools_for_model<I>(tools: I) -> Vec<ToolInfo>
|
||||
///
|
||||
/// When `prefix_mcp_tool_names` is true, the historical `mcp__` namespace
|
||||
/// prefix is added without restoring the old trailing `__` namespace suffix.
|
||||
pub(crate) fn normalize_tools_for_model_with_prefix<I>(
|
||||
tools: I,
|
||||
prefix_mcp_tool_names: bool,
|
||||
) -> Vec<ToolInfo>
|
||||
where
|
||||
I: IntoIterator<Item = ToolInfo>,
|
||||
{
|
||||
@@ -163,8 +171,19 @@ where
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut callable_namespace = callable_namespace_with_prefix(
|
||||
&sanitize_responses_api_tool_name(&tool.callable_namespace),
|
||||
prefix_mcp_tool_names,
|
||||
);
|
||||
if !prefix_mcp_tool_names
|
||||
&& RESERVED_UNPREFIXED_MCP_NAMESPACES.contains(&callable_namespace.as_str())
|
||||
{
|
||||
callable_namespace =
|
||||
append_namespace_hash_suffix(&callable_namespace, &raw_namespace_identity);
|
||||
}
|
||||
|
||||
candidates.push(CallableToolCandidate {
|
||||
callable_namespace: sanitize_responses_api_tool_name(&tool.callable_namespace),
|
||||
callable_namespace,
|
||||
callable_name: sanitize_responses_api_tool_name(&tool.callable_name),
|
||||
raw_namespace_identity,
|
||||
raw_tool_identity,
|
||||
@@ -226,6 +245,7 @@ where
|
||||
&candidate.callable_name,
|
||||
&candidate.raw_tool_identity,
|
||||
&mut used_names,
|
||||
MCP_TOOL_NAME_DELIMITER.len(),
|
||||
);
|
||||
candidate.tool.callable_namespace = callable_namespace;
|
||||
candidate.tool.callable_name = callable_name;
|
||||
@@ -247,6 +267,15 @@ const MCP_TOOL_NAME_DELIMITER: &str = "__";
|
||||
const MAX_TOOL_NAME_LENGTH: usize = 64;
|
||||
const CALLABLE_NAME_HASH_LEN: usize = 12;
|
||||
const META_OPENAI_FILE_PARAMS: &str = "openai/fileParams";
|
||||
const RESERVED_UNPREFIXED_MCP_NAMESPACES: &[&str] = &["tools", "web"];
|
||||
|
||||
fn callable_namespace_with_prefix(namespace: &str, prefix_mcp_tool_names: bool) -> String {
|
||||
if !prefix_mcp_tool_names || namespace.starts_with(LEGACY_MCP_TOOL_NAME_PREFIX) {
|
||||
namespace.to_string()
|
||||
} else {
|
||||
format!("{LEGACY_MCP_TOOL_NAME_PREFIX}{namespace}")
|
||||
}
|
||||
}
|
||||
|
||||
fn mask_input_schema_for_file_path_params(input_schema: &mut JsonValue, file_params: &[String]) {
|
||||
let Some(properties) = input_schema
|
||||
@@ -331,9 +360,10 @@ fn fit_callable_parts_with_hash(
|
||||
namespace: &str,
|
||||
tool_name: &str,
|
||||
raw_identity: &str,
|
||||
reserved_len: usize,
|
||||
) -> (String, String) {
|
||||
let suffix = callable_name_hash_suffix(raw_identity);
|
||||
let max_tool_len = MAX_TOOL_NAME_LENGTH.saturating_sub(namespace.len());
|
||||
let max_tool_len = MAX_TOOL_NAME_LENGTH.saturating_sub(namespace.len() + reserved_len);
|
||||
if max_tool_len >= suffix.len() {
|
||||
let prefix_len = max_tool_len - suffix.len();
|
||||
return (
|
||||
@@ -342,7 +372,7 @@ fn fit_callable_parts_with_hash(
|
||||
);
|
||||
}
|
||||
|
||||
let max_namespace_len = MAX_TOOL_NAME_LENGTH - suffix.len();
|
||||
let max_namespace_len = MAX_TOOL_NAME_LENGTH.saturating_sub(suffix.len() + reserved_len);
|
||||
(truncate_name(namespace, max_namespace_len), suffix)
|
||||
}
|
||||
|
||||
@@ -351,9 +381,10 @@ fn unique_callable_parts(
|
||||
tool_name: &str,
|
||||
raw_identity: &str,
|
||||
used_names: &mut HashSet<String>,
|
||||
reserved_len: usize,
|
||||
) -> (String, String) {
|
||||
let model_name = format!("{namespace}{tool_name}");
|
||||
if model_name.len() <= MAX_TOOL_NAME_LENGTH && used_names.insert(model_name) {
|
||||
if model_name.len() + reserved_len <= MAX_TOOL_NAME_LENGTH && used_names.insert(model_name) {
|
||||
return (namespace.to_string(), tool_name.to_string());
|
||||
}
|
||||
|
||||
@@ -365,7 +396,7 @@ fn unique_callable_parts(
|
||||
format!("{raw_identity}\0{attempt}")
|
||||
};
|
||||
let (namespace, tool_name) =
|
||||
fit_callable_parts_with_hash(namespace, tool_name, &hash_input);
|
||||
fit_callable_parts_with_hash(namespace, tool_name, &hash_input, reserved_len);
|
||||
let model_name = format!("{namespace}{tool_name}");
|
||||
if used_names.insert(model_name) {
|
||||
return (namespace, tool_name);
|
||||
|
||||
Reference in new issue
Block a user