Skip to content

[Bug]: Anthropic OAuth strips mcp_ prefix from Hermes-native MCP tool names, breaking registry lookup #25255

@kotafarms

Description

@kotafarms

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

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existscomp/agentCore agent loop, run_agent.py, prompt builderprovider/anthropicAnthropic native Messages APItool/mcpMCP client and OAuthtype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions