mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-07 02:07:45 +02:00
[eric] agents: extract built-in MCP-server registration into builtin_mcp_servers + tests
This commit is contained in:
@@ -64,6 +64,7 @@ from backend.apps.agents.manager.streaming.upsert_message import upsert_message
|
||||
from backend.apps.agents.manager.prompt.system_prompt import compose_turn_system_prompt
|
||||
from backend.apps.agents.tools.web import should_register_web_mcp
|
||||
from backend.apps.agents.manager.permissions.effective_tools import build_effective_tool_lists
|
||||
from backend.apps.agents.manager.builtin_mcp_servers import register_builtin_mcp_servers
|
||||
from backend.apps.agents.manager.session.SessionLifecycleMixin import SessionLifecycleMixin
|
||||
from backend.apps.agents.manager.MessagingMixin import MessagingMixin
|
||||
from backend.apps.agents.manager.AgentLaunchMixin import AgentLaunchMixin
|
||||
@@ -252,103 +253,10 @@ class AgentManager(SessionLifecycleMixin, MessagingMixin, AgentLaunchMixin, RunS
|
||||
# dispatch layer (see p_build_mcp_servers docstring).
|
||||
mcp_servers = await self.p_build_mcp_servers(session.allowed_tools, session.active_mcps)
|
||||
|
||||
p_browser_delegation_tools = ["CreateBrowserAgent", "BrowserAgent", "BrowserAgents"]
|
||||
p_browser_all_denied = all(
|
||||
builtin_perms.get(t, "always_allow") == "deny"
|
||||
for t in p_browser_delegation_tools
|
||||
browser_delegation_tools, invoke_agent_tools = register_builtin_mcp_servers(
|
||||
mcp_servers, session, builtin_perms, selected_browser_ids, os.path.dirname(__file__)
|
||||
)
|
||||
|
||||
if not p_browser_all_denied:
|
||||
browser_agent_server_path = os.path.join(
|
||||
os.path.dirname(__file__), "browser_agent_mcp_server.py"
|
||||
)
|
||||
backend_port = os.environ.get("OPENSWARM_PORT", "8324")
|
||||
# Only the card the user actually picked in select-mode gets claimed for the
|
||||
# task, so the sub drives that one instead of opening its own duplicate. Passing
|
||||
# EVERY dashboard card here (the old behavior) made the sub force-grab a random,
|
||||
# usually-parked card and never navigate it, which broke the bulk of browser tasks.
|
||||
pre_selected_bids = [b for b in (selected_browser_ids or []) if b]
|
||||
from backend.auth import get_auth_token as p_get_auth_token
|
||||
p_auth_tok = p_get_auth_token()
|
||||
mcp_servers["openswarm-browser-agent"] = {
|
||||
"command": sys.executable,
|
||||
"args": [browser_agent_server_path],
|
||||
"env": {
|
||||
"OPENSWARM_PORT": backend_port,
|
||||
"OPENSWARM_AUTH_TOKEN": p_auth_tok,
|
||||
"OPENSWARM_AGENT_MODEL": session.model,
|
||||
"OPENSWARM_DASHBOARD_ID": session.dashboard_id or "",
|
||||
"OPENSWARM_PRE_SELECTED_BROWSER_IDS": ",".join(pre_selected_bids),
|
||||
"OPENSWARM_PARENT_SESSION_ID": session.id,
|
||||
},
|
||||
"type": "stdio",
|
||||
}
|
||||
|
||||
p_invoke_agent_tools = ["InvokeAgent"]
|
||||
p_invoke_all_denied = all(
|
||||
builtin_perms.get(t, "always_allow") == "deny"
|
||||
for t in p_invoke_agent_tools
|
||||
)
|
||||
|
||||
if not p_invoke_all_denied:
|
||||
invoke_agent_server_path = os.path.join(
|
||||
os.path.dirname(__file__), "invoke_agent_mcp_server.py"
|
||||
)
|
||||
backend_port = os.environ.get("OPENSWARM_PORT", "8324")
|
||||
from backend.auth import get_auth_token as p_get_auth_token2
|
||||
mcp_servers["openswarm-invoke-agent"] = {
|
||||
"command": sys.executable,
|
||||
"args": [invoke_agent_server_path],
|
||||
"env": {
|
||||
"OPENSWARM_PORT": backend_port,
|
||||
"OPENSWARM_AUTH_TOKEN": p_get_auth_token2(),
|
||||
"OPENSWARM_PARENT_SESSION_ID": session.id,
|
||||
"OPENSWARM_DASHBOARD_ID": session.dashboard_id or "",
|
||||
},
|
||||
"type": "stdio",
|
||||
}
|
||||
|
||||
# Always-on meta-MCP server. Exposes MCPList / MCPSearch /
|
||||
# MCPActivate so the model can discover and activate user MCPs at
|
||||
# runtime. The activation gate (active_mcps filter in
|
||||
# p_build_mcp_servers above) ensures the model cannot reach any
|
||||
# other MCP server's tools without going through this layer first.
|
||||
mcp_meta_server_path = os.path.join(
|
||||
os.path.dirname(__file__), "mcp_meta_server.py"
|
||||
)
|
||||
from backend.auth import get_auth_token as p_get_auth_token3
|
||||
mcp_servers["openswarm-mcp-meta"] = {
|
||||
"command": sys.executable,
|
||||
"args": [mcp_meta_server_path],
|
||||
"env": {
|
||||
"OPENSWARM_PORT": os.environ.get("OPENSWARM_PORT", "8324"),
|
||||
"OPENSWARM_AUTH_TOKEN": p_get_auth_token3(),
|
||||
"OPENSWARM_PARENT_SESSION_ID": session.id,
|
||||
},
|
||||
"type": "stdio",
|
||||
}
|
||||
|
||||
# Always-on settings-meta server: SettingsRead / SettingsWrite let the
|
||||
# agent read and edit its own OpenSwarm Settings autonomously. The
|
||||
# backend (/api/settings-meta) enforces the only two guardrails: it
|
||||
# can't disconnect the credential powering this run, and reads come
|
||||
# back with secrets redacted. No activation gate, Settings is the
|
||||
# agent's own house, not a third-party MCP.
|
||||
settings_meta_server_path = os.path.join(
|
||||
os.path.dirname(__file__), "settings_meta_server.py"
|
||||
)
|
||||
from backend.auth import get_auth_token as p_get_auth_token4
|
||||
mcp_servers["openswarm-settings-meta"] = {
|
||||
"command": sys.executable,
|
||||
"args": [settings_meta_server_path],
|
||||
"env": {
|
||||
"OPENSWARM_PORT": os.environ.get("OPENSWARM_PORT", "8324"),
|
||||
"OPENSWARM_AUTH_TOKEN": p_get_auth_token4(),
|
||||
"OPENSWARM_PARENT_SESSION_ID": session.id,
|
||||
},
|
||||
"type": "stdio",
|
||||
}
|
||||
|
||||
|
||||
# Register the DDG-backed openswarm-web MCP only when the primary has no reliable
|
||||
# native Anthropic web path (decided in tools/web.py); p_m feeds the registration log
|
||||
@@ -378,7 +286,7 @@ class AgentManager(SessionLifecycleMixin, MessagingMixin, AgentLaunchMixin, RunS
|
||||
"command": sys.executable,
|
||||
"args": [web_mcp_server_path],
|
||||
"env": {
|
||||
"OPENSWARM_PORT": backend_port,
|
||||
"OPENSWARM_PORT": os.environ.get("OPENSWARM_PORT", "8324"),
|
||||
"OPENSWARM_AUTH_TOKEN": p_get_auth_token3(),
|
||||
"OPENSWARM_PRIMARY_API": p_primary_hint,
|
||||
},
|
||||
@@ -391,7 +299,7 @@ class AgentManager(SessionLifecycleMixin, MessagingMixin, AgentLaunchMixin, RunS
|
||||
|
||||
effective_allowed, effective_disallowed = build_effective_tool_lists(
|
||||
session, mcp_servers, builtin_perms, need_web_mcp,
|
||||
p_browser_delegation_tools, p_invoke_agent_tools,
|
||||
browser_delegation_tools, invoke_agent_tools,
|
||||
)
|
||||
|
||||
# Tell the model directly which web tools work for this session.
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
"""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
|
||||
browser/invoke delegation tool-name lists the allowlist gate needs."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from typeguard import typechecked
|
||||
|
||||
from backend.apps.agents.core.models import AgentSession
|
||||
from backend.auth import get_auth_token
|
||||
|
||||
|
||||
@typechecked
|
||||
def register_builtin_mcp_servers(
|
||||
mcp_servers: Dict,
|
||||
session: AgentSession,
|
||||
builtin_perms: Dict[str, str],
|
||||
selected_browser_ids: Optional[List[str]],
|
||||
agents_dir: str,
|
||||
) -> Tuple[List[str], List[str]]:
|
||||
browser_delegation_tools = ["CreateBrowserAgent", "BrowserAgent", "BrowserAgents"]
|
||||
browser_all_denied = all(
|
||||
builtin_perms.get(t, "always_allow") == "deny"
|
||||
for t in browser_delegation_tools
|
||||
)
|
||||
|
||||
if not browser_all_denied:
|
||||
browser_agent_server_path = os.path.join(
|
||||
agents_dir, "browser_agent_mcp_server.py"
|
||||
)
|
||||
backend_port = os.environ.get("OPENSWARM_PORT", "8324")
|
||||
# Only the card the user actually picked in select-mode gets claimed for the
|
||||
# task, so the sub drives that one instead of opening its own duplicate. Passing
|
||||
# EVERY dashboard card here (the old behavior) made the sub force-grab a random,
|
||||
# usually-parked card and never navigate it, which broke the bulk of browser tasks.
|
||||
pre_selected_bids = [b for b in (selected_browser_ids or []) if b]
|
||||
auth_tok = get_auth_token()
|
||||
mcp_servers["openswarm-browser-agent"] = {
|
||||
"command": sys.executable,
|
||||
"args": [browser_agent_server_path],
|
||||
"env": {
|
||||
"OPENSWARM_PORT": backend_port,
|
||||
"OPENSWARM_AUTH_TOKEN": auth_tok,
|
||||
"OPENSWARM_AGENT_MODEL": session.model,
|
||||
"OPENSWARM_DASHBOARD_ID": session.dashboard_id or "",
|
||||
"OPENSWARM_PRE_SELECTED_BROWSER_IDS": ",".join(pre_selected_bids),
|
||||
"OPENSWARM_PARENT_SESSION_ID": session.id,
|
||||
},
|
||||
"type": "stdio",
|
||||
}
|
||||
|
||||
invoke_agent_tools = ["InvokeAgent"]
|
||||
invoke_all_denied = all(
|
||||
builtin_perms.get(t, "always_allow") == "deny"
|
||||
for t in invoke_agent_tools
|
||||
)
|
||||
|
||||
if not invoke_all_denied:
|
||||
invoke_agent_server_path = os.path.join(
|
||||
agents_dir, "invoke_agent_mcp_server.py"
|
||||
)
|
||||
backend_port = os.environ.get("OPENSWARM_PORT", "8324")
|
||||
mcp_servers["openswarm-invoke-agent"] = {
|
||||
"command": sys.executable,
|
||||
"args": [invoke_agent_server_path],
|
||||
"env": {
|
||||
"OPENSWARM_PORT": backend_port,
|
||||
"OPENSWARM_AUTH_TOKEN": get_auth_token(),
|
||||
"OPENSWARM_PARENT_SESSION_ID": session.id,
|
||||
"OPENSWARM_DASHBOARD_ID": session.dashboard_id or "",
|
||||
},
|
||||
"type": "stdio",
|
||||
}
|
||||
|
||||
# Always-on meta-MCP server. Exposes MCPList / MCPSearch /
|
||||
# MCPActivate so the model can discover and activate user MCPs at
|
||||
# runtime. The activation gate (active_mcps filter in
|
||||
# p_build_mcp_servers above) ensures the model cannot reach any
|
||||
# other MCP server's tools without going through this layer first.
|
||||
mcp_meta_server_path = os.path.join(
|
||||
agents_dir, "mcp_meta_server.py"
|
||||
)
|
||||
mcp_servers["openswarm-mcp-meta"] = {
|
||||
"command": sys.executable,
|
||||
"args": [mcp_meta_server_path],
|
||||
"env": {
|
||||
"OPENSWARM_PORT": os.environ.get("OPENSWARM_PORT", "8324"),
|
||||
"OPENSWARM_AUTH_TOKEN": get_auth_token(),
|
||||
"OPENSWARM_PARENT_SESSION_ID": session.id,
|
||||
},
|
||||
"type": "stdio",
|
||||
}
|
||||
|
||||
# Always-on settings-meta server: SettingsRead / SettingsWrite let the
|
||||
# agent read and edit its own OpenSwarm Settings autonomously. The
|
||||
# backend (/api/settings-meta) enforces the only two guardrails: it
|
||||
# can't disconnect the credential powering this run, and reads come
|
||||
# back with secrets redacted. No activation gate, Settings is the
|
||||
# agent's own house, not a third-party MCP.
|
||||
settings_meta_server_path = os.path.join(
|
||||
agents_dir, "settings_meta_server.py"
|
||||
)
|
||||
mcp_servers["openswarm-settings-meta"] = {
|
||||
"command": sys.executable,
|
||||
"args": [settings_meta_server_path],
|
||||
"env": {
|
||||
"OPENSWARM_PORT": os.environ.get("OPENSWARM_PORT", "8324"),
|
||||
"OPENSWARM_AUTH_TOKEN": get_auth_token(),
|
||||
"OPENSWARM_PARENT_SESSION_ID": session.id,
|
||||
},
|
||||
"type": "stdio",
|
||||
}
|
||||
return browser_delegation_tools, invoke_agent_tools
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Direct coverage for register_builtin_mcp_servers: the always-on meta + settings-meta servers
|
||||
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."""
|
||||
|
||||
from backend.apps.agents.core.models import AgentSession
|
||||
from backend.apps.agents.manager.builtin_mcp_servers import register_builtin_mcp_servers
|
||||
|
||||
|
||||
def _session():
|
||||
return AgentSession(name="t", model="sonnet", dashboard_id="d")
|
||||
|
||||
|
||||
def test_registers_always_on_and_delegation_servers():
|
||||
mcp_servers = {}
|
||||
browser_tools, invoke_tools = register_builtin_mcp_servers(
|
||||
mcp_servers, _session(), {}, None, "/agents")
|
||||
# always-on
|
||||
assert "openswarm-mcp-meta" in mcp_servers
|
||||
assert "openswarm-settings-meta" in mcp_servers
|
||||
# delegation (not denied)
|
||||
assert "openswarm-browser-agent" in mcp_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")
|
||||
|
||||
|
||||
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, _session(), perms, None, "/agents")
|
||||
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
|
||||
Reference in New Issue
Block a user