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
- 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_..."
- Use
provider: anthropic with an OAuth credential source (Claude Max subscription, ~/.claude/.credentials.json or ANTHROPIC_OAUTH_TOKEN).
- Ask the agent to use a tool from that server, e.g.
mcp_composio_COMPOSIO_SEARCH_TOOLS.
- 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:2701 — prefixed_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
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.
Summary
When Hermes is running on the Anthropic OAuth path (
provider: anthropicwith a Claude Code subscription / OAuth credentials) AND has a Hermes-native MCP server configured undermcp_servers:in~/.hermes/config.yaml, calls to tools provided by that MCP server intermittently fail withTool '<name>' does not existeven 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
~/.hermes/config.yaml:provider: anthropicwith an OAuth credential source (Claude Max subscription,~/.claude/.credentials.jsonorANTHROPIC_OAUTH_TOKEN).mcp_composio_COMPOSIO_SEARCH_TOOLS.Root cause
agent/transports/anthropic.pyline ~108:This unconditionally strips
mcp_from the tool name in Anthropic OAuth responses.strip_tool_prefixis set toTruewhenself._is_anthropic_oauthis true (seerun_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 intools/registry.pyunder their fullmcp_<server>_<tool>name (seetools/mcp_tool.py:2701—prefixed_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, notcomposio_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
mcp_Terminal→Terminaldoesn't match registeredterminal). Same file, same line, different surface.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.
I have a local patch with this logic + 5 regression tests covering:
mcp_*tool (still stripped — legacy fail-downstream)strip_tool_prefix=False(never touched)All 5 pass. Existing
tests/agent/transports/suite (182 tests) andtests/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.