main
md 120 lines 8.85 KB
Rendered Raw
1 # mcp_handler.py DOX
2
3 ## Purpose
4
5 - Own the `mcp_handler.py` helper module.
6 - This module loads global and project-scoped MCP server configuration and exposes MCP tools to agents.
7 - Keep this file-level DOX profile synchronized with `mcp_handler.py` because this directory is intentionally flat.
8
9 ## Ownership
10
11 - `mcp_handler.py` owns the runtime implementation.
12 - `mcp_handler.py.dox.md` owns durable notes about responsibilities, contracts, side effects, and verification for that implementation.
13 - Classes:
14 - `MCPTool` (`Tool`)
15 - `get_log_object(self) -> LogItem`
16 - `async execute(self, **kwargs)`
17 - `async before_execution(self, **kwargs)`
18 - `async after_execution(self, response: Response, **kwargs)`
19 - `MCPServerRemote` (`BaseModel`)
20 - `get_error(self) -> str`
21 - `get_log(self) -> str`
22 - `get_tools(self) -> List[dict[str, Any]]`
23 - `get_all_tools(self) -> List[dict[str, Any]]`
24 - `has_tool(self, tool_name: str) -> bool`
25 - `async call_tool(self, tool_name: str, input_data: Dict[str, Any]) -> CallToolResult`
26 - `update(self, config: dict[str, Any]) -> 'MCPServerRemote'`
27 - `async initialize(self) -> 'MCPServerRemote'`
28 - `MCPServerLocal` (`BaseModel`)
29 - `get_error(self) -> str`
30 - `get_log(self) -> str`
31 - `get_tools(self) -> List[dict[str, Any]]`
32 - `get_all_tools(self) -> List[dict[str, Any]]`
33 - `has_tool(self, tool_name: str) -> bool`
34 - `async call_tool(self, tool_name: str, input_data: Dict[str, Any]) -> CallToolResult`
35 - `update(self, config: dict[str, Any]) -> 'MCPServerLocal'`
36 - `async initialize(self) -> 'MCPServerLocal'`
37 - `MCPConfig` (`BaseModel`)
38 - `get_instance(cls) -> 'MCPConfig'`
39 - `clear_project_instances(cls)`
40 - `parse_config_string(cls, config_str: str) -> List[Dict[str, Any]]`
41 - `merge_config_strings(cls, global_config: str, project_config: str) -> tuple[List[Dict[str, Any]], str]`
42 - `get_project_instance(cls, project_name: str | None, *, force: bool = False) -> 'MCPConfig'`
43 - `refresh_project(cls, project_name: str) -> 'MCPConfig'`
44 - `get_for_agent(cls, agent: Any) -> 'MCPConfig'`
45 - `wait_for_lock(cls)`
46 - `update(cls, config_str: str) -> Any`
47 - `normalize_config(cls, servers: Any)`
48 - `get_server_log(self, server_name: str) -> str`
49 - `get_servers_status(self) -> list[dict[str, Any]]`
50 - `get_server_detail(self, server_name: str) -> dict[str, Any]`
51 - `is_initialized(self) -> bool`
52 - `MCPClientBase` (`ABC`)
53 - `async update_tools(self) -> 'MCPClientBase'`
54 - `has_tool(self, tool_name: str) -> bool`
55 - `get_tools(self) -> List[dict[str, Any]]`
56 - `async call_tool(self, tool_name: str, input_data: Dict[str, Any]) -> CallToolResult`
57 - `get_log(self)`
58 - `MCPClientLocal` (`MCPClientBase`)
59 - `CustomHTTPClientFactory` (`ABC`)
60 - `MCPClientRemote` (`MCPClientBase`)
61 - `get_session_id(self) -> Optional[str]`
62 - Top-level functions:
63 - `_mcp_get(item: Any, key: str, default: Any=...) -> Any`
64 - `normalize_name(name: str) -> str`
65 - `_determine_server_type(config_dict: dict) -> str`: Determine the server type based on configuration, with backward compatibility.
66 - `_is_streaming_http_type(server_type: str) -> bool`: Check if the server type is a streaming HTTP variant.
67 - `_split_qualified_tool_name(tool_name: str) -> tuple[str, str]`: Split `server.tool` names while preserving dots inside MCP tool names.
68 - `_normalize_disabled_tools(value: Any) -> list[str]`: Normalize the optional per-server disabled tool list.
69 - `_split_stdio_command(command: Any) -> tuple[str, list[str]]`: Split shell-style local MCP command lines into an executable plus leading arguments.
70 - `_split_stdio_arg_fragment(arg: str) -> list[str]`: Split collapsed option/value argument fragments while preserving obvious single values with spaces.
71 - `_normalize_stdio_args(value: Any) -> list[str]`: Normalize local MCP argument lists after manager/raw JSON parsing.
72 - `initialize_mcp(mcp_servers_config: str)`
73 - Notable constants/configuration names: `DEFAULT_MCP_SERVERS_CONFIG`, `MCP_MEDIA_TOKENS_ESTIMATE`, `MAX_MCP_RESOURCE_TEXT_CHARS`, `MCP_SESSION_CLEANUP_TIMEOUT_SECONDS`, `MCP_OPERATION_TIMEOUT_GRACE_SECONDS`, `T`.
74
75 ## Runtime Contracts
76
77 - Helper modules own reusable framework APIs and must preserve public callers unless all callers, tests, and docs are updated together.
78 - Update this file whenever public functions, classes, persistence behavior, path/security assumptions, side effects, or cross-module contracts change.
79 - `MCPTool` is a `Tool`.
80 - `MCPTool` defines `execute(...)`.
81 - Global MCP configuration remains backed by settings; project MCP configuration is loaded through `helpers.projects` and merged with global config when an active agent context has `context.project`.
82 - Project-scoped MCP servers overlay global servers by normalized name. The resulting `MCPConfig` cache key is derived from both config strings so project instances refresh when either scope changes.
83 - Server status and detail responses include `scope`, and MCP tools resolve through `MCPConfig.get_for_agent(agent)` before execution.
84 - MCP tool names are qualified as `server_name.tool_name`; server names are normalized without dots, and the tool portion may contain dots.
85 - Agent-facing MCP prompt descriptions filter through the central profile tool
86 policy, and `MCPTool.execute()` rechecks the same policy with the explicit MCP
87 canonical ID before invocation.
88 - `MCPConfig.get_tool()` tries the supplied qualified name first, then restores an advertised Responses alias from the calling agent's name map; names that still do not identify an MCP tool return `None` unchanged for downstream local-tool resolution.
89 - Servers may define `disabled_tools` as a list of MCP tool names. Disabled tools are omitted from agent-facing prompts, status counts, `has_tool`, and calls, while detail views can still retrieve them through `get_all_tools()` with a `disabled` flag so users can re-enable them.
90 - Server-specific `init_timeout` and `tool_timeout` override global MCP client timeout settings for list-tools and call-tool operations.
91 - Local stdio server configs accept either strict MCP JSON (`command: "uvx", args: [...]`) or manager-style command lines (`command: "uvx package"`) and normalize them before spawning the process.
92 - MCP image and image-resource content is materialized to scoped artifact files and returned both as model-visible image attachments and as path metadata (`attachments`/`media_paths`) for downstream delivery.
93 - MCP config locks must not be held across awaited server initialization or tool-call operations. Slow or wedged MCP servers must not block status reads, prompt construction, unrelated MCP servers, or later tool calls through the shared config lock.
94 - MCP client session work runs inside disposable isolated `DeferredTask` workers with an outer timeout. Normal `AsyncExitStack` cleanup is also bounded; if cleanup or transport shutdown does not finish, the operation reports failure or warning while Agent Zero keeps control of the agent loop.
95 - Server status marks initialized server objects with cached initialization errors as disconnected, even if the config object exists.
96 - Observed side-effect areas: filesystem writes, network calls, WebSocket state, settings/state persistence, secret handling.
97 - Imported dependency areas include: `abc`, `anyio.streams.memory`, `asyncio`, `contextlib`, `datetime`, `helpers`, `helpers.defer`, `helpers.log`, `helpers.print_style`, `helpers.tool`, `httpx`, `json`, `mcp`, `mcp.client.sse`, `mcp.client.stdio`, `mcp.client.streamable_http`, `mcp.shared.message`, `shlex`.
98
99 ## Key Concepts
100
101 - Important called helpers/classes observed in the source: `TypeVar`, `name.strip.lower`, `re.sub`, `Field`, `PrivateAttr`, `threading.Lock`, `DeferredTask`, `_split_qualified_tool_name`, `config_dict.lower`, `server_type.lower`, `MCPConfig.get_instance.is_initialized`, `MCPConfig.get_for_agent`, `projects.validate_project_name`, `projects.load_project_mcp_servers`, `settings.get_settings`, `self.agent.context.log.log`, `str.strip`, `media_artifacts.guess_extension`, `callable`, `self._content_item_dump`, `join`, `Response`, `self.get_log_object`, `self._raw_tool_response`, `additional.pop`, `self._coerce_media_token_estimate`.
102 - Keep request/response, tool, or helper semantics documented here at the same time as source changes.
103
104 ## Work Guidance
105
106 - Preserve public helper APIs used by core code and plugins unless every caller is updated.
107 - Keep path, auth, secret, persistence, network, and subprocess behavior explicit and bounded.
108 - Prefer adding cohesive helper functions here only when behavior is reused across modules.
109 - Keep MCP timeout and cleanup changes covered by deterministic tests that do not require real MCP servers or network credentials.
110
111 ## Verification
112
113 - Run targeted tests for changed helper behavior; run security regressions for auth, filesystem, WebSocket, tunnel, upload, or secret-handling helpers.
114 - Related tests observed by source search:
115 - `tests/test_mcp_handler_multimodal.py`
116 - `tests/test_tool_policy.py`
117
118 ## Child DOX Index
119
120 No child DOX files.