Repository navigation
Conversation
`getAllMcpTools` compared the current server's tool names against only the names allocated by earlier servers, and collapsed the current batch through a `Set` first, so two tools from the same server that normalize to one function tool name were both returned under that name. `toFunctionToolName` rewrites every non-alphanumeric character to `_`, so raw names such as `search-a` and `search_a` both become `search_a`. - Detect names that repeat inside the current batch as well, instead of collapsing them before the comparison, and use the same helper in both the prefixed and non-prefixed paths. - Count and reserve prefixed names by their normalized function tool name in `buildPrefixedToolNameOverrides`, so an intra-server collision forces the same deterministic hash suffix already used for other prefixed collisions instead of surfacing only after normalization.
🦋 Changeset detectedLatest commit: f754145 The changes in this PR will be included in the next version bump. This PR includes changesets to release 5 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
jbeckwith-oai
left a comment
There was a problem hiding this comment.
Reviewed the complete three-file change at 3281882 against d187747, including #1934, the existing allocation/dispatch paths, and the v0.18.0 compatibility baseline.
Need status: demonstrated. This is a supported tool-identity failure with a caller-visible consequence, and the patch is merge-worthy as-is. I found no blocking correctness, compatibility, or scope issues.
The root cause is twofold: the default check converts each server's names to a Set before checking only previously processed servers, hiding duplicates within a server; the prefixed allocator compares names before the final toFunctionToolName normalization, so search-a and search_a appear distinct until both become mcp_docs__search_a. The runner then retains only one function under its default collision policy.
I independently reproduced both levels of the problem using source snapshots of the exact base and head, synthetic MCP discovery/call stubs, and the SDK's ScriptedModel:
- Running the PR's mcpCache tests against the base produces exactly the two reported failures: 67 pass and 2 fail. Both failures disappear on the PR head.
- Through the public run API, the base exposes only one prefixed tool and invokes only the original search_a tool. The head exposes two unique tools and invokes both original names, search-a and search_a. This holds for streaming and non-streaming runs.
- Without prefixing, the head raises UserError before any model request or MCP invocation, rather than silently allowing the runner to drop a tool.
- Additional controls confirm that a normalized local function name is reserved correctly, and filtering out one colliding tool avoids an unnecessary error.
The implementation is placed at the right boundary. findDuplicateToolNames checks both intra-batch and prior-server duplicates without mutating the caller's set. Applying the existing normalization to the allocator's counts, reservation checks, and used-name checks closes the same identity gap throughout allocation. Keeping raw tool names in the hash seed and dispatch closure preserves deterministic disambiguation and correct remote invocation. Changing global function-name normalization would have a much broader compatibility impact; reusing this existing allocator is the narrower fix. Configuring prefixes alone does not solve the problem on the base.
I also inspected the affected identity/security boundary: server-name redaction still occurs before prefix/hash allocation, and this patch does not change MCP approval, guardrail, credential, or execution configuration. Existing focused coverage for redaction, guardrail isolation, cache rebinding/filtering, reserved handoff names, and long-name allocation passes.
Validation: 139 tests passed across mcpCache, mcpToFunctionTool, mcpToolFilter, runner/modelPreparation, and six independent reproduction/control cases. The six controls also pass against the base with expectations for its observed broken behavior. ESLint passed for both changed TypeScript files; Prettier passed for all three changed files; git diff --check passed.
Limits: these were deterministic local tests, not live model/MCP service calls. I did not rerun the full monorepo verification stack. GitHub CI for this head currently reports action_required, so this code approval does not establish CI readiness.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
The existing coverage pins the allocated names, but not the outcome a user reports: a second tool on the same server disappearing from the run. This drives run() with a ScriptedModel that calls both advertised tools and asserts the server receives both original names, in streaming and non-streaming modes. Against the current base it fails with `expected 1 to be 2` on both modes, because only one tool is advertised.
Summary
getAllMcpToolsonly detected duplicate function tool names across MCP servers. Two tools from the same server whose names normalize to one function tool name were both returned under that name, so only one of them stayed reachable by the model.Why
Every MCP tool name is passed through
toFunctionToolName, which replaces every non-alphanumeric character with_(packages/agents-core/src/utils/tools.ts:87). Raw MCP names such assearch-aandsearch_atherefore both becomesearch_a.The guard in
getAllMcpToolscompared the current server's names against only the names allocated by earlier servers, and collapsed the current batch through aSetfirst, so a repeat inside the batch never reached the comparison:packages/agents-core/src/mcp.ts:892-900onmain(default path,includeServerInToolNames: false)packages/agents-core/src/mcp.ts:865-873onmain(includeServerInToolNames: true)In the prefixed path the collision originated in
buildPrefixedToolNameOverrides(packages/agents-core/src/mcp.ts:1050):getSafeToolNameParttreats both-and_as safe and preserves them, sosearch-aandsearch_aproduced different prefixed base names,baseNameCountscounted one occurrence of each, no hash suffix was forced, and the two names collided only oncetoFunctionToolNamenormalized them.Changes
packages/agents-core/src/mcp.ts— bothgetAllMcpToolspaths now use afindDuplicateToolNameshelper that reports a name repeating inside the current batch as well as a name already allocated by an earlier server. The non-prefixed path raises the existingUserError, matching the cross-server behavior.packages/agents-core/src/mcp.ts—buildPrefixedToolNameOverridescounts and reserves prefixed names by their normalized function tool name, so a same-server collision forces the same deterministic hash suffix that other prefixed collisions already use. Both tools stay reachable instead of one being dropped byresolveModelVisibleToolNameCollisions..changeset/mcp-intra-server-tool-name-collisions.md— patch changeset for@openai/agents-core.Test plan
Added
throws when one server returns tool names that normalize to one function tool name, and extended the existingallocates normalized MCP tool name collisions stablywith thesearch-a/search_apair, inpackages/agents-core/test/mcpCache.test.ts.On the unpatched tree, before the fix:
With the fix:
Full verification on
d187747e9b905a515608bc1d6720d0e52b766b5eplus this commit:pnpm test:examplesalso passes. One earlier local run of the full suite reported two PTY-signal failures inpackages/agents-core/test/sandboxes/unixLocal.signals.test.ts; that file contains no MCP code and fails the same way with this change stashed away, so it is unrelated to this diff.Issue number
Closes #1934
Checks
pnpm testandpnpm test:examplespnpm test:integration(see details).agents/skills/code-change-verification/scripts/run.sh/reviewbefore submitting this PRpnpm changesetto indicate my changes