feat(mcp): adopt mcp__server__tool naming convention (port from opencode#33533) - #52750
Merged
Merged
Conversation
Contributor
🔎 Lint report:
|
Port from anomalyco/opencode#33533. Native MCP tools now register as mcp__<server>__<tool> (double-underscore delimiter) instead of mcp_<server>_<tool>, aligning with the convention used by Claude Code, Codex, and OpenCode. The double-underscore delimiter disambiguates the server/tool boundary even when either component contains underscores (the single-underscore form was ambiguous, which is why is_mcp_tool_parallel_safe already had to track provenance in a side-map). It also unifies native registration with the Anthropic-OAuth wire form (_MCP_TOOL_PREFIX = 'mcp__'), so the single->double promotion that path performed is now a no-op for native tools while still handling legacy replayed names. - tools/mcp_tool.py: add MCP_TOOL_NAME_PREFIX + mcp_prefixed_tool_name() helper; route _convert_mcp_schema, utility schemas, refresh stale-set, and the parallel-safe prefix gate through it - agent/transports/codex_event_projector.py: mirror convention in the deterministic call_id input for MCP server-executed tool calls - tests: update produced-name assertions to the new convention
…nvention TestMcpParallelToolBatch seeded provenance under old-style mcp_<server>_<tool> names, which no longer pass the is_mcp_tool_parallel_safe() prefix gate after the naming change.
teknium1
force-pushed
the
opencode-port/mcp-double-underscore-naming
branch
from
July 5, 2026 13:42
fffbef0 to
de9ed1b
Compare
yingliang-zhang
added a commit
to yingliang-zhang/hermes-agent
that referenced
this pull request
Jul 9, 2026
Models (notably GLM) sometimes emit MCP tool names without the full mcp__<server>__ prefix — e.g. list_memory_projects instead of mcp__basic_memory__list_memory_projects. The existing repair pipeline (case-folding, CamelCase, suffix stripping, fuzzy match at 0.7 cutoff) cannot map the bare suffix back to the full prefixed name, so the call fails with "Unknown tool" and the agent burns 3 retries before aborting with "Model generated invalid tool call". Add two repair paths in repair_tool_call() before the fuzzy-match fallback: 1. Bare suffix (no __ in emitted name): scan registered mcp__ tools and match on the tool-suffix portion. Skip when ambiguous (multiple servers expose the same suffix) to avoid non-deterministic routing. 2. Partial prefix (__ in emitted name but missing mcp__): prepend mcp__ and check for an exact match. The mcp__server__tool naming convention was introduced in NousResearch#52750 (merged 2026-07-05). Prior PRs NousResearch#21696, NousResearch#37100, NousResearch#33359 target the old single-underscore mcp_ format and do not apply to the new convention. 9 new tests in TestMcpPrefixRepair covering both cases, ambiguity guard, and non-MCP tool passthrough. All 38 tests in test_repair_tool_call_name.py pass.
yingliang-zhang
added a commit
to yingliang-zhang/hermes-agent
that referenced
this pull request
Jul 11, 2026
Models (notably GLM) sometimes emit MCP tool names without the full mcp__<server>__ prefix — e.g. list_memory_projects instead of mcp__basic_memory__list_memory_projects. The existing repair pipeline (case-folding, CamelCase, suffix stripping, fuzzy match at 0.7 cutoff) cannot map the bare suffix back to the full prefixed name, so the call fails with "Unknown tool" and the agent burns 3 retries before aborting with "Model generated invalid tool call". Add two repair paths in repair_tool_call() before the fuzzy-match fallback: 1. Bare suffix (no __ in emitted name): scan registered mcp__ tools and match on the tool-suffix portion. Skip when ambiguous (multiple servers expose the same suffix) to avoid non-deterministic routing. 2. Partial prefix (__ in emitted name but missing mcp__): prepend mcp__ and check for an exact match. The mcp__server__tool naming convention was introduced in NousResearch#52750 (merged 2026-07-05). Prior PRs NousResearch#21696, NousResearch#37100, NousResearch#33359 target the old single-underscore mcp_ format and do not apply to the new convention. 9 new tests in TestMcpPrefixRepair covering both cases, ambiguity guard, and non-MCP tool passthrough. All 38 tests in test_repair_tool_call_name.py pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Native MCP tools now register as
mcp__<server>__<tool>(double-underscore delimiter), matching the convention used by Claude Code, Codex, and OpenCode. Previously Hermes usedmcp_<server>_<tool>, whose single-underscore boundary is ambiguous when a server or tool name contains underscores.Ported from anomalyco/opencode#33533.
Why
mcp_my_server_querycan't be reliably split into server vs tool.is_mcp_tool_parallel_safe()already documented this ("that string shape is ambiguous when server names contain underscores") and worked around it with a provenance side-map. Themcp__server__toolform makes the boundary explicit.mcp__prefix; aligning means the model recognizes MCP-sourced tools by name shape.agent/anthropic_adapter.py,_MCP_TOOL_PREFIX = "mcp__", [Bug]: Anthropic OAuth stripsmcp_prefix from Hermes-native MCP tool names, breaking registry lookup #25255) already rewrote native single-underscore names up tomcp__on the wire and reversed them on response. Native registration now producesmcp__directly, so that promotion is a no-op for native tools — while the legacy single→double promotion is retained for tool names replayed from older sessions.Changes
tools/mcp_tool.py: addMCP_TOOL_NAME_PREFIX+mcp_prefixed_tool_name()helper; route_convert_mcp_schema, the resources/prompts utility schemas, the refresh stale-name set, and theis_mcp_tool_parallel_safeprefix gate through it.agent/transports/codex_event_projector.py: mirror the convention in the deterministiccall_idinput for MCP server-executed tool calls (Codex Responses streaming), keeping it consistent with registration names.tests/: update produced-name assertions intest_mcp_tool.py,test_mcp_dynamic_discovery.py,test_toolsets.pyto the new convention.Adaptation notes (vs. the OpenCode PR)
OpenCode's change was a one-line string-template swap (
sanitize(server) + "_" + sanitize(tool)→"mcp__" + ... + "__" + ...). Hermes had the same name constructed in several places plus twostartswith("mcp_")parse sites and a deterministic-call-id derivation, so this introduces a singlemcp_prefixed_tool_name()helper and routes every site through it rather than scattering literals. The Anthropic-OAuth interplay (which has no analog in OpenCode) is left intact — it already handles both forms and now does less work.Backward-compat: tool names are produced fresh per process; this only affects new sessions (no mid-conversation cache invalidation). MCP
tools.include/excludeconfig references the bare tool name, not the prefixed name, so existing configs are unaffected. Themcp-<server>toolset alias (hyphen) is unrelated and unchanged.Validation
test_mcp_tool.pytest_mcp_dynamic_discovery.pytest_toolsets.pytest_anthropic_mcp_prefix_strip.py(OAuth interplay)test_codex_event_projector.pyInfographic