From b9205bb48be2c47fafac16c65727ad53e4c8b98d Mon Sep 17 00:00:00 2001 From: amanning3390 Date: Thu, 23 Jul 2026 18:14:22 -0500 Subject: [PATCH 1/2] fix(acp): allow hosts to skip configured MCP startup --- acp_adapter/entry.py | 13 +++++++----- tests/acp/test_entry.py | 44 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/acp_adapter/entry.py b/acp_adapter/entry.py index 55773536122a..fb9ed95450c3 100644 --- a/acp_adapter/entry.py +++ b/acp_adapter/entry.py @@ -32,6 +32,7 @@ import argparse import asyncio import logging +import os import sys from pathlib import Path from hermes_constants import get_hermes_home @@ -251,11 +252,13 @@ def main(argv: list[str] | None = None) -> None: # MCP servers dynamically via asyncio.to_thread inside the event # loop; that path is unaffected.) Moved from model_tools.py module # scope to avoid freezing the gateway's loop on lazy import (#16856). - try: - from tools.mcp_tool import discover_mcp_tools - discover_mcp_tools() - except Exception: - logger.debug("MCP tool discovery failed at ACP startup", exc_info=True) + # Metadata-only hosts can opt out of unrelated global MCP startup. + if os.environ.get("HERMES_ACP_SKIP_CONFIGURED_MCP", "").strip() != "1": + try: + from tools.mcp_tool import discover_mcp_tools + discover_mcp_tools() + except Exception: + logger.debug("MCP tool discovery failed at ACP startup", exc_info=True) agent = HermesACPAgent() try: diff --git a/tests/acp/test_entry.py b/tests/acp/test_entry.py index 1d881565bd90..7ceee3660624 100644 --- a/tests/acp/test_entry.py +++ b/tests/acp/test_entry.py @@ -23,6 +23,50 @@ async def fake_run_agent(agent, **kwargs): assert calls["kwargs"]["use_unstable_protocol"] is True +def test_main_skips_configured_mcp_discovery_when_requested(monkeypatch): + discovery_calls = [] + + async def fake_run_agent(agent, **kwargs): + pass + + monkeypatch.setattr(entry, "_setup_logging", lambda: None) + monkeypatch.setattr(entry, "_load_env", lambda: None) + monkeypatch.setenv("HERMES_ACP_SKIP_CONFIGURED_MCP", "1") + monkeypatch.setattr( + "tools.mcp_tool.discover_mcp_tools", + lambda: discovery_calls.append(True), + ) + monkeypatch.setattr(acp, "run_agent", fake_run_agent) + + entry.main([]) + + assert discovery_calls == [] + + +@pytest.mark.parametrize("skip_value", [None, "", "0", "false"]) +def test_main_discovers_configured_mcp_when_skip_is_not_enabled(monkeypatch, skip_value): + discovery_calls = [] + + async def fake_run_agent(agent, **kwargs): + pass + + monkeypatch.setattr(entry, "_setup_logging", lambda: None) + monkeypatch.setattr(entry, "_load_env", lambda: None) + if skip_value is None: + monkeypatch.delenv("HERMES_ACP_SKIP_CONFIGURED_MCP", raising=False) + else: + monkeypatch.setenv("HERMES_ACP_SKIP_CONFIGURED_MCP", skip_value) + monkeypatch.setattr( + "tools.mcp_tool.discover_mcp_tools", + lambda: discovery_calls.append(True), + ) + monkeypatch.setattr(acp, "run_agent", fake_run_agent) + + entry.main([]) + + assert discovery_calls == [True] + + def test_main_version_prints_without_starting_server(monkeypatch, capsys): monkeypatch.setattr(entry, "_setup_logging", lambda: (_ for _ in ()).throw(AssertionError("started server"))) From c02801a1da55080456f24f2dc5c91922b0eb2c9a Mon Sep 17 00:00:00 2001 From: amanning3390 Date: Fri, 24 Jul 2026 16:36:26 -0500 Subject: [PATCH 2/2] docs(acp): document HERMES_ACP_SKIP_CONFIGURED_MCP for ACP hosts Adds a 'Host integration' section to the ACP guide and a row in the environment variable reference so the next ACP host implementer does not have to read the adapter source. Documents the exact contract the tests already pin: the value must be exactly `1`; unset/empty/`0`/`false` keep the default behavior; only globally configured config.yaml MCP discovery is skipped, and servers supplied by the ACP session through session/new are still registered. Framed as a host-set process marker rather than user configuration - the same shape as the existing HERMES_KANBAN_TASK entry - so it does not read as a behavioral setting that belongs in config.yaml. Co-authored-by: amanning3390 Signed-off-by: amanning3390 --- .../docs/reference/environment-variables.md | 1 + website/docs/user-guide/features/acp.md | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/website/docs/reference/environment-variables.md b/website/docs/reference/environment-variables.md index dfd122d68904..2b293d60dd7b 100644 --- a/website/docs/reference/environment-variables.md +++ b/website/docs/reference/environment-variables.md @@ -731,6 +731,7 @@ Advanced per-platform knobs for throttling the outbound message batcher. Most us | `HERMES_QUIET` | Suppress non-essential output (`true`/`false`) | | `CODEX_HOME` | When [Codex app-server runtime](../user-guide/features/codex-app-server-runtime) is enabled, override the directory Codex CLI reads its config + auth from (default: `~/.codex`). Hermes' migration writes the managed block to `/config.toml`. | | `HERMES_KANBAN_TASK` | Set by the kanban dispatcher when spawning a worker (task UUID). Workers and the spawned `hermes-tools` MCP subprocess inherit it so kanban tools gate correctly. Don't set manually. | +| `HERMES_ACP_SKIP_CONFIGURED_MCP` | Set by an [ACP host](../user-guide/features/acp#host-integration) on the Hermes subprocess it spawns. `1` skips starting the globally configured `config.yaml` MCP servers before the ACP JSON-RPC loop, for hosts that pass the session's MCP servers through `session/new` themselves. Servers supplied by the ACP session are still registered; any other value keeps the default. Don't set manually. | | `HERMES_API_TIMEOUT` | LLM API call timeout in seconds (default: `1800`) | | `HERMES_API_CALL_STALE_TIMEOUT` | Non-streaming stale-call timeout in seconds (default: `90`). Auto-disabled for local providers when left unset, and may scale upward for very large contexts. Also configurable via `providers..stale_timeout_seconds` or `providers..models..stale_timeout_seconds` in `config.yaml`. | | `HERMES_STREAM_READ_TIMEOUT` | Streaming socket read timeout in seconds (default: `120`). Auto-increased to `HERMES_API_TIMEOUT` for local providers. Increase if local LLMs time out during long code generation. | diff --git a/website/docs/user-guide/features/acp.md b/website/docs/user-guide/features/acp.md index ccdaabc5832d..4983465d2fd0 100644 --- a/website/docs/user-guide/features/acp.md +++ b/website/docs/user-guide/features/acp.md @@ -156,6 +156,28 @@ ACP mode uses the same Hermes configuration as the CLI: Provider resolution uses Hermes' normal runtime resolver, so ACP inherits the currently configured provider and credentials. Hermes also advertises a terminal auth method (`--setup`) for first-run ACP clients; this opens Hermes' interactive model/provider setup. +## Host integration + +These variables are set by an **ACP host process** (an editor or another agent +harness) on the Hermes subprocess it spawns. They are not user configuration — +do not set them by hand in `.env` or `config.yaml`. + +| Variable | Value | Effect | +|----------|-------|--------| +| `HERMES_ACP_SKIP_CONFIGURED_MCP` | `1` | Skip starting the **globally configured** MCP servers from `config.yaml` before the ACP JSON-RPC loop begins. | + +Hermes normally starts every MCP server configured in `config.yaml` before it +enters the ACP JSON-RPC loop. A host that owns MCP itself — passing the +session's servers explicitly through `session/new` — does not need that global +startup, and an unrelated slow or interactive MCP server would otherwise delay +`initialize`. Setting the marker to exactly `1` lets such a host skip it. + +Only the global `config.yaml` discovery is skipped. **MCP servers supplied by +the ACP session through `session/new` are still registered**, so a host loses +no capability it asked for. Any other value (unset, empty, `0`, `false`) keeps +the default behavior, so an unrelated truthy-looking string cannot silently +disable MCP. + ## Session behavior ACP sessions are tracked by the ACP adapter's in-memory session manager while the server is running.