diff --git a/backend/apps/agents/manager/builtin_mcp_servers.py b/backend/apps/agents/manager/builtin_mcp_servers.py index 62842d44..698d728c 100644 --- a/backend/apps/agents/manager/builtin_mcp_servers.py +++ b/backend/apps/agents/manager/builtin_mcp_servers.py @@ -1,6 +1,7 @@ """Register the always-on + delegation MCP servers (browser-agent, invoke-agent, meta, -settings-meta) into the per-turn mcp_servers map. Lifted out of the agent loop; the server -scripts live beside agent_manager.py, so the loop passes their directory in. Returns the +settings-meta) into the per-turn mcp_servers map. The server scripts live in the agents +package, so we resolve their directory off that package here, NOT off a dir a caller passes +in: a caller in a moved file would compute the wrong dir (this bit us once). Returns the browser/invoke delegation tool-name lists the allowlist gate needs.""" import os @@ -19,8 +20,9 @@ def register_builtin_mcp_servers( session: AgentSession, builtin_perms: Dict[str, str], selected_browser_ids: Optional[List[str]], - agents_dir: str, ) -> Tuple[List[str], List[str]]: + import backend.apps.agents as p_agents_pkg + agents_dir = os.path.dirname(p_agents_pkg.__file__) browser_delegation_tools = ["CreateBrowserAgent", "BrowserAgent", "BrowserAgents"] browser_all_denied = all( builtin_perms.get(t, "always_allow") == "deny" diff --git a/backend/apps/agents/manager/run/RunOptionsMixin.py b/backend/apps/agents/manager/run/RunOptionsMixin.py index 4900a765..d1467ca4 100644 --- a/backend/apps/agents/manager/run/RunOptionsMixin.py +++ b/backend/apps/agents/manager/run/RunOptionsMixin.py @@ -4,7 +4,6 @@ options_kwargs (provider env, preset, thinking, resume/history), runs the pre-se and returns the ClaudeAgentOptions plus the bits the streaming turn needs. Mixin method: self.* and the gate hooks resolve across the MRO unchanged.""" -import os import json import logging from typing import Dict, List, Optional, Union @@ -110,7 +109,7 @@ class RunOptionsMixin: mcp_servers = await self.build_mcp_servers(session.allowed_tools, session.active_mcps) browser_delegation_tools, invoke_agent_tools = register_builtin_mcp_servers( - mcp_servers, session, builtin_perms, selected_browser_ids, os.path.dirname(__file__) + mcp_servers, session, builtin_perms, selected_browser_ids ) diff --git a/backend/tests/test_builtin_mcp_servers.py b/backend/tests/test_builtin_mcp_servers.py index 4feb6198..66eea134 100644 --- a/backend/tests/test_builtin_mcp_servers.py +++ b/backend/tests/test_builtin_mcp_servers.py @@ -2,6 +2,8 @@ are always registered, the browser/invoke delegation servers register unless their tools are fully denied, and the delegation tool-name lists come back for the allowlist gate.""" +import os + from backend.apps.agents.core.models import AgentSession from backend.apps.agents.manager.builtin_mcp_servers import register_builtin_mcp_servers @@ -13,7 +15,7 @@ def p_session(): def test_registers_always_on_and_delegation_servers(): mcp_servers = {} browser_tools, invoke_tools = register_builtin_mcp_servers( - mcp_servers, p_session(), {}, None, "/agents") + mcp_servers, p_session(), {}, None) # always-on assert "openswarm-mcp-meta" in mcp_servers assert "openswarm-settings-meta" in mcp_servers @@ -22,14 +24,18 @@ def test_registers_always_on_and_delegation_servers(): assert "openswarm-invoke-agent" in mcp_servers assert browser_tools == ["CreateBrowserAgent", "BrowserAgent", "BrowserAgents"] assert invoke_tools == ["InvokeAgent"] - # server scripts resolve under the passed agents dir - assert mcp_servers["openswarm-mcp-meta"]["args"][0].startswith("/agents") + # Every registered server's script path must resolve to a file that ACTUALLY EXISTS. + # This is the assertion that catches a moved-caller resolving the wrong agents dir. + for name in ("openswarm-mcp-meta", "openswarm-settings-meta", + "openswarm-browser-agent", "openswarm-invoke-agent"): + script = mcp_servers[name]["args"][0] + assert os.path.isfile(script), f"{name} script does not exist on disk: {script}" def test_fully_denied_delegation_servers_are_not_registered(): mcp_servers = {} perms = {t: "deny" for t in ("CreateBrowserAgent", "BrowserAgent", "BrowserAgents", "InvokeAgent")} - register_builtin_mcp_servers(mcp_servers, p_session(), perms, None, "/agents") + register_builtin_mcp_servers(mcp_servers, p_session(), perms, None) assert "openswarm-browser-agent" not in mcp_servers # all browser tools denied -> skip assert "openswarm-invoke-agent" not in mcp_servers assert "openswarm-mcp-meta" in mcp_servers # always-on regardless