This commit is contained in:
Sayan Sisodiya
2026-05-06 16:52:51 -07:00
parent 9e905528bb
commit bc961b75f1
31 changed files with 632 additions and 284 deletions
+8 -8
View File
@@ -112,7 +112,7 @@ fn codex_app_tool(
) -> ToolInfo {
let tool_namespace = connector_name
.map(sanitize_name)
.map(|connector_name| format!("mcp__{CODEX_APPS_MCP_SERVER_NAME}__{connector_name}"))
.map(|connector_name| format!("{CODEX_APPS_MCP_SERVER_NAME}__{connector_name}"))
.unwrap_or_else(|| CODEX_APPS_MCP_SERVER_NAME.to_string());
ToolInfo {
@@ -175,7 +175,7 @@ fn merge_connectors_replaces_plugin_placeholder_name_with_accessible_name() {
fn accessible_connectors_from_mcp_tools_carries_plugin_display_names() {
let tools = HashMap::from([
(
"mcp__codex_apps__calendar_list_events".to_string(),
"codex_apps__calendar__list_events".to_string(),
codex_app_tool(
"calendar_list_events",
"calendar",
@@ -184,7 +184,7 @@ fn accessible_connectors_from_mcp_tools_carries_plugin_display_names() {
),
),
(
"mcp__codex_apps__calendar_create_event".to_string(),
"codex_apps__calendar__create_event".to_string(),
codex_app_tool(
"calendar_create_event",
"calendar",
@@ -193,7 +193,7 @@ fn accessible_connectors_from_mcp_tools_carries_plugin_display_names() {
),
),
(
"mcp__sample__echo".to_string(),
"sample__echo".to_string(),
ToolInfo {
server_name: "sample".to_string(),
callable_name: "echo".to_string(),
@@ -242,7 +242,7 @@ async fn refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_instal
let cache_key = accessible_connectors_cache_key(&config, /*auth*/ None);
let tools = HashMap::from([
(
"mcp__codex_apps__calendar_list_events".to_string(),
"codex_apps__calendar__list_events".to_string(),
codex_app_tool(
"calendar_list_events",
"calendar",
@@ -251,7 +251,7 @@ async fn refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_instal
),
),
(
"mcp__codex_apps__openai_hidden".to_string(),
"codex_apps__openai_hidden".to_string(),
codex_app_tool(
"openai_hidden",
"connector_openai_hidden",
@@ -318,11 +318,11 @@ fn merge_connectors_unions_and_dedupes_plugin_display_names() {
#[test]
fn accessible_connectors_from_mcp_tools_preserves_description() {
let mcp_tools = HashMap::from([(
"mcp__codex_apps__calendar_create_event".to_string(),
"codex_apps__calendar__create_event".to_string(),
ToolInfo {
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
callable_name: "calendar_create_event".to_string(),
callable_namespace: "mcp__codex_apps__calendar".to_string(),
callable_namespace: "codex_apps__calendar".to_string(),
server_instructions: None,
tool: Tool {
name: "calendar_create_event".to_string().into(),
+17 -17
View File
@@ -1907,7 +1907,7 @@ async fn approve_mode_skips_when_annotations_do_not_require_approval() {
&turn_context,
"call-1",
&invocation,
"mcp__test__tool",
"test__tool",
Some(&metadata),
AppToolApproval::Approve,
)
@@ -1980,7 +1980,7 @@ async fn guardian_mode_skips_auto_when_annotations_do_not_require_approval() {
&turn_context,
"call-guardian",
&invocation,
"mcp__test__tool",
"test__tool",
Some(&metadata),
AppToolApproval::Auto,
)
@@ -1995,7 +1995,7 @@ async fn permission_request_hook_allows_mcp_tool_call() {
let log_path = install_mcp_permission_request_hook(
&mut session,
&turn_context,
"mcp__memory__.*",
"memory__.*",
&serde_json::json!({
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
@@ -2036,7 +2036,7 @@ async fn permission_request_hook_allows_mcp_tool_call() {
&turn_context,
"call-mcp-hook",
&invocation,
"mcp__memory__create_entities",
"memory__create_entities",
Some(&metadata),
AppToolApproval::Auto,
)
@@ -2057,7 +2057,7 @@ async fn permission_request_hook_allows_mcp_tool_call() {
"transcript_path": null,
"model": turn_context.model_info.slug,
"permission_mode": "default",
"tool_name": "mcp__memory__create_entities",
"tool_name": "memory__create_entities",
"hook_event_name": "PermissionRequest",
"tool_input": {
"entities": [{
@@ -2075,7 +2075,7 @@ async fn permission_request_hook_uses_hook_tool_name_without_metadata() {
let log_path = install_mcp_permission_request_hook(
&mut session,
&turn_context,
"mcp__memory__.*",
"memory__.*",
&serde_json::json!({
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
@@ -2096,7 +2096,7 @@ async fn permission_request_hook_uses_hook_tool_name_without_metadata() {
&turn_context,
"call-mcp-hook-no-metadata",
&invocation,
"mcp__memory__create_entities",
"memory__create_entities",
/*metadata*/ None,
AppToolApproval::Auto,
)
@@ -2117,7 +2117,7 @@ async fn permission_request_hook_uses_hook_tool_name_without_metadata() {
"transcript_path": null,
"model": turn_context.model_info.slug,
"permission_mode": "default",
"tool_name": "mcp__memory__create_entities",
"tool_name": "memory__create_entities",
"hook_event_name": "PermissionRequest",
"tool_input": { "entities": [] }
})]
@@ -2130,7 +2130,7 @@ async fn permission_request_hook_runs_after_remembered_mcp_approval() {
let log_path = install_mcp_permission_request_hook(
&mut session,
&turn_context,
"mcp__memory__.*",
"memory__.*",
&serde_json::json!({
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
@@ -2173,7 +2173,7 @@ async fn permission_request_hook_runs_after_remembered_mcp_approval() {
&turn_context,
"call-mcp-remembered",
&invocation,
"mcp__memory__create_entities",
"memory__create_entities",
Some(&metadata),
AppToolApproval::Auto,
)
@@ -2253,7 +2253,7 @@ async fn guardian_mode_mcp_denial_returns_rationale_message() {
&turn_context,
"call-guardian-deny",
&invocation,
"mcp__test__tool",
"test__tool",
Some(&metadata),
AppToolApproval::Auto,
)
@@ -2310,7 +2310,7 @@ async fn prompt_mode_waits_for_approval_when_annotations_do_not_require_approval
&turn_context,
"call-prompt",
&invocation,
"mcp__test__tool",
"test__tool",
Some(&metadata),
AppToolApproval::Prompt,
)
@@ -2385,7 +2385,7 @@ async fn approve_mode_blocks_when_arc_returns_interrupt_for_model() {
&turn_context,
"call-2",
&invocation,
"mcp__test__tool",
"test__tool",
Some(&metadata),
AppToolApproval::Approve,
)
@@ -2457,7 +2457,7 @@ async fn custom_approve_mode_blocks_when_arc_returns_interrupt_for_model() {
&turn_context,
"call-2-custom",
&invocation,
"mcp__test__tool",
"test__tool",
Some(&metadata),
AppToolApproval::Approve,
)
@@ -2529,7 +2529,7 @@ async fn approve_mode_blocks_when_arc_returns_interrupt_without_annotations() {
&turn_context,
"call-3",
&invocation,
"mcp__test__tool",
"test__tool",
Some(&metadata),
AppToolApproval::Approve,
)
@@ -2611,7 +2611,7 @@ async fn full_access_mode_skips_arc_monitor_for_all_approval_modes() {
&turn_context,
"call-2",
&invocation,
"mcp__test__tool",
"test__tool",
Some(&metadata),
approval_mode,
)
@@ -2701,7 +2701,7 @@ async fn approve_mode_skips_arc_and_guardian_when_guardian_reviewer_is_enabled()
&turn_context,
"call-3",
&invocation,
"mcp__test__tool",
"test__tool",
Some(&metadata),
AppToolApproval::Approve,
)
+12 -12
View File
@@ -48,10 +48,10 @@ fn make_mcp_tool(
let tool_namespace = if server_name == CODEX_APPS_MCP_SERVER_NAME {
connector_name
.map(sanitize_name)
.map(|connector_name| format!("mcp__{server_name}__{connector_name}"))
.map(|connector_name| format!("{server_name}__{connector_name}"))
.unwrap_or_else(|| server_name.to_string())
} else {
format!("mcp__{server_name}__")
server_name.to_string()
};
ToolInfo {
@@ -82,7 +82,7 @@ fn numbered_mcp_tools(count: usize) -> HashMap<String, ToolInfo> {
.map(|index| {
let tool_name = format!("tool_{index}");
(
format!("mcp__rmcp__{tool_name}"),
format!("rmcp__{tool_name}"),
make_mcp_tool(
"rmcp", &tool_name, /*connector_id*/ None, /*connector_name*/ None,
),
@@ -165,7 +165,7 @@ async fn directly_exposes_explicit_apps_without_deferred_overlap() {
let tools_config = tools_config_for_mcp_tool_exposure(/*search_tool*/ true).await;
let mut mcp_tools = numbered_mcp_tools(DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD - 1);
mcp_tools.extend([(
"mcp__codex_apps__calendar_create_event".to_string(),
"codex_apps__calendar__create_event".to_string(),
make_mcp_tool(
CODEX_APPS_MCP_SERVER_NAME,
"calendar_create_event",
@@ -187,7 +187,7 @@ async fn directly_exposes_explicit_apps_without_deferred_overlap() {
tool_names.sort();
assert_eq!(
tool_names,
vec!["mcp__codex_apps__calendar_create_event".to_string()]
vec!["codex_apps__calendar__create_event".to_string()]
);
assert_eq!(
exposure.deferred_tools.as_ref().map(HashMap::len),
@@ -203,8 +203,8 @@ async fn directly_exposes_explicit_apps_without_deferred_overlap() {
.all(|direct_tool_name| !deferred_tools.contains_key(direct_tool_name)),
"direct tools should not also be deferred: {tool_names:?}"
);
assert!(!deferred_tools.contains_key("mcp__codex_apps__calendar_create_event"));
assert!(deferred_tools.contains_key("mcp__rmcp__tool_0"));
assert!(!deferred_tools.contains_key("codex_apps__calendar__create_event"));
assert!(deferred_tools.contains_key("rmcp__tool_0"));
}
#[tokio::test]
@@ -217,13 +217,13 @@ async fn always_defer_feature_preserves_explicit_apps() {
let tools_config = tools_config_for_mcp_tool_exposure(/*search_tool*/ true).await;
let mcp_tools = HashMap::from([
(
"mcp__rmcp__tool".to_string(),
"rmcp__tool".to_string(),
make_mcp_tool(
"rmcp", "tool", /*connector_id*/ None, /*connector_name*/ None,
),
),
(
"mcp__codex_apps__calendar_create_event".to_string(),
"codex_apps__calendar__create_event".to_string(),
make_mcp_tool(
CODEX_APPS_MCP_SERVER_NAME,
"calendar_create_event",
@@ -246,12 +246,12 @@ async fn always_defer_feature_preserves_explicit_apps() {
direct_tool_names.sort();
assert_eq!(
direct_tool_names,
vec!["mcp__codex_apps__calendar_create_event".to_string()]
vec!["codex_apps__calendar__create_event".to_string()]
);
let deferred_tools = exposure
.deferred_tools
.as_ref()
.expect("MCP tools should be discoverable through tool_search");
assert!(deferred_tools.contains_key("mcp__rmcp__tool"));
assert!(!deferred_tools.contains_key("mcp__codex_apps__calendar_create_event"));
assert!(deferred_tools.contains_key("rmcp__tool"));
assert!(!deferred_tools.contains_key("codex_apps__calendar__create_event"));
}
+4 -4
View File
@@ -142,12 +142,12 @@ mod tests {
cancellation_token: tokio_util::sync::CancellationToken::new(),
tracker: Arc::new(Mutex::new(TurnDiffTracker::new())),
call_id: "call-mcp-pre".to_string(),
tool_name: codex_tools::ToolName::namespaced("mcp__memory__", "create_entities"),
tool_name: codex_tools::ToolName::namespaced("memory", "create_entities"),
source: ToolCallSource::Direct,
payload,
}),
Some(PreToolUsePayload {
tool_name: HookToolName::new("mcp__memory__create_entities"),
tool_name: HookToolName::new("memory__create_entities"),
tool_input: json!({
"entities": [{
"name": "Ada",
@@ -191,14 +191,14 @@ mod tests {
cancellation_token: tokio_util::sync::CancellationToken::new(),
tracker: Arc::new(Mutex::new(TurnDiffTracker::new())),
call_id: "call-mcp-post".to_string(),
tool_name: codex_tools::ToolName::namespaced("mcp__filesystem__", "read_file"),
tool_name: codex_tools::ToolName::namespaced("filesystem", "read_file"),
source: ToolCallSource::Direct,
payload,
};
assert_eq!(
McpHandler.post_tool_use_payload(&invocation, &output),
Some(PostToolUsePayload {
tool_name: HookToolName::new("mcp__filesystem__read_file"),
tool_name: HookToolName::new("filesystem__read_file"),
tool_use_id: "call-mcp-post".to_string(),
tool_input: json!({
"path": {
@@ -202,11 +202,11 @@ mod tests {
let handler = handler_from_tools(
Some(&std::collections::HashMap::from([
(
"mcp__calendar__create_event".to_string(),
"calendar__create_event".to_string(),
tool_info("calendar", "create_event", "Create events"),
),
(
"mcp__calendar__list_events".to_string(),
"calendar__list_events".to_string(),
tool_info("calendar", "list_events", "List events"),
),
])),
@@ -226,8 +226,8 @@ mod tests {
tools,
vec![
LoadableToolSpec::Namespace(ResponsesApiNamespace {
name: "mcp__calendar__".to_string(),
description: "Tools in the mcp__calendar__ namespace.".to_string(),
name: "calendar".to_string(),
description: "Tools in the calendar namespace.".to_string(),
tools: vec![
ResponsesApiNamespaceTool::Function(ResponsesApiTool {
name: "create_event".to_string(),
@@ -380,7 +380,7 @@ mod tests {
.map(|index| {
let tool_name = format!("tool_{index:03}");
(
format!("mcp__{server_name}__{tool_name}"),
format!("{server_name}__{tool_name}"),
tool_info(server_name, &tool_name, description_prefix),
)
})
@@ -391,7 +391,7 @@ mod tests {
ToolInfo {
server_name: server_name.to_string(),
callable_name: tool_name.to_string(),
callable_namespace: format!("mcp__{server_name}__"),
callable_namespace: server_name.to_string(),
server_instructions: None,
tool: Tool {
name: tool_name.to_string().into(),
+2 -2
View File
@@ -23,7 +23,7 @@ impl ToolHandler for TestHandler {
fn handler_looks_up_namespaced_aliases_explicitly() {
let plain_handler = Arc::new(TestHandler) as Arc<dyn AnyToolHandler>;
let namespaced_handler = Arc::new(TestHandler) as Arc<dyn AnyToolHandler>;
let namespace = "mcp__codex_apps__gmail";
let namespace = "codex_apps__gmail";
let tool_name = "gmail_get_recent_emails";
let plain_name = codex_tools::ToolName::plain(tool_name);
let namespaced_name = codex_tools::ToolName::namespaced(namespace, tool_name);
@@ -35,7 +35,7 @@ fn handler_looks_up_namespaced_aliases_explicitly() {
let plain = registry.handler(&plain_name);
let namespaced = registry.handler(&namespaced_name);
let missing_namespaced = registry.handler(&codex_tools::ToolName::namespaced(
"mcp__codex_apps__calendar",
"codex_apps__calendar",
tool_name,
));
+5 -5
View File
@@ -55,7 +55,7 @@ async fn parallel_support_does_not_match_namespaced_local_tool_names() -> anyhow
.expect("test session should expose a parallel shell-like tool");
assert!(!router.tool_supports_parallel(&ToolCall {
tool_name: ToolName::namespaced("mcp__server__", parallel_tool_name),
tool_name: ToolName::namespaced("server", parallel_tool_name),
call_id: "call-namespaced-tool".to_string(),
payload: ToolPayload::Function {
arguments: "{}".to_string(),
@@ -76,7 +76,7 @@ async fn build_tool_call_uses_namespace_for_registry_name() -> anyhow::Result<()
ResponseItem::FunctionCall {
id: None,
name: tool_name.clone(),
namespace: Some("mcp__codex_apps__calendar".to_string()),
namespace: Some("codex_apps__calendar".to_string()),
arguments: "{}".to_string(),
call_id: "call-namespace".to_string(),
},
@@ -86,7 +86,7 @@ async fn build_tool_call_uses_namespace_for_registry_name() -> anyhow::Result<()
assert_eq!(
call.tool_name,
ToolName::namespaced("mcp__codex_apps__calendar", tool_name)
ToolName::namespaced("codex_apps__calendar", tool_name)
);
assert_eq!(call.call_id, "call-namespace");
match call.payload {
@@ -115,7 +115,7 @@ async fn mcp_parallel_support_uses_exact_payload_server() -> anyhow::Result<()>
);
let deferred_call = ToolCall {
tool_name: ToolName::namespaced("mcp__echo__", "query_with_delay"),
tool_name: ToolName::namespaced("echo", "query_with_delay"),
call_id: "call-deferred".to_string(),
payload: ToolPayload::Mcp {
server: "echo".to_string(),
@@ -126,7 +126,7 @@ async fn mcp_parallel_support_uses_exact_payload_server() -> anyhow::Result<()>
assert!(router.tool_supports_parallel(&deferred_call));
let different_server_call = ToolCall {
tool_name: ToolName::namespaced("mcp__hello_echo__", "query_with_delay"),
tool_name: ToolName::namespaced("hello_echo", "query_with_delay"),
call_id: "call-other-server".to_string(),
payload: ToolPayload::Mcp {
server: "hello_echo".to_string(),
+24 -24
View File
@@ -61,7 +61,7 @@ fn mcp_tool_info(tool: rmcp::model::Tool) -> ToolInfo {
ToolInfo {
server_name: "test_server".to_string(),
callable_name: tool.name.to_string(),
callable_namespace: "mcp__test_server__".to_string(),
callable_namespace: "test_server".to_string(),
server_instructions: None,
tool,
connector_id: None,
@@ -133,7 +133,7 @@ fn deferred_responses_api_tool_serializes_with_defer_loading() {
let serialized = serde_json::to_value(ToolSpec::Function(
mcp_tool_to_deferred_responses_api_tool(
&ToolName::namespaced("mcp__codex_apps__", "lookup_order"),
&ToolName::namespaced("codex_apps", "lookup_order"),
&tool,
)
.expect("convert deferred tool"),
@@ -893,11 +893,11 @@ async fn search_tool_description_falls_back_to_connector_name_without_descriptio
&tools_config,
/*mcp_tools*/ None,
Some(HashMap::from([(
"mcp__codex_apps__calendar_create_event".to_string(),
"codex_apps__calendar__create_event".to_string(),
ToolInfo {
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
callable_name: "_create_event".to_string(),
callable_namespace: "mcp__codex_apps__calendar".to_string(),
callable_name: "create_event".to_string(),
callable_namespace: "codex_apps__calendar".to_string(),
server_instructions: None,
tool: mcp_tool(
"calendar_create_event",
@@ -945,11 +945,11 @@ async fn search_tool_registers_namespaced_mcp_tool_aliases() {
/*mcp_tools*/ None,
Some(HashMap::from([
(
"mcp__codex_apps__calendar_create_event".to_string(),
"codex_apps__calendar__create_event".to_string(),
ToolInfo {
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
callable_name: "_create_event".to_string(),
callable_namespace: "mcp__codex_apps__calendar".to_string(),
callable_name: "create_event".to_string(),
callable_namespace: "codex_apps__calendar".to_string(),
server_instructions: None,
tool: mcp_tool(
"calendar-create-event",
@@ -963,11 +963,11 @@ async fn search_tool_registers_namespaced_mcp_tool_aliases() {
},
),
(
"mcp__codex_apps__calendar_list_events".to_string(),
"codex_apps__calendar__list_events".to_string(),
ToolInfo {
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
callable_name: "_list_events".to_string(),
callable_namespace: "mcp__codex_apps__calendar".to_string(),
callable_name: "list_events".to_string(),
callable_namespace: "codex_apps__calendar".to_string(),
server_instructions: None,
tool: mcp_tool(
"calendar-list-events",
@@ -981,11 +981,11 @@ async fn search_tool_registers_namespaced_mcp_tool_aliases() {
},
),
(
"mcp__rmcp__echo".to_string(),
"rmcp__echo".to_string(),
ToolInfo {
server_name: "rmcp".to_string(),
callable_name: "echo".to_string(),
callable_namespace: "mcp__rmcp__".to_string(),
callable_namespace: "rmcp".to_string(),
server_instructions: None,
tool: mcp_tool("echo", "Echo", serde_json::json!({"type": "object"})),
connector_id: None,
@@ -999,8 +999,8 @@ async fn search_tool_registers_namespaced_mcp_tool_aliases() {
)
.build();
let app_alias = ToolName::namespaced("mcp__codex_apps__calendar", "_create_event");
let mcp_alias = ToolName::namespaced("mcp__rmcp__", "echo");
let app_alias = ToolName::namespaced("codex_apps__calendar", "create_event");
let mcp_alias = ToolName::namespaced("rmcp", "echo");
assert!(registry.has_handler(&ToolName::plain(TOOL_SEARCH_TOOL_NAME)));
assert!(registry.has_handler(&app_alias));
@@ -1025,7 +1025,7 @@ async fn tool_search_entries_skip_namespace_outputs_when_namespace_tools_are_dis
});
tools_config.namespace_tools = false;
let mcp_tools = HashMap::from([(
"mcp__test_server__echo".to_string(),
"test_server__echo".to_string(),
mcp_tool_info(mcp_tool(
"echo",
"Echo",
@@ -1084,7 +1084,7 @@ async fn direct_mcp_tools_register_namespaced_handlers() {
let (_, registry) = build_specs(
&tools_config,
Some(HashMap::from([(
"mcp__test_server__echo".to_string(),
"test_server__echo".to_string(),
mcp_tool_info(mcp_tool(
"echo",
"Echo",
@@ -1096,8 +1096,8 @@ async fn direct_mcp_tools_register_namespaced_handlers() {
)
.build();
assert!(registry.has_handler(&ToolName::namespaced("mcp__test_server__", "echo")));
assert!(!registry.has_handler(&ToolName::plain("mcp__test_server__echo")));
assert!(registry.has_handler(&ToolName::namespaced("test_server", "echo")));
assert!(!registry.has_handler(&ToolName::plain("test_server__echo")));
}
#[tokio::test]
@@ -1118,7 +1118,7 @@ async fn unavailable_mcp_tools_are_exposed_as_dummy_function_tools() {
windows_sandbox_level: WindowsSandboxLevel::Disabled,
});
let unavailable_tool = ToolName::namespaced("mcp__codex_apps__calendar", "_create_event");
let unavailable_tool = ToolName::namespaced("codex_apps__calendar", "create_event");
let (tools, registry) = build_specs_with_unavailable_tools(
&tools_config,
/*mcp_tools*/ None,
@@ -1128,7 +1128,7 @@ async fn unavailable_mcp_tools_are_exposed_as_dummy_function_tools() {
)
.build();
let tool = find_tool(&tools, "mcp__codex_apps__calendar_create_event");
let tool = find_tool(&tools, "codex_apps__calendar__create_event");
let ToolSpec::Function(ResponsesApiTool {
description,
parameters,
@@ -1143,10 +1143,10 @@ async fn unavailable_mcp_tools_are_exposed_as_dummy_function_tools() {
Some(AdditionalProperties::Boolean(false))
);
assert!(registry.has_handler(&ToolName::namespaced(
"mcp__codex_apps__calendar",
"_create_event"
"codex_apps__calendar",
"create_event"
)));
assert!(!registry.has_handler(&ToolName::plain("mcp__codex_apps__calendar_create_event")));
assert!(!registry.has_handler(&ToolName::plain("codex_apps__calendar__create_event")));
}
#[tokio::test]
+7 -2
View File
@@ -39,7 +39,10 @@ pub(crate) fn collect_unavailable_called_tools(
}
fn should_collect_unavailable_tool(name: &str, namespace: Option<&str>) -> bool {
namespace.is_some_and(|namespace| namespace.starts_with("mcp__")) || name.starts_with("mcp__")
// New histories preserve the namespace split, so any missing namespaced call
// is eligible for a placeholder. Keep the flattened MCP branch for rollouts
// written before namespaced MCP calls were preserved in history.
namespace.is_some() || name.starts_with("mcp__")
}
#[cfg(test)]
@@ -58,11 +61,12 @@ mod tests {
}
#[test]
fn collect_unavailable_called_tools_detects_mcp_function_calls() {
fn collect_unavailable_called_tools_detects_namespaced_and_legacy_mcp_calls() {
let input = vec![
function_call("shell", /*namespace*/ None),
function_call("mcp__server__lookup", /*namespace*/ None),
function_call("_create_event", Some("mcp__codex_apps__calendar")),
function_call("lookup", Some("calendar")),
];
let tools = collect_unavailable_called_tools(&input, &HashSet::new());
@@ -70,6 +74,7 @@ mod tests {
assert_eq!(
tools,
vec![
ToolName::namespaced("calendar", "lookup"),
ToolName::namespaced("mcp__codex_apps__calendar", "_create_event"),
ToolName::plain("mcp__server__lookup"),
]