feat(mcp): add tool_search for on-demand MCP schema fetching - #27257
Closed
DaveDev42 wants to merge 2 commits into
Closed
feat(mcp): add tool_search for on-demand MCP schema fetching#27257DaveDev42 wants to merge 2 commits into
DaveDev42 wants to merge 2 commits into
Conversation
Parks MCP tool schemas in a deferred pool and exposes a single `tool_search`
tool that fetches full schemas on demand. Mirrors Anthropic Claude Code's
MCP Tool Search (v2.1.7) wire format so models trained on that data
recognise the `<functions><function>{json}</function></functions>` envelope
natively.
Validated locally with 234 MCP tools across 6 servers (Apple, GitHub,
Google Workspace, iCloud, Readwise, Slack): full self.tools array drops
from 234 → 0 MCP entries (~273 KB schema savings), with tool_search
auto-injected and promoted tools (those fetched via select:) staying
callable on subsequent turns.
Activation:
mcp:
tool_search:
enabled: true # opt-in
always_active: true # park even when below threshold
threshold_chars: 8000 # auto-activate above this MCP schema size
Query forms supported by tool_search:
select:mcp_github_search_code,mcp_slack_conversations_history
+github search code (require 'github', rank by remaining terms)
notebook jupyter (keyword search, top max_results matches)
System prompt now carries a compact deferred-tool roster (name + 1-line
summary) grouped by toolset, so the model can pick names without paying
the full schema cost upfront.
This comment was marked as spam.
This comment was marked as spam.
Two live-test fixes: 1. Registry's dispatch() calls handler(args: dict, **kw); the previous _handle_tool_search took (query, max_results=5) directly, so the first real call from a model blew up with `unexpected keyword argument 'task_id'`. Switched to the canonical (args, **kw) shape. 2. _apply_mcp_tool_search now logger.info()s on every activation, not only when quiet_mode=False. Oneshot mode (hermes -z) runs with quiet_mode=True so previously there was no agent.log evidence the pass ever fired. Validated with `hermes -z` runs that force MCP usage: - mcp_github_search_code returned a real GitHub API response - mcp_slack_channels_list returned actual channel names both via tool_search → promote → next-turn dispatch.
Author
|
Live-tested end-to-end with Test 1 — GitHub MCP Test 2 — Slack MCP Both go through the full path: model picks an MCP tool by name from the deferred-tool roster in the system prompt → calls Two follow-up fixes in
Test-plan updates:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
tool_searchtool that fetches them on demand.<functions><function>{json}</function></functions>— so models trained on that data recognise it natively, no per-provider prompting required.self.toolsarray drops from 234 → 0 MCP entries (~273 KB schema savings).tool_searchis auto-injected; promoted tools (those the model has fetched viaselect:) stay callable on subsequent turns via a newpool.promoted_names()set that survivespool.clear()/repopulate cycles.Activation
Opt-in via
~/.hermes/config.yaml:Default
enabled: falsepreserves current behaviour for existing users.Query forms
tool_searchaccepts:select:mcp_github_search_code,mcp_slack_conversations_history— exact fetch+github search code— requiregithubin name, rank by remaining termsnotebook jupyter— keyword search, topmax_resultsmatchesResponse is a
<functions>block carrying the full JSON schema(s); selected tools are promoted back intoself.tools/valid_tool_nameson the nextget_tool_definitions()pass so subsequent calls dispatch normally.Files
tools/mcp_tool_search.py(new, ~200 lines) — schema, query parser, scorer, response builder, registry self-registration withtoolset=hermes-cli,check_fngated on pool sizetools/deferred_pool.py(new, ~100 lines) — thread-safeDeferredToolPoolwithput/remove/promoted_names/clear/generation counter (long-lived Gateway process where MCP refresh can race agent threads)model_tools.py—_apply_mcp_tool_search()pass runs afterregistry.get_definitions(); cache key extended withdeferred_pool.generationso promotions invalidate;tool_searchre-injected from registry when pool is non-empty (itscheck_fnwould otherwise hide it at registry-pass time)run_agent.py— system-prompt block listing deferred tools grouped by toolset; post-dispatch hook in both parallel and sequential paths rebuildsself.tools/self.valid_tool_namesand clears_cached_system_promptafter atool_searchcalltoolsets.py—tool_searchadded to_HERMES_CORE_TOOLSso it's part of thehermes-clicompositeCloses
Partially addresses #6839 — MCP-server tools (typically the biggest schema source) only. Built-in toolsets (terminal, file, browser, etc.) are out of scope for this PR; the issue's broader "all toolsets" lazy-loading could layer on top using the same deferred pool.
Test plan
enabled_toolsets=['hermes-cli', 'mcp-*']:tool_search, 28 = non-MCP), pool size = 234, MCP count in self.tools = 0 ✅tool_searchinvoked withselect:mcp_github_search_codereturns valid<functions>block ✅mcp_github_search_codecallable,tool_searchstill present, pool = 233 ✅no_mcpplatform (the test author's telegram config hasno_mcpsentinel; needs a fresh test withplatform_toolsets.<plat>that includes MCP server keys)enabled: false(default) — verify zero-diff vs current mainpool.generationincrement under contention