From d3afc6fcac335bccc172cb12bbfba2d27919f101 Mon Sep 17 00:00:00 2001 From: haikdc Date: Thu, 2 Apr 2026 21:05:33 -0700 Subject: [PATCH] [Haik]: ckpt, mcp server dict builder and tool permission lists done --- backend/apps/agents/agents.py | 20 ++++++-- backend/core/tools/shared_structs/Toolkit.py | 53 +++++++++++++++++++- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/backend/apps/agents/agents.py b/backend/apps/agents/agents.py index 9b6f3f45..1e9902a6 100644 --- a/backend/apps/agents/agents.py +++ b/backend/apps/agents/agents.py @@ -35,6 +35,7 @@ from backend.apps.agents.session_store import ( ) from backend.apps.agents import ws from backend.apps.agents.compose_system_prompt import compose_system_prompt +from backend.core.tools.make_builtin_toolkit.make_builtin_toolkit import make_builtin_toolkit from claude_agent_sdk import ClaudeAgentOptions SESSIONS: dict[str, Agent] = {} @@ -137,17 +138,28 @@ async def launch(body: LaunchBody) -> dict: system_prompt = compose_system_prompt( session_prompt=body.system_prompt or None, ) + # Placeholder config — replaced below once the toolkit is built agent: Agent = Agent( model=body.model, mode=body.mode, status="stopped", - config=ClaudeAgentOptions( - system_prompt=system_prompt, - max_turns=body.max_turns, - ), + config=ClaudeAgentOptions(max_turns=body.max_turns), ) agent.on_event = p_make_session_emitter(agent.session_id) SESSIONS[agent.session_id] = agent + + toolkit = make_builtin_toolkit(agent, SESSIONS, p_send_browser_command) + mcp_servers = toolkit.collect_mcp_servers() + allowed_tools, disallowed_tools = toolkit.collect_tool_permissions() + + agent.config = ClaudeAgentOptions( + system_prompt=system_prompt, + max_turns=body.max_turns, + mcp_servers=mcp_servers if mcp_servers else None, + allowed_tools=allowed_tools, + disallowed_tools=disallowed_tools, + ) + await agent.emit(AgentStatusEvent( session_id=agent.session_id, status="stopped", session=agent.snapshot(), diff --git a/backend/core/tools/shared_structs/Toolkit.py b/backend/core/tools/shared_structs/Toolkit.py index 3bdbc5bb..2f22824a 100644 --- a/backend/core/tools/shared_structs/Toolkit.py +++ b/backend/core/tools/shared_structs/Toolkit.py @@ -1,7 +1,9 @@ -from typing import Optional, List +from typing import Optional, List, Dict, Tuple from pydantic import BaseModel from backend.core.tools.shared_structs.Tool import Tool from backend.core.tools.shared_structs.TOOL_PERMISSIONS import TOOL_PERMISSIONS +from backend.core.tools.shared_structs.MCP_Tool import MCP_Tool +from claude_agent_sdk.types import McpServerConfig from typeguard import typechecked class Toolkit(BaseModel): @@ -39,4 +41,51 @@ class Toolkit(BaseModel): tool.permission = permission if self.nested_toolkits is not None: for toolkit in self.nested_toolkits: - toolkit.set_permission(permission) \ No newline at end of file + toolkit.set_permission(permission) + + @typechecked + def collect_mcp_servers(self) -> Dict[str, McpServerConfig]: + """Walk the toolkit tree and collect MCP server configs from every MCP_Tool. + + Returns a dict mapping server_name -> McpServerConfig, ready to pass + to ClaudeAgentOptions(mcp_servers=...). + """ + servers: Dict[str, McpServerConfig] = {} + if self.tools is not None: + for tool in self.tools: + if isinstance(tool, MCP_Tool): + tool_config: Dict[str, McpServerConfig] = tool.to_mcp_server_config() + for key, value in tool_config.items(): + servers[key] = value + if self.nested_toolkits is not None: + for toolkit in self.nested_toolkits: + toolkit_config: Dict[str, McpServerConfig] = toolkit.collect_mcp_servers() + for key, value in toolkit_config.items(): + servers[key] = value + return servers + + @typechecked + def collect_tool_permissions(self) -> Tuple[List[str], List[str]]: + """Walk the toolkit tree and partition tools by permission. + + Returns (allowed_tools, disallowed_tools) — lists of SDK-format + tool names. Tools with permission "ask" appear in neither list; + they are gated at runtime by the can_use_tool hook. + """ + allowed: List[str] = [] + disallowed: List[str] = [] + if self.tools is not None: + for tool in self.tools: + sdk_name: str = tool.to_sdk_args() + if tool.permission == "allow": + allowed.append(sdk_name) + elif tool.permission == "deny": + disallowed.append(sdk_name) + if self.nested_toolkits is not None: + for toolkit in self.nested_toolkits: + a: List[str] + d: List[str] + a, d = toolkit.collect_tool_permissions() + allowed.extend(a) + disallowed.extend(d) + return allowed, disallowed \ No newline at end of file