hermes - ✅(Solved) Fix [Bug]: Anthropic OAuth strips `mcp_` prefix from Hermes-native MCP tool names, breaking registry lookup [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#25255Fetched 2026-05-14 03:47:49
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Author
Participants
Timeline (top)
labeled ×5cross-referenced ×1

When Hermes is running on the Anthropic OAuth path (provider: anthropic with a Claude Code subscription / OAuth credentials) AND has a Hermes-native MCP server configured under mcp_servers: in ~/.hermes/config.yaml, calls to tools provided by that MCP server intermittently fail with Tool '<name>' does not exist even though the tool IS in the registry under exactly that name.

The error message even lists the correct tool in its "Available tools:" suggestion list — but with a leading mcp_ that the caller's name lacks.

Error Message

The error message even lists the correct tool in its "Available tools:" suggestion list — but with a leading mcp_ that the caller's name lacks.

Root Cause

agent/transports/anthropic.py line ~108:

if strip_tool_prefix and name.startswith(_MCP_PREFIX):
    name = name[len(_MCP_PREFIX):]

This unconditionally strips mcp_ from the tool name in Anthropic OAuth responses. strip_tool_prefix is set to True when self._is_anthropic_oauth is true (see run_agent.py:11619, 11649, 12869, 14379).

The strip is correct for OAuth-injected MCP tools (where Hermes adds the mcp_ prefix going out and must remove it coming back), but wrong for Hermes-native MCP server tools, which are registered in tools/registry.py under their full mcp_<server>_<tool> name (see tools/mcp_tool.py:2701prefixed_name = f"mcp_{safe_server_name}_{safe_tool_name}").

After stripping, lookup against the registry fails because the registry contains mcp_composio_COMPOSIO_SEARCH_TOOLS, not composio_COMPOSIO_SEARCH_TOOLS.

Fix Action

Fix / Workaround

I have a local patch with this logic + 5 regression tests covering:

  • Native MCP tool (prefix preserved)
  • OAuth-injected tool (prefix stripped — legacy behaviour)
  • Non-MCP tool (untouched)
  • Unknown mcp_* tool (still stripped — legacy fail-downstream)
  • strip_tool_prefix=False (never touched)

PR fix notes

PR #25270: fix(agent): only strip mcp_ prefix for OAuth-injected tools

Description (problem / solution / changelog)

Fix for #25255. When using Anthropic OAuth, normalize_response unconditionally strips mcp_ prefix from ALL tool names, breaking native MCP server tools. Fix: check tool registry before stripping. 7 new tests, 150 existing tests pass.

Changed files

  • agent/transports/anthropic.py (modified, +11/-1)
  • tests/agent/test_anthropic_mcp_prefix_strip.py (added, +185/-0)

Code Example

mcp_servers:
     composio:
       url: "https://connect.composio.dev/mcp"
       headers:
         x-consumer-api-key: "ck_..."

---

Tool 'composio_COMPOSIO_SEARCH_TOOLS' does not exist. Available tools: ...
   mcp_composio_COMPOSIO_GET_TOOL_SCHEMAS, mcp_composio_COMPOSIO_MANAGE_CONNECTIONS,
   mcp_composio_COMPOSIO_MULTI_EXECUTE_TOOL, mcp_composio_COMPOSIO_REMOTE_BASH_TOOL, ...

---

if strip_tool_prefix and name.startswith(_MCP_PREFIX):
    name = name[len(_MCP_PREFIX):]

---

if strip_tool_prefix and name.startswith(_MCP_PREFIX):
    stripped = name[len(_MCP_PREFIX):]
    from tools.registry import registry as _registry
    registered = set(_registry.get_all_tool_names())
    if stripped in registered:
        name = stripped
    elif not registered:
        name = stripped  # legacy: registry not yet populated
    elif name not in registered:
        name = stripped  # legacy: neither form known, fail downstream
    # else: prefixed form registered, stripped form not — leave alone (native MCP)
RAW_BUFFERClick to expand / collapse

Summary

When Hermes is running on the Anthropic OAuth path (provider: anthropic with a Claude Code subscription / OAuth credentials) AND has a Hermes-native MCP server configured under mcp_servers: in ~/.hermes/config.yaml, calls to tools provided by that MCP server intermittently fail with Tool '<name>' does not exist even though the tool IS in the registry under exactly that name.

The error message even lists the correct tool in its "Available tools:" suggestion list — but with a leading mcp_ that the caller's name lacks.

Reproduction

  1. Configure any Hermes-native MCP server in ~/.hermes/config.yaml:
    mcp_servers:
      composio:
        url: "https://connect.composio.dev/mcp"
        headers:
          x-consumer-api-key: "ck_..."
  2. Use provider: anthropic with an OAuth credential source (Claude Max subscription, ~/.claude/.credentials.json or ANTHROPIC_OAUTH_TOKEN).
  3. Ask the agent to use a tool from that server, e.g. mcp_composio_COMPOSIO_SEARCH_TOOLS.
  4. First call typically succeeds. Subsequent calls in the same conversation fail with:
    Tool 'composio_COMPOSIO_SEARCH_TOOLS' does not exist. Available tools: ...
    mcp_composio_COMPOSIO_GET_TOOL_SCHEMAS, mcp_composio_COMPOSIO_MANAGE_CONNECTIONS,
    mcp_composio_COMPOSIO_MULTI_EXECUTE_TOOL, mcp_composio_COMPOSIO_REMOTE_BASH_TOOL, ...

Root cause

agent/transports/anthropic.py line ~108:

if strip_tool_prefix and name.startswith(_MCP_PREFIX):
    name = name[len(_MCP_PREFIX):]

This unconditionally strips mcp_ from the tool name in Anthropic OAuth responses. strip_tool_prefix is set to True when self._is_anthropic_oauth is true (see run_agent.py:11619, 11649, 12869, 14379).

The strip is correct for OAuth-injected MCP tools (where Hermes adds the mcp_ prefix going out and must remove it coming back), but wrong for Hermes-native MCP server tools, which are registered in tools/registry.py under their full mcp_<server>_<tool> name (see tools/mcp_tool.py:2701prefixed_name = f"mcp_{safe_server_name}_{safe_tool_name}").

After stripping, lookup against the registry fails because the registry contains mcp_composio_COMPOSIO_SEARCH_TOOLS, not composio_COMPOSIO_SEARCH_TOOLS.

Why it's intermittent (not consistent)

The first call in a session often succeeds — possibly because of differences in how the initial tool list is presented vs. subsequent tool-use response normalization. Once a session has had at least one normalize_response pass on a Hermes-native MCP tool, every subsequent call fails.

This makes the bug especially nasty for unattended workloads (cron jobs, scheduled routines) where the first call works but the next 11 fail silently.

Related

  • #16817 (closed): PascalCase variant of the same root cause (mcp_TerminalTerminal doesn't match registered terminal). Same file, same line, different surface.
  • #17681 (open): Switches OAuth path to mcp__ double-underscore marker. Necessary, but doesn't fix the collision between OAuth-injected and natively-registered MCP tools.

Proposed fix

Make the strip registry-aware: only strip when the stripped form matches a registered tool name; otherwise leave the name alone.

if strip_tool_prefix and name.startswith(_MCP_PREFIX):
    stripped = name[len(_MCP_PREFIX):]
    from tools.registry import registry as _registry
    registered = set(_registry.get_all_tool_names())
    if stripped in registered:
        name = stripped
    elif not registered:
        name = stripped  # legacy: registry not yet populated
    elif name not in registered:
        name = stripped  # legacy: neither form known, fail downstream
    # else: prefixed form registered, stripped form not — leave alone (native MCP)

I have a local patch with this logic + 5 regression tests covering:

  • Native MCP tool (prefix preserved)
  • OAuth-injected tool (prefix stripped — legacy behaviour)
  • Non-MCP tool (untouched)
  • Unknown mcp_* tool (still stripped — legacy fail-downstream)
  • strip_tool_prefix=False (never touched)

All 5 pass. Existing tests/agent/transports/ suite (182 tests) and tests/agent/test_anthropic_adapter.py (excluding 3 pre-existing OAuth-token failures unrelated to this change) continue to pass.

Platform

macOS 14 / Python 3.11.15 / hermes-agent main @ 1979ef580.

Happy to send the PR if useful.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix [Bug]: Anthropic OAuth strips `mcp_` prefix from Hermes-native MCP tool names, breaking registry lookup [1 pull requests, 1 participants]