Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions acp_adapter/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,12 @@
)
from acp_adapter.permissions import make_approval_callback
from acp_adapter.provenance import session_provenance_meta
from acp_adapter.session import SessionManager, SessionState, _expand_acp_enabled_toolsets
from acp_adapter.session import (
SessionManager,
SessionState,
_expand_acp_enabled_toolsets,
resolve_acp_tool_policy,
)
from acp_adapter.tools import build_tool_complete, build_tool_start
from agent.context_compressor import (
COMPRESSED_SUMMARY_METADATA_KEY,
Expand Down Expand Up @@ -798,10 +803,35 @@ async def _register_session_mcp_servers(
state: SessionState,
mcp_servers: list[McpServerStdio | McpServerHttp | McpServerSse] | None,
) -> None:
"""Register ACP-provided MCP servers and refresh the agent tool surface."""
"""Register ACP-provided MCP servers and refresh the agent tool surface.

In ``acp.tool_policy: profile`` mode the host may not expand the
selected profile's capability set. Client-provided MCP servers are
ignored; profile-configured MCP remains whatever session construction
already resolved from Hermes config.
"""
if not mcp_servers:
return

try:
from hermes_cli.config import load_config

if resolve_acp_tool_policy(load_config()) == "profile":
logger.info(
"Session %s: ignoring %d host MCP server(s) under "
"acp.tool_policy=profile (profile owns tool policy)",
state.session_id,
len(mcp_servers),
)
return
except Exception:
logger.debug(
"Session %s: could not resolve ACP tool policy before host MCP "
"registration; continuing with compatibility behaviour",
state.session_id,
exc_info=True,
)

try:
from tools.mcp_tool import register_mcp_servers

Expand Down
95 changes: 85 additions & 10 deletions acp_adapter/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,90 @@ def _expand_acp_enabled_toolsets(
return expanded


# Compatibility values keep the historical coding-focused ACP toolset.
_ACP_COMPAT_TOOL_POLICIES = frozenset({"", "hermes-acp", "compat", "default"})
_ACP_PROFILE_TOOL_POLICY = "profile"


def resolve_acp_tool_policy(config: dict | None) -> str:
"""Return the effective ACP tool policy for *config*.

``hermes-acp`` (default) preserves the coding-focused ACP toolset used by
editor hosts. ``profile`` resolves the selected Hermes profile's local CLI
tool configuration so remote ACP hosts can match interactive Hermes.

Absent, invalid, or unknown values fall back to the compatibility default.
"""
if not isinstance(config, dict):
return "hermes-acp"
acp_cfg = config.get("acp")
if not isinstance(acp_cfg, dict):
return "hermes-acp"
raw = acp_cfg.get("tool_policy")
if raw is None:
return "hermes-acp"
if not isinstance(raw, str):
return "hermes-acp"
policy = raw.strip().lower()
if policy in _ACP_COMPAT_TOOL_POLICIES:
return "hermes-acp"
if policy == _ACP_PROFILE_TOOL_POLICY:
return _ACP_PROFILE_TOOL_POLICY
return "hermes-acp"


def resolve_acp_base_toolsets(config: dict | None) -> List[str]:
"""Resolve base ACP toolsets for *config* without host-provided MCP names.

Profile mode uses the canonical platform tool resolver against the
profile's local interactive policy (``platform_toolsets.cli``), or an
explicit ``platform_toolsets.acp`` list when the profile defines one.
Profile-configured MCP servers are included by that resolver. Host MCP
expansion is intentionally not applied here.
"""
config = config if isinstance(config, dict) else {}
if resolve_acp_tool_policy(config) != _ACP_PROFILE_TOOL_POLICY:
return ["hermes-acp"]

from hermes_cli.tools_config import _get_platform_tools

platform_toolsets = config.get("platform_toolsets") or {}
# Explicit acp platform list wins when present; otherwise mirror CLI so a
# profile that already works interactively keeps the same capability set.
if isinstance(platform_toolsets.get("acp"), list):
platform = "acp"
else:
platform = "cli"

resolved = sorted(
_get_platform_tools(
config,
platform,
include_default_mcp_servers=True,
)
)
return resolved or ["hermes-acp"]


def resolve_acp_enabled_toolsets(config: dict | None) -> List[str]:
"""Resolve the full ACP enabled-toolset list for session creation/restore."""
config = config if isinstance(config, dict) else {}
base = resolve_acp_base_toolsets(config)
if resolve_acp_tool_policy(config) == _ACP_PROFILE_TOOL_POLICY:
# Profile MCP membership already lives in *base* via _get_platform_tools.
return _expand_acp_enabled_toolsets(base, mcp_server_names=None)

configured_mcp_servers = [
name
for name, cfg in (config.get("mcp_servers") or {}).items()
if not isinstance(cfg, dict) or cfg.get("enabled", True) is not False
]
return _expand_acp_enabled_toolsets(
base,
mcp_server_names=configured_mcp_servers,
)


def _clear_task_cwd(task_id: str) -> None:
"""Remove task-specific cwd overrides for an ACP session."""
if not task_id:
Expand Down Expand Up @@ -614,18 +698,9 @@ def _make_agent(
elif isinstance(model_cfg, str) and model_cfg.strip():
default_model = model_cfg.strip()

configured_mcp_servers = [
name
for name, cfg in (config.get("mcp_servers") or {}).items()
if not isinstance(cfg, dict) or cfg.get("enabled", True) is not False
]

kwargs = {
"platform": "acp",
"enabled_toolsets": _expand_acp_enabled_toolsets(
["hermes-acp"],
mcp_server_names=configured_mcp_servers,
),
"enabled_toolsets": resolve_acp_enabled_toolsets(config),
"quiet_mode": True,
"session_id": session_id,
"session_db": self._get_db(),
Expand Down
11 changes: 11 additions & 0 deletions hermes_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3536,6 +3536,17 @@ def _ensure_hermes_home_managed(home: Path):
"region": "global",
},

# ACP host integration (Zed, Buzz, and other Agent Client Protocol hosts).
# tool_policy:
# "hermes-acp" (default) — coding-focused ACP toolset for editor hosts.
# "profile" — use this profile's local CLI tool configuration so a remote
# ACP host gets the same capability surface as interactive
# Hermes (skills, memory, cron, kanban, etc. as configured).
# Absent or invalid values preserve the hermes-acp default. No migration.
"acp": {
"tool_policy": "hermes-acp",
},

# Config schema version - bump this when adding new required fields
"_config_version": 33,
}
Expand Down
3 changes: 3 additions & 0 deletions hermes_cli/web_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,9 @@ def _memory_provider_options() -> List[str]:
# field — fold it into the agent tab rather than spawning a one-field
# orphan category.
"computer_use": "agent",
# `acp.tool_policy` is the only schema-surfaced ACP host field — fold it
# into the agent tab rather than spawning a one-field orphan category.
"acp": "agent",
}

# Display order for tabs — unlisted categories sort alphabetically after these.
Expand Down
Loading
Loading