Skip to content

HERMES_TOOLS_SUBSET doesn't filter MCP-server-provided tools #86

@PowerCreek

Description

@PowerCreek

Follow-up to #75 (HERMES_TOOLS_SUBSET) + #82/#83/#85 (mcp_serve autowire).

Bug

With autowire merged, fresh workers boot with 52 tools (built-ins + 36 MCP from hermes-internal). The model hits the tool-paralysis ceiling — emits text-with-affirmation pattern instead of tool_call for verticals that should be one-shot tool invocations.

HERMES_TOOLS_SUBSET (#75) was supposed to let operators narrow the surface per worker, but it only filters BUILT-IN tools at agent_init.py:838. MCP-discovered tools sneak through three ways:

  1. The feat(env): HERMES_TOOLS_SUBSET — operator-side tool surface narrowing (closes #74) #75 filter runs after get_tool_definitions() which includes MCP tools — so HERMES_TOOLS_SUBSET=foo correctly drops MCP tools at that filter. BUT the user-facing UX gap: subset values must match the prefixed names (mcp_hermes-internal_grafted_context_fetch), which most operators won't know.
  2. cli.py:9790 (/reload-mcp + auto-reload on config change) re-assigns agent.tools = get_tool_definitions(...) WITHOUT applying the feat(env): HERMES_TOOLS_SUBSET — operator-side tool surface narrowing (closes #74) #75 filter — regression: any post-init MCP server reload nukes the subset.
  3. Even if filtering works at agent.tools assembly time, registry still carries the unwanted MCP tools, costing schema-conversion + collision-check work per tool per boot.

Fix

Apply the HERMES_TOOLS_SUBSET allow-list at MCP tool registration time in tools/mcp_tool.py::_register_server_tools. Tools that fail the filter never enter the registry, so:

Scope:

  • Extract a shared helper hermes_cli/tool_subset.py with get_subset_allow() / is_allowed()
  • Call it inside the per-tool loop AND the utility-tools loop in _register_server_tools
  • Subset matches against the prefixed name (mcp_<server>_<tool>) — exact match, predictable behavior. Fuzzy/unprefixed matching is a separate feature request.

Acceptance

  • Worker boot with HERMES_TOOLS_SUBSET=mcp_hermes-internal_grafted_context_fetch,doc_view → registry contains exactly those 2 tools (no other MCP tools registered).
  • Empty/unset HERMES_TOOLS_SUBSET → all MCP tools register (baseline preserved).
  • /reload-mcp post-boot → subset still respected.
  • Worker can emit tool_call against grafted_context_fetch (out of tool-paralysis range).

Severity

Final blocker for poly-explorer end-to-end. After this lands, the cascade closes — workers can be configured with a 5-tool surface that includes their specific MCP needs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    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