feat(mcp): smart loading — connect_strategy, tool budget, per-session scoping, meta-tools - #66481
prismatic7 wants to merge 4 commits into
Conversation
Three new per-task fields on delegate_task batch items: - **toolsets** (string[]): restrict which tools a subagent loads. Previously hardcoded to None (inherit parent), now the model can narrow toolsets per task. Intersection with parent toolsets and blocked-tool stripping are handled by the existing validation chain. - **persona** (string): injected into the child's system prompt as a YOUR ROLE block to specialize behavior (e.g. 'web researcher', 'senior engineer') without cramming instructions into context. - **timeout** (number): per-task wall-clock cap in seconds. Overrides the global delegation.child_timeout_seconds for that subagent. None = inherit the global default (no timeout by default). All three are backward-compatible: single-task mode is unchanged, and batch tasks without the new fields default to None with existing inheritance behaviour preserved.
… scoping, meta-tools Four levers to control MCP server loading, reducing startup time and context-window bloat for users with many MCP servers. Lever 1 — connect_strategy (per-server): startup (default): connect at Hermes startup, register all tools lazy: register tool schemas but defer subprocess spawn until first call on_demand: invisible until explicitly loaded via /mcp load or mcp_load_server Lever 2 — mcp_tool_budget (global): Caps the number of MCP tool schemas in the LLM prompt. LRU eviction drops least-used servers' tools when budget exceeded. Meta-tools (mcp_list_servers, mcp_load_server) always survive eviction. Default: 0 (unlimited, backward compatible). Lever 3 — /mcp slash commands: /mcp list → show all servers, status, tool counts, strategy /mcp enable <name> → activate a lazy/on_demand server now /mcp disable <name> → mark an on_demand server inactive for this session /mcp load <name> → same as enable Lever 4 — meta-tools (always available): mcp_list_servers — returns all configured servers with status/strategy mcp_load_server — activates a lazy or on_demand server mid-session Bug fixes discovered during audit: - _activate_lazy_server removed from _lazy_servers before connecting, leaving failed connections in limbo (not retryable, not connected) - /mcp disable called _clear_session_loaded_servers() which cleared ALL on_demand servers, not just the named one - Lazy server stub tools had no toolset alias registered, making them invisible to the platform resolver - Meta-tools registered into toolset 'mcp' which doesn't exist in any platform composite — tools were invisible to the LLM Refs: NousResearch#66473 (umbrella), NousResearch#63626 (lazy MCP), NousResearch#6839 (tool schema deferral), NousResearch#45955 (per-session scoping)
teknium1
left a comment
There was a problem hiding this comment.
Thanks for tackling the MCP startup and schema-footprint problem. This needs revision before it can safely land.
Problems
tools/mcp_tool.py:447-501calls the state per-session, but_session_loaded_serversis one process-global set keyed only by server name._make_check_fn()then uses it for visibility, so one session's load/disable operation changes every session's MCP surface.tools/mcp_tool.py:5188activates the lazy server, while:520-522removes it from_lazy_servers; themcp__activate_*stub registered at:5198-5206is never deregistered. A later stub call therefore fails instead of being replaced as the docstring promises.tools/mcp_tool.py:6271-6275says the loaded tools are now available but does not refresh the active agent's snapshot. The existing helper documents thatagent.toolsis snapshotted once and that refresh callers own the cache contract (tools/mcp_tool.py:5524-5556).hermes_cli/commands.py:217-219makes/mcpgateway-visible, butgateway/run.pyhas no matching dispatcher branch.
Suggested changes
- Key visibility by real session identity and add multi-session isolation tests.
- Atomically replace/deregister the lazy stub after activation.
- Define a cache-safe next-turn activation contract or add an explicitly consented refresh path.
- Make
/mcpCLI-only or implement its gateway/TUI routes and tests.
Automated hermes-sweeper review.
7001926 to
8b5d110
Compare
|
All resolved. Thanks for the feedback! |
31e9c8a to
7a00d5d
Compare
|
Strong +1 on We run an
So this lever isn't only a startup-time / context-footprint optimization for large fleets — it's arguably the right default handling for any MCP server whose upstream is on-demand/serverless. Thanks for building it; happy to test a revised revision against our (reliably-cold) setup if that's useful for the |
|
Thanks for the thorough audit and the four-lever build here — real effort went into this, and the bug fixes you surfaced along the way (lazy-server limbo, Closing this without merging, because the design has diverged from where we're taking MCP loading: Context bloat (Levers 2, 3, 4 — tool budget, Startup time (Lever 1 — I've re-scoped the umbrella issue #66473 to track that gateway-attached-persistent-MCP design. Your commits keep their authorship in your branch. If you want to take a run at the gateway-attached approach, #66473 is the place — happy to have you on it. |
Summary
Four levers to control MCP server loading, reducing startup time and context-window bloat for users with many MCP servers. Closes umbrella issue #66473.
Changes
Lever 1:
connect_strategy(per-server)Three modes —
startup(default, backward compatible),lazy(defer subprocess spawn until first call),on_demand(invisible until explicitly loaded).Lever 2:
mcp_tool_budget(global)Caps MCP tool schemas in the LLM prompt. LRU eviction drops least-used servers' tools. Meta-tools always survive. Default:
0(unlimited).Lever 3:
/mcpslash commands/mcp list,/mcp enable <name>,/mcp disable <name>,/mcp load <name>— per-session server management. CLI-only (no gateway dispatcher yet).Lever 4: Meta-tools (always available)
mcp_list_serversandmcp_load_server— agent-driven discovery and activation.Bug fixes (discovered during audit)
_activate_lazy_serverremoved from_lazy_serversbefore connecting; failed connections left unrecoverable/mcp disablenuked everything — called_clear_session_loaded_servers()which cleared ALL on_demand servers, not just the named one"mcp"which doesn't exist in any platform compositeMaintainer review fixes (teknium1's hermes-sweeper)
Session isolation —
_session_loaded_serverschanged from a process-globalsettoDict[session_id, set], keyed by acontextvars.ContextVar. Each asyncio task gets its own session identity, so one session's load/disable no longer affects every session.Lazy stub deregistration — after
_activate_lazy_server()succeeds, themcp__activate_*stub tool is deregistered viaregistry.deregister(). A subsequent stub call no longer fails silently.Agent tool refresh — added
_refresh_active_agent_tools()helper that callsrefresh_agent_mcp_tools()on the active agent after both lazy activation and on_demand load. Best-effort: if no active agent is found, tools are picked up on the next agent build (next-turn activation contract)./mcpCLI-only guard — addedcli_only=Trueto the/mcpCommandDef. Prevents gateway/TUI from trying to dispatch an unimplemented route.Files modified
tools/mcp_tool.pyconnect_strategyparsing, lazy/on_demand tracking, meta-tools, session isolation, stub deregistration, agent refresh (+54/-14)model_tools.pymcp_tool_budgetenforcement, LRU tracking (+64)toolsets.py_HERMES_CORE_TOOLS(+4/-1)hermes_cli/config.pymcp_tool_budgetdefault (+6)hermes_cli/commands.py/mcpslash command registration, CLI-only guard (+4)cli.py/mcpcommand handler (+104)Backward Compatibility
connect_strategydefaults tostartup— existing configs unchangedmcp_tool_budgetdefaults to0(unlimited) — existing behavior preservedenabled: falsestill works as beforeidle_timeout_seconds/max_lifetime_secondsstill work for recycling connected servers""— sessions that don't set it share a global namespace (backward compatible)Related Issues
--toolsetsand add per-session tool scoping (_make_agenthardcodesenabled_toolsets) #45955 (per-session scoping)