Skip to content

fix(stage): match tool semantics by bare MCP tool name - #831

Merged
grahamking merged 1 commit into
mainfrom
gk-1456
Sep 22, 2026
Merged

grahamking merged 1 commit into
mainfrom
gk-1456

Conversation

@grahamking

@grahamking grahamking commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Stage tool_semantics now match MCP tools by their bare tool name. For example, mutate = ["send_payment_request"] now matches:

  • A Codex Responses call with "name": "send_payment_request", "namespace": "mcp__billing".
  • A Claude Code call named mcp__billing__send_payment_request.

The full name mcp__billing__send_payment_request still matches both. The full name is checked first, and the bare name only when the full name matches nothing.

Changes:

  1. libsy finds the bare name in one of two ways:
    • Codex: from the namespace mapping that the Responses decoder stores on the request.
    • Claude Code: from the mcp__<server>__<tool> form. The name is split once, after the server name, so tool names that contain __ still work.
  2. The namespace key and its two read helpers (tool_namespaces, split_qualified_name) move from switchyard-translation into a new switchyard_protocol::codex_namespaces module. This lets libsy read the mapping without depending on
    switchyard-translation.

Fixes: https://linear.app/nvidia/issue/SWITCH-1456

Assisted-by: Claude:Opus 5.5 medium
Signed-off-by: Graham King grahamk@nvidia.com

Summary by CodeRabbit

  • New Features

    • Tool routing now matches both bare tool names and prefixed names for namespaced and MCP-style tool calls.
    • Tool selection can recognize qualified names like mcp__server__tool and still resolve the underlying tool when appropriate.
  • Bug Fixes

    • Improved matching for custom tool semantics so configured routes are applied more reliably.
  • Documentation

    • Updated routing guidance to explain how MCP tool names are matched, including edge cases with nested namespace separators.

Stage `tool_semantics` now match MCP tools by their bare tool name. For example, `mutate = ["send_payment_request"]` now matches:

- A Codex Responses call with `"name": "send_payment_request", "namespace": "mcp__billing"`.
- A Claude Code call named `mcp__billing__send_payment_request`.

The full name `mcp__billing__send_payment_request` still matches both. The full name is checked first, and the bare name only when the full name matches nothing.

Changes:

1. `libsy` finds the bare name in one of two ways:
    - Codex: from the namespace mapping that the Responses decoder stores on the request.
    - Claude Code: from the `mcp__<server>__<tool>` form. The name is split once, after the server name, so tool names that contain `__` still work.
2. The namespace key and its two read helpers (`tool_namespaces`, `split_qualified_name`) move from `switchyard-translation` into a new `switchyard_protocol::codex_namespaces` module. This lets `libsy` read the mapping without depending on
`switchyard-translation`.

Fixes: https://linear.app/nvidia/issue/SWITCH-1456

Assisted-by: Claude:Opus 5.5 medium
Signed-off-by: Graham King <grahamk@nvidia.com>
@grahamking
grahamking requested a review from a team as a code owner September 22, 2026 21:50
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1

🚀 View preview at
https://NVIDIA-NeMo.github.io/Switchyard/pr-preview/pr-831/

Built to branch gh-pages at 2026-09-22 21:51 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: NVIDIA-NeMo/Switchyard/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 811337d5-d0f4-47a1-9815-2dba40252849

📥 Commits

Reviewing files that changed from the base of the PR and between bec0cb5 and ec13b36.

📒 Files selected for processing (6)
  • crates/libsy/src/algorithms/util/tool_signals.rs
  • crates/protocol/src/codex_namespaces.rs
  • crates/protocol/src/lib.rs
  • crates/switchyard-translation/src/codecs/responses/buffered.rs
  • crates/switchyard-translation/src/codex_namespaces.rs
  • docs/routing_algorithms/stage_router_routing.md

Included review availability: Your plan provides up to 12 included reviews per hour; 10 remain after this review.


Walkthrough

Tool-signal extraction now matches configured semantics against bare names from namespaced and MCP-prefixed tool calls when the original name is unknown. Codex namespace helpers are exposed from the protocol crate, and translation code reuses them.

Changes

Tool semantic matching

Layer / File(s) Summary
Shared Codex namespace mapping
crates/protocol/src/codex_namespaces.rs, crates/protocol/src/lib.rs, crates/switchyard-translation/src/codex_namespaces.rs, crates/switchyard-translation/src/codecs/responses/buffered.rs
The protocol crate defines and exports the Codex namespace helpers. Translation code re-exports them, and Responses encoding borrows tool names until JSON construction. Emitted names and grouping remain unchanged.
Bare-name semantic matching
crates/libsy/src/algorithms/util/tool_signals.rs, docs/routing_algorithms/stage_router_routing.md
Tool-signal extraction derives bare names from namespace mappings or MCP-prefixed names. If the original name is classified as unknown, it retries using the bare name. A unit test and routing documentation cover the matching rules.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: 🔵 Low · up to ec13b

Unmapped Claude MCP calls with a server name containing __ can inherit another tool’s routing semantics when the parsed suffix matches a configured bare name. This is a narrow, configuration-dependent risk; merge with awareness that full-name matching avoids the fallback for configured calls.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 16 functions across 5 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: matching tool semantics by bare MCP tool name.
Full details: Docstring Coverage

Explanation

Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 16 functions across 5 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR

A rabbit checks the tool names in a row
Finds bare names where the namespaces go
MCP calls join the matching game
Unknown turns to a known tool name
Then hops away, pleased with the flow

Comment @coderabbitai help to get the list of available commands.

@grahamking
grahamking enabled auto-merge (squash) September 22, 2026 22:26
@grahamking
grahamking merged commit 42784fd into main Sep 22, 2026
19 of 20 checks passed
@grahamking
grahamking deleted the gk-1456 branch September 22, 2026 23:53
@sabhatinas

Copy link
Copy Markdown
Contributor

Do you think we should add a normalizer for all tool calls by defining at internal IR?

mrPronin added a commit to mrPronin/Switchyard that referenced this pull request Sep 23, 2026
@grahamking

Copy link
Copy Markdown
Contributor Author

Do you think we should add a normalizer for all tool calls by defining at internal IR?

I looked into it you kind of already have that with ToolSemantic and friends. I'm not sure we need any more than that.

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