Simplify MCP tool handler plumbing (#21595)

## Why
The MCP tool path had accumulated a few core-owned special cases: a
dedicated payload variant, resolver plumbing, a legacy `AfterToolUse`
translation path, and a side channel for parallel-call metadata. That
made `ToolRegistry` and the spec builder know more about MCP than they
needed to.

This change moves MCP-specific execution details back onto `ToolInfo`
and `McpHandler` so `codex-core` can treat MCP calls like normal
function calls while still preserving MCP-specific dispatch and
telemetry behavior where it belongs.

## What changed
- removed `resolve_mcp_tool_info`, `ToolPayload::Mcp`, `ToolKind`, and
the remaining registry-side MCP resolver path
- stored MCP routing metadata directly on `McpHandler` and `ToolInfo`,
including `supports_parallel_tool_calls`
- deleted the legacy `AfterToolUse` consumer in `core`, which removes
the need for handler-specific `after_tool_use_payload` implementations
- switched tool-result telemetry to handler-provided tags and kept
MCP-specific dispatch payload construction inside the handler
- simplified tool spec planning/building by passing `ToolInfo` directly
and dropping the direct/deferred MCP wrapper structs and the
parallel-server side table

## Testing
- `cargo check -p codex-core -p codex-mcp -p codex-otel`
- `cargo test -p codex-core
mcp_parallel_support_uses_exact_payload_server`
- `cargo test -p codex-core
direct_mcp_tools_register_namespaced_handlers`
- `cargo test -p codex-core
search_tool_description_lists_each_mcp_source_once`
- `cargo test -p codex-mcp
list_all_tools_uses_startup_snapshot_while_client_is_pending`
- `just fix -p codex-core -p codex-mcp -p codex-otel`
This commit is contained in:
pakrym-oai
2026-05-12 00:11:31 +00:00
committed by GitHub
parent e16b4e46d4
commit ed5944ba1d
70 changed files with 412 additions and 638 deletions
@@ -3,7 +3,6 @@ use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload;
use crate::tools::registry::ToolHandler;
use crate::tools::registry::ToolKind;
use codex_tools::ToolName;
use codex_tools::ToolSpec;
@@ -94,10 +93,6 @@ impl ToolHandler for CodeModeExecuteHandler {
Some(self.spec.clone())
}
fn kind(&self) -> ToolKind {
ToolKind::Function
}
fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Custom { .. })
}
+6 -23
View File
@@ -276,7 +276,6 @@ async fn build_nested_router(exec: &ExecContext) -> ToolRouter {
let nested_tools_config = exec.turn.tools_config.for_code_mode_nested_tools();
let mcp_connection_manager = exec.session.services.mcp_connection_manager.read().await;
let listed_mcp_tools = mcp_connection_manager.list_all_tools().await;
let parallel_mcp_server_names = mcp_connection_manager.parallel_tool_call_server_names();
ToolRouter::from_config(
&nested_tools_config,
@@ -284,7 +283,6 @@ async fn build_nested_router(exec: &ExecContext) -> ToolRouter {
deferred_mcp_tools: None,
mcp_tools: Some(listed_mcp_tools),
unavailable_called_tools: Vec::new(),
parallel_mcp_server_names,
discoverable_tools: None,
extension_tool_bundles: extension_tool_bundles(exec.session.as_ref()),
dynamic_tools: exec.turn.dynamic_tools.as_slice(),
@@ -293,7 +291,7 @@ async fn build_nested_router(exec: &ExecContext) -> ToolRouter {
}
async fn call_nested_tool(
exec: ExecContext,
_exec: ExecContext,
tool_runtime: ToolCallRuntime,
invocation: CodeModeNestedToolCall,
cancellation_token: CancellationToken,
@@ -310,29 +308,14 @@ async fn call_nested_tool(
)));
}
let (tool_call_name, payload) =
if let Some(tool_info) = exec.session.resolve_mcp_tool_info(&tool_name).await {
let raw_arguments = match serialize_function_tool_arguments(&tool_name, input) {
Ok(raw_arguments) => raw_arguments,
Err(error) => return Err(FunctionCallError::RespondToModel(error)),
};
(
tool_info.canonical_tool_name(),
ToolPayload::Mcp {
server: tool_info.server_name,
tool: tool_info.tool.name.to_string(),
raw_arguments,
},
)
} else {
match build_nested_tool_payload(tool_runtime.find_spec(&tool_name), &tool_name, input) {
Ok(payload) => (tool_name, payload),
Err(error) => return Err(FunctionCallError::RespondToModel(error)),
}
let payload =
match build_nested_tool_payload(tool_runtime.find_spec(&tool_name), &tool_name, input) {
Ok(payload) => payload,
Err(error) => return Err(FunctionCallError::RespondToModel(error)),
};
let call = ToolCall {
tool_name: tool_call_name,
tool_name,
call_id: format!("{PUBLIC_TOOL_NAME}-{}", uuid::Uuid::new_v4()),
payload,
};
@@ -5,7 +5,6 @@ use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload;
use crate::tools::registry::ToolHandler;
use crate::tools::registry::ToolKind;
use codex_tools::ToolName;
use codex_tools::ToolSpec;
@@ -52,10 +51,6 @@ impl ToolHandler for CodeModeWaitHandler {
Some(create_wait_tool())
}
fn kind(&self) -> ToolKind {
ToolKind::Function
}
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
let ToolInvocation {
session,