Normalize MCP tool names to code-mode safe form (#14605)

Code mode doesn't allow `-` in names and it's better if function names
and code-mode names are the same.
This commit is contained in:
pakrym-oai
2026-03-13 14:50:16 -07:00
committed by GitHub
parent f8f82bfc2b
commit cb7d8f45a1
7 changed files with 32 additions and 30 deletions
+4 -2
View File
@@ -110,7 +110,7 @@ const MCP_TOOLS_CACHE_WRITE_DURATION_METRIC: &str = "codex.mcp.tools.cache_write
fn sanitize_responses_api_tool_name(name: &str) -> String {
let mut sanitized = String::with_capacity(name.len());
for c in name.chars() {
if c.is_ascii_alphanumeric() || c == '_' || c == '-' {
if c.is_ascii_alphanumeric() || c == '_' {
sanitized.push(c);
} else {
sanitized.push('_');
@@ -1230,11 +1230,12 @@ fn normalize_codex_apps_tool_name(
return tool_name.to_string();
}
let tool_name = sanitize_name(tool_name);
let tool_name = sanitize_name(tool_name).replace('-', "_");
if let Some(connector_name) = connector_name
.map(str::trim)
.map(sanitize_name)
.map(|name| name.replace('-', "_"))
.filter(|name| !name.is_empty())
&& let Some(stripped) = tool_name.strip_prefix(&connector_name)
&& !stripped.is_empty()
@@ -1245,6 +1246,7 @@ fn normalize_codex_apps_tool_name(
if let Some(connector_id) = connector_id
.map(str::trim)
.map(sanitize_name)
.map(|name| name.replace('-', "_"))
.filter(|name| !name.is_empty())
&& let Some(stripped) = tool_name.strip_prefix(&connector_id)
&& !stripped.is_empty()
@@ -161,17 +161,17 @@ fn test_qualify_tools_long_names_same_server() {
#[test]
fn test_qualify_tools_sanitizes_invalid_characters() {
let tools = vec![create_test_tool("server.one", "tool.two")];
let tools = vec![create_test_tool("server.one", "tool.two-three")];
let qualified_tools = qualify_tools(tools);
assert_eq!(qualified_tools.len(), 1);
let (qualified_name, tool) = qualified_tools.into_iter().next().expect("one tool");
assert_eq!(qualified_name, "mcp__server_one__tool_two");
assert_eq!(qualified_name, "mcp__server_one__tool_two_three");
// The key is sanitized for OpenAI, but we keep original parts for the actual MCP call.
assert_eq!(tool.server_name, "server.one");
assert_eq!(tool.tool_name, "tool.two");
assert_eq!(tool.tool_name, "tool.two-three");
assert!(
qualified_name
@@ -10,10 +10,10 @@ use std::sync::Arc;
fn serialize_tool_search_output_tools_groups_results_by_namespace() {
let entries = [
(
"mcp__codex_apps__calendar-create-event".to_string(),
"mcp__codex_apps__calendar_create_event".to_string(),
ToolInfo {
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
tool_name: "-create-event".to_string(),
tool_name: "_create_event".to_string(),
tool_namespace: "mcp__codex_apps__calendar".to_string(),
tool: Tool {
name: "calendar-create-event".to_string().into(),
@@ -36,10 +36,10 @@ fn serialize_tool_search_output_tools_groups_results_by_namespace() {
},
),
(
"mcp__codex_apps__gmail-read-email".to_string(),
"mcp__codex_apps__gmail_read_email".to_string(),
ToolInfo {
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
tool_name: "-read-email".to_string(),
tool_name: "_read_email".to_string(),
tool_namespace: "mcp__codex_apps__gmail".to_string(),
tool: Tool {
name: "gmail-read-email".to_string().into(),
@@ -62,10 +62,10 @@ fn serialize_tool_search_output_tools_groups_results_by_namespace() {
},
),
(
"mcp__codex_apps__calendar-list-events".to_string(),
"mcp__codex_apps__calendar_list_events".to_string(),
ToolInfo {
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
tool_name: "-list-events".to_string(),
tool_name: "_list_events".to_string(),
tool_namespace: "mcp__codex_apps__calendar".to_string(),
tool: Tool {
name: "calendar-list-events".to_string().into(),
@@ -100,7 +100,7 @@ fn serialize_tool_search_output_tools_groups_results_by_namespace() {
description: "Plan events".to_string(),
tools: vec![
ResponsesApiNamespaceTool::Function(ResponsesApiTool {
name: "-create-event".to_string(),
name: "_create_event".to_string(),
description: "Create a calendar event.".to_string(),
strict: false,
defer_loading: Some(true),
@@ -112,7 +112,7 @@ fn serialize_tool_search_output_tools_groups_results_by_namespace() {
output_schema: None,
}),
ResponsesApiNamespaceTool::Function(ResponsesApiTool {
name: "-list-events".to_string(),
name: "_list_events".to_string(),
description: "List calendar events.".to_string(),
strict: false,
defer_loading: Some(true),
@@ -129,7 +129,7 @@ fn serialize_tool_search_output_tools_groups_results_by_namespace() {
name: "mcp__codex_apps__gmail".to_string(),
description: "Read mail".to_string(),
tools: vec![ResponsesApiNamespaceTool::Function(ResponsesApiTool {
name: "-read-email".to_string(),
name: "_read_email".to_string(),
description: "Read an email.".to_string(),
strict: false,
defer_loading: Some(true),
@@ -148,10 +148,10 @@ fn serialize_tool_search_output_tools_groups_results_by_namespace() {
#[test]
fn serialize_tool_search_output_tools_falls_back_to_connector_name_description() {
let entries = [(
"mcp__codex_apps__gmail-batch-read-email".to_string(),
"mcp__codex_apps__gmail_batch_read_email".to_string(),
ToolInfo {
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
tool_name: "-batch-read-email".to_string(),
tool_name: "_batch_read_email".to_string(),
tool_namespace: "mcp__codex_apps__gmail".to_string(),
tool: Tool {
name: "gmail-batch-read-email".to_string().into(),
@@ -182,7 +182,7 @@ fn serialize_tool_search_output_tools_falls_back_to_connector_name_description()
name: "mcp__codex_apps__gmail".to_string(),
description: "Tools for working with Gmail.".to_string(),
tools: vec![ResponsesApiNamespaceTool::Function(ResponsesApiTool {
name: "-batch-read-email".to_string(),
name: "_batch_read_email".to_string(),
description: "Read multiple emails.".to_string(),
strict: false,
defer_loading: Some(true),
+7 -7
View File
@@ -1686,10 +1686,10 @@ fn search_tool_description_includes_only_codex_apps_connector_names() {
])),
Some(HashMap::from([
(
"mcp__codex_apps__calendar-create-event".to_string(),
"mcp__codex_apps__calendar_create_event".to_string(),
ToolInfo {
server_name: crate::mcp::CODEX_APPS_MCP_SERVER_NAME.to_string(),
tool_name: "-create-event".to_string(),
tool_name: "_create_event".to_string(),
tool_namespace: "mcp__codex_apps__calendar".to_string(),
tool: mcp_tool(
"calendar-create-event",
@@ -1862,10 +1862,10 @@ fn search_tool_registers_namespaced_app_tool_aliases() {
None,
Some(HashMap::from([
(
"mcp__codex_apps__calendar-create-event".to_string(),
"mcp__codex_apps__calendar_create_event".to_string(),
ToolInfo {
server_name: crate::mcp::CODEX_APPS_MCP_SERVER_NAME.to_string(),
tool_name: "-create-event".to_string(),
tool_name: "_create_event".to_string(),
tool_namespace: "mcp__codex_apps__calendar".to_string(),
tool: mcp_tool(
"calendar-create-event",
@@ -1879,10 +1879,10 @@ fn search_tool_registers_namespaced_app_tool_aliases() {
},
),
(
"mcp__codex_apps__calendar-list-events".to_string(),
"mcp__codex_apps__calendar_list_events".to_string(),
ToolInfo {
server_name: crate::mcp::CODEX_APPS_MCP_SERVER_NAME.to_string(),
tool_name: "-list-events".to_string(),
tool_name: "_list_events".to_string(),
tool_namespace: "mcp__codex_apps__calendar".to_string(),
tool: mcp_tool(
"calendar-list-events",
@@ -1900,7 +1900,7 @@ fn search_tool_registers_namespaced_app_tool_aliases() {
)
.build();
let alias = tool_handler_key("-create-event", Some("mcp__codex_apps__calendar"));
let alias = tool_handler_key("_create_event", Some("mcp__codex_apps__calendar"));
assert!(registry.has_handler(TOOL_SEARCH_TOOL_NAME, None));
assert!(registry.has_handler(alias.as_str(), None));