Skip to content

fix(core): normalize claude-code MCP tool names to bare endpoint - #178

Open
Rodriguespn wants to merge 3 commits into
mainfrom
fix/claude-code-mcp-tool-endpoint
Open

fix(core): normalize claude-code MCP tool names to bare endpoint#178
Rodriguespn wants to merge 3 commits into
mainfrom
fix/claude-code-mcp-tool-endpoint

Conversation

@Rodriguespn

@Rodriguespn Rodriguespn commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

What

The claude-code parser recorded MCP tool calls' originalName (the scorer-facing endpoint) as Claude Code's raw mcp__<server>__<tool> (e.g. mcp__supabase-mcp__query_logs), while the Codex parser records the bare query_logs. So a scorer doing tc.endpoint === "query_logs" matches Codex but never matches claude-code — tool-selection evals false-fail on claude-code for behavior that actually happened.

This strips the mcp__<server>__ prefix in the claude-code parser so the endpoint is agent-agnostic, matching Codex. The raw name is still passed to normalizeToolName, so name stays tool_use.

Why it's in the parser

Each agent parser owns normalizing its own tool names; Codex already emits the bare MCP tool name, claude-code was the outlier.

Verification

  • End-to-end: investigate-logs-002-query-logs-tool-selection with claude-code-sonnet-5 against the mcp#333 preview build went FAIL (1/2) → PASS (2/2); Showing a false negative (agent was called the tool but the eval check ignored it)

Claude Code names MCP tools `mcp__<server>__<tool>`, so the parser recorded
`originalName` (the scorer-facing endpoint) as e.g.
`mcp__supabase-mcp__query_logs`, while the Codex parser records the bare
`query_logs`. A scorer doing `tc.endpoint === "query_logs"` therefore matches
Codex but silently never matches Claude Code, so tool-selection evals
false-fail on Claude Code for behavior that actually happened.

Strip the `mcp__<server>__` prefix in the claude-code parser so the endpoint
is agent-agnostic, consistent with Codex. The raw name is still passed to
`normalizeToolName`, so `name` stays `tool_use`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
evals-runner Error Error Aug 10, 2026 7:48pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
evals Ignored Ignored Preview Aug 10, 2026 7:48pm

Request Review

@Rodriguespn
Rodriguespn requested a review from mattrossman August 7, 2026 13:58
@Rodriguespn
Rodriguespn marked this pull request as ready for review August 7, 2026 14:00
@Rodriguespn
Rodriguespn requested a review from a team August 7, 2026 14:00
Rodriguespn and others added 2 commits August 7, 2026 16:12
… parsers

Replaces the flat scorer-facing `endpoint` string with a structured
`tool: ToolCall` — `{ kind: 'mcp'; server; toolName } | { kind: 'other'; toolName }`
— produced by each agent parser from what its transcript encodes:

- claude-code: split `mcp__<server>__<tool>` structurally.
- codex: `mcp_tool_call` items carry the server (explicit `item.server`, else
  the sole configured MCP server from ParseContext); bare `item.tool` name.
- opencode: `<server>_<tool>` is lossy, so recover the server by matching the
  configured names' sanitized prefix (opencode's own `sanitize`) via a new
  optional `ParseContext.mcpServerNames`, threaded from the engine.
- ai-sdk executor: attribute via a tool→server map built from the MCP handles.

`toolName` is the bare, agent-agnostic name (no server prefix); `originalName`
stays raw for tracing. This lets scorers disambiguate our MCP server's tool
from a same-named native/hosted tool or another server's tool, e.g.
`tc.tool.kind === 'mcp' && tc.tool.server === 'supabase-mcp' && tc.tool.toolName === 'query_logs'`.

Supersedes the earlier claude-code-only prefix strip. Updates consumers
(docs-results, resolve-database-001 scorer) and all parser tests; adds MCP
attribution coverage for codex (explicit + sole-server) and opencode (prefix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ults tests

docs-results.test.ts re-derived the mcp__<server>__<tool> split instead of
reusing claude-code/parser's parseClaudeCodeToolCall, which already owns that
format. Export and reuse it instead of duplicating the parsing logic.
// treat a missing status as unknown (not success). `item.tool` is the
// bare tool name; `item.server` names the MCP server when present.
const bare =
str(item.tool) ?? str(item.name) ?? str(item.server) ?? 'mcp_tool_call';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's not new in this PR, but this fallback chain looks strange. I wouldn't expect the tool name to fall back to server name, maybe better to keep it undefined or generic fallback text if it's truly something that can go missing (Codex source seems to indicate it's not optional)

@mattrossman

Copy link
Copy Markdown
Collaborator

Sanity checked running all 3 harnesses, toolName and related fields appear to be populating as expected! Claude examples below.

CleanShot 2026-08-11 at 11 45 19@2x CleanShot 2026-08-11 at 11 46 28@2x

str(item.tool) ?? str(item.name) ?? str(item.server) ?? 'mcp_tool_call';
// Prefer the explicit server field; fall back to the sole configured MCP
// server when the (unpinned) shape omits it.
const server = str(item.server) ?? soleServer;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove the concept of "soleServer" as a fallback entirely, seems like dead code given the version of Codex we're on. I'd assume it will always tell us which server an MCP tool call was associated with, otherwise I'd want it fail validation in some visible way so we can stay aware of parser behaviors.

@mattrossman mattrossman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving to unblock, see notes about about Codex fallback cleanup opportunities

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants