fix(cron): layer enabled MCP servers onto per-job enabled_toolsets (#23997) - #50117
Conversation
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-attribute |
2 |
invalid-argument-type |
1 |
First entries
tests/cron/test_scheduler.py:76: [invalid-argument-type] invalid-argument-type: Argument to `set.__init__` is incorrect: Expected `Iterable[str]`, found `list[str] | None`
tests/run_agent/test_credits_notices_toggle.py:76: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_credits_session_start_micros` on type `AIAgent`
run_agent.py:2984: [unresolved-attribute] unresolved-attribute: Object of type `Self@get_credits_spent_micros` has no attribute `_credits_session_start_micros`
✅ Fixed issues (1):
| Rule | Count |
|---|---|
invalid-assignment |
1 |
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`
Unchanged: 5964 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
|
Duplicate of #32788 — both fix #23997 with the same mechanism: a |
0dd3da8 to
121301b
Compare
A cron job that sets `enabled_toolsets` to a list of *native* toolsets (e.g. `["web", "terminal"]`) silently got ZERO MCP tools, while a job with no per-job list got every globally-enabled MCP server. `_resolve_cron_enabled_ toolsets` returned the per-job list verbatim, bypassing the MCP-merge that the platform-fallback branch performs via `_get_platform_tools`. So `discover_mcp_tools()` registered the MCP tools into the registry, but `get_tool_definitions(enabled_toolsets=...)` kept only the named native toolsets — the agent then rejected every `mcp_*` call as "Unknown tool". (R2 of #23997.) Fix: `_merge_mcp_into_per_job_toolsets` layers MCP membership onto a per-job allowlist with the SAME semantics as `_get_platform_tools`: * `no_mcp` sentinel present -> no MCP servers (sentinel stripped) * one or more MCP server names already listed -> treat as an allowlist * otherwise -> union in every globally-enabled MCP server To avoid duplicating the "which MCP servers are enabled" computation (it already existed inline in `_get_platform_tools`), this extracts a shared `enabled_mcp_server_names(config)` helper in `hermes_cli.tools_config` and has BOTH the gateway/CLI platform resolver and the cron per-job resolver call it — so every path agrees on MCP membership (extend, don't duplicate). Note: the issue's *headline* — bare MCP server names rejected, registry never includes them — was already fixed on main (commits c10fea8 + 0491834, both before the issue was filed). This PR closes the remaining cron-specific gap (R2). The `server:*` / `mcp:server` alias-notation rejection (R1) and the quiet-mode silent-drop (R3) are tracked separately. Salvaged from #32788 by sherman-yang (credited below). Reworked to reuse the shared `enabled_mcp_server_names` helper instead of re-implementing the MCP membership set in cron/scheduler.py. Fixes #23997 Co-authored-by: sherman-yang <58446328+sherman-yang@users.noreply.github.com>
121301b to
5bd3dae
Compare
A cron job with a native-only enabled_toolsets (e.g. ["file","skills","terminal"]) silently stripped every MCP server's tools from the scheduled run — the allowlist named no MCP servers, so scheduled jobs ran with builtin tools only while interactive runs had the full MCP set. (Upstream NousResearch/hermes-agent NousResearch#23997; fixed there by NousResearch#50117.) _resolve_cron_enabled_toolsets now runs a per-job allowlist through _merge_mcp_into_per_job_toolsets, which unions globally-enabled MCP server names in (matching the _get_platform_tools default), honors a `no_mcp` opt-out sentinel, and leaves a list that already names an MCP server as an explicit allowlist. enabled_mcp_server_names() is extracted in tools_config.py and shared so the platform-default and cron paths cannot diverge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on-mcp-per-job fix(cron): layer enabled MCP servers onto per-job enabled_toolsets (NousResearch#23997)
…on-mcp-per-job fix(cron): layer enabled MCP servers onto per-job enabled_toolsets (NousResearch#23997)
…on-mcp-per-job fix(cron): layer enabled MCP servers onto per-job enabled_toolsets (NousResearch#23997)
…on-mcp-per-job fix(cron): layer enabled MCP servers onto per-job enabled_toolsets (NousResearch#23997)
…on-mcp-per-job fix(cron): layer enabled MCP servers onto per-job enabled_toolsets (NousResearch#23997)
Problem
A cron job that set
enabled_toolsetsto native toolsets (e.g.["web", "terminal"]) silently got zero MCP tools, while a job with no per-job list got every globally-enabled MCP server._resolve_cron_enabled_toolsetsreturned the per-job list verbatim, bypassing the MCP-merge that the platform-fallback branch does via_get_platform_tools.discover_mcp_tools()registered the MCP tools into the registry, butget_tool_definitions(enabled_toolsets=...)kept only the named native toolsets — the agent rejected everymcp_*call as "Unknown tool". (R2 of #23997)Fix
_merge_mcp_into_per_job_toolsetslayers MCP membership onto a per-job allowlist with the same semantics as_get_platform_tools:no_mcpsentinel → no MCP serversTo avoid duplicating the "which MCP servers are enabled" computation (the original PR re-implemented it), this extracts a shared
enabled_mcp_server_names(config)helper inhermes_cli.tools_configand has BOTH the gateway/CLI platform resolver and the cron per-job resolver call it — so every path agrees on MCP membership (extend, don't duplicate).Scope — what was already fixed on main
The issue's headline (bare MCP server names rejected, registry never includes them in cron) was already fixed on main before the issue was filed (commits
c10fea8d2server-alias +04918345ediscover-MCP-before-cron-agent). This PR closes the remaining cron-specific gap (R2). Two smaller residuals are tracked separately, not in scope here:server:*/mcp:serveralias notation still rejected (→ fix(tools): log unknown toolsets in quiet mode #24104).quiet_mode(→ fix(tools): log unknown toolsets in quiet mode #24104's warning).Salvage / credit
Salvaged from #32788 by @sherman-yang (authorship preserved). Reworked to reuse the shared
enabled_mcp_server_nameshelper instead of re-implementing the MCP membership set incron/scheduler.py(the DRY concern flagged in review).Tests
tests/cron/test_scheduler.py— per-job native allowlist unions in enabled MCP;no_mcpopts out; explicit MCP name acts as allowlist. Fulltests/cron/: 517 passed;tests/hermes_cli/test_tools_config.py: 97 passed (the gateway path still resolves MCP identically after the helper extraction).Closes
Fixes #23997
Review-driven changes (/hermes-pr-review, 3 reviewers — verdict: Approve/ship-ready, no Critical)
test_resolver_empty_per_job_falls_through_to_platformnow stubs_get_platform_toolsand asserts the platform branch is actually taken withplatform="cron"and its result returned — was previously a near-tautology (result is None or isinstance(result, list)).enabled_mcp_server_namesdocstring now notes a server is enabled unless explicitly falsey (false/0/no/off via_parse_enabled_flag); a missing/unrecognized flag is treated as enabled — matching prior gateway behavior.Reviewers confirmed: the
enabled_mcp_server_namesextraction is behavior-identical for the gateway (verbatim move of the inline computation, same_parse_enabled_flag); the 3 merge branches are correct with proper allowlist exclusion of unnamed enabled servers; merge runs on the per-job branch only (no double-merge with the platform fallback); the lazy import poses no circular-import risk (cron already imports from hermes_cli widely); disabled-toolset subtraction reliably runs last so a disabled MCP server can't sneak back. Both files pass in isolation (cron 517, tools_config 97).Note: tests assert the resolved toolset-name list (the contract of these pure resolver functions), not end-to-end tool registration — consistent with the existing resolver tests.