Skip to content

feat(mcp): adopt mcp__server__tool naming convention (port from opencode#33533) - #52750

Merged
teknium1 merged 2 commits into
mainfrom
opencode-port/mcp-double-underscore-naming
Jul 5, 2026
Merged

feat(mcp): adopt mcp__server__tool naming convention (port from opencode#33533)#52750
teknium1 merged 2 commits into
mainfrom
opencode-port/mcp-double-underscore-naming

Conversation

@teknium1

@teknium1 teknium1 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

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 used mcp_<server>_<tool>, whose single-underscore boundary is ambiguous when a server or tool name contains underscores.

Ported from anomalyco/opencode#33533.

Why

  • Disambiguates the server/tool boundary. With single underscores, mcp_my_server_query can'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. The mcp__server__tool form makes the boundary explicit.
  • Matches what models are trained on. Claude Code and Codex both use the mcp__ prefix; aligning means the model recognizes MCP-sourced tools by name shape.
  • Unifies an existing split-brain. The Anthropic-OAuth wire path (agent/anthropic_adapter.py, _MCP_TOOL_PREFIX = "mcp__", [Bug]: Anthropic OAuth strips mcp_ prefix from Hermes-native MCP tool names, breaking registry lookup #25255) already rewrote native single-underscore names up to mcp__ on the wire and reversed them on response. Native registration now produces mcp__ 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: add MCP_TOOL_NAME_PREFIX + mcp_prefixed_tool_name() helper; route _convert_mcp_schema, the resources/prompts utility schemas, the refresh stale-name set, and the is_mcp_tool_parallel_safe prefix gate through it.
  • agent/transports/codex_event_projector.py: mirror the convention in the deterministic call_id input for MCP server-executed tool calls (Codex Responses streaming), keeping it consistent with registration names.
  • tests/: update produced-name assertions in test_mcp_tool.py, test_mcp_dynamic_discovery.py, test_toolsets.py to 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 two startswith("mcp_") parse sites and a deterministic-call-id derivation, so this introduces a single mcp_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/exclude config references the bare tool name, not the prefixed name, so existing configs are unaffected. The mcp-<server> toolset alias (hyphen) is unrelated and unchanged.

Validation

Result
test_mcp_tool.py 200 passed
test_mcp_dynamic_discovery.py 10 passed
test_toolsets.py 27 passed
test_anthropic_mcp_prefix_strip.py (OAuth interplay) 12 passed
test_codex_event_projector.py 23 passed
E2E (real imports: convention, underscore/dot/hyphen servers, utility schemas, parallel-safe gate, OAuth prefix) 8/8 assertions

Infographic

mcp-double-underscore-naming

@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: opencode-port/mcp-double-underscore-naming vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 11349 on HEAD, 11349 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 5985 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/feature New feature or request tool/mcp MCP client and OAuth comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have labels Jun 26, 2026
teknium1 added 2 commits July 5, 2026 06:39
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
teknium1 force-pushed the opencode-port/mcp-double-underscore-naming branch from fffbef0 to de9ed1b Compare July 5, 2026 13:42
@teknium1
teknium1 merged commit 3d02761 into main Jul 5, 2026
31 checks passed
@teknium1
teknium1 deleted the opencode-port/mcp-double-underscore-naming branch July 5, 2026 20:40
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have tool/mcp MCP client and OAuth type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants