agent-zero/helpers/mcp_handler.py.dox.md
Alessandro ef25d89ad8 Harden scoped tool policy enforcement
Resolve effective policy through inherited layers without letting empty upper configs shadow restrictions, and carry canonical tool identities through local and MCP execution.

Enforce required policy hooks and parent delegation checks at runtime, keep installed connector capabilities configurable while disconnected, and remove complete blocked-tool examples from rendered prompts.

Use the canonical tool prompts for concise catalog descriptions and add regressions for plugin activation, dotted local names, MCP calls, layered policies, and parallel subordinate paths.
2026-08-10 05:08:42 +02:00

8.8 KiB

mcp_handler.py DOX

Purpose

  • Own the mcp_handler.py helper module.
  • This module loads global and project-scoped MCP server configuration and exposes MCP tools to agents.
  • Keep this file-level DOX profile synchronized with mcp_handler.py because this directory is intentionally flat.

Ownership

  • mcp_handler.py owns the runtime implementation.
  • mcp_handler.py.dox.md owns durable notes about responsibilities, contracts, side effects, and verification for that implementation.
  • Classes:
  • MCPTool (Tool)
    • get_log_object(self) -> LogItem
    • async execute(self, **kwargs)
    • async before_execution(self, **kwargs)
    • async after_execution(self, response: Response, **kwargs)
  • MCPServerRemote (BaseModel)
    • get_error(self) -> str
    • get_log(self) -> str
    • get_tools(self) -> List[dict[str, Any]]
    • get_all_tools(self) -> List[dict[str, Any]]
    • has_tool(self, tool_name: str) -> bool
    • async call_tool(self, tool_name: str, input_data: Dict[str, Any]) -> CallToolResult
    • update(self, config: dict[str, Any]) -> 'MCPServerRemote'
    • async initialize(self) -> 'MCPServerRemote'
  • MCPServerLocal (BaseModel)
    • get_error(self) -> str
    • get_log(self) -> str
    • get_tools(self) -> List[dict[str, Any]]
    • get_all_tools(self) -> List[dict[str, Any]]
    • has_tool(self, tool_name: str) -> bool
    • async call_tool(self, tool_name: str, input_data: Dict[str, Any]) -> CallToolResult
    • update(self, config: dict[str, Any]) -> 'MCPServerLocal'
    • async initialize(self) -> 'MCPServerLocal'
  • MCPConfig (BaseModel)
    • get_instance(cls) -> 'MCPConfig'
    • clear_project_instances(cls)
    • parse_config_string(cls, config_str: str) -> List[Dict[str, Any]]
    • merge_config_strings(cls, global_config: str, project_config: str) -> tuple[List[Dict[str, Any]], str]
    • get_project_instance(cls, project_name: str | None, *, force: bool = False) -> 'MCPConfig'
    • refresh_project(cls, project_name: str) -> 'MCPConfig'
    • get_for_agent(cls, agent: Any) -> 'MCPConfig'
    • wait_for_lock(cls)
    • update(cls, config_str: str) -> Any
    • normalize_config(cls, servers: Any)
    • get_server_log(self, server_name: str) -> str
    • get_servers_status(self) -> list[dict[str, Any]]
    • get_server_detail(self, server_name: str) -> dict[str, Any]
    • is_initialized(self) -> bool
  • MCPClientBase (ABC)
    • async update_tools(self) -> 'MCPClientBase'
    • has_tool(self, tool_name: str) -> bool
    • get_tools(self) -> List[dict[str, Any]]
    • async call_tool(self, tool_name: str, input_data: Dict[str, Any]) -> CallToolResult
    • get_log(self)
  • MCPClientLocal (MCPClientBase)
  • CustomHTTPClientFactory (ABC)
  • MCPClientRemote (MCPClientBase)
    • get_session_id(self) -> Optional[str]
  • Top-level functions:
  • _mcp_get(item: Any, key: str, default: Any=...) -> Any
  • normalize_name(name: str) -> str
  • _determine_server_type(config_dict: dict) -> str: Determine the server type based on configuration, with backward compatibility.
  • _is_streaming_http_type(server_type: str) -> bool: Check if the server type is a streaming HTTP variant.
  • _split_qualified_tool_name(tool_name: str) -> tuple[str, str]: Split server.tool names while preserving dots inside MCP tool names.
  • _normalize_disabled_tools(value: Any) -> list[str]: Normalize the optional per-server disabled tool list.
  • _split_stdio_command(command: Any) -> tuple[str, list[str]]: Split shell-style local MCP command lines into an executable plus leading arguments.
  • _split_stdio_arg_fragment(arg: str) -> list[str]: Split collapsed option/value argument fragments while preserving obvious single values with spaces.
  • _normalize_stdio_args(value: Any) -> list[str]: Normalize local MCP argument lists after manager/raw JSON parsing.
  • initialize_mcp(mcp_servers_config: str)
  • 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.

Runtime Contracts

  • Helper modules own reusable framework APIs and must preserve public callers unless all callers, tests, and docs are updated together.
  • Update this file whenever public functions, classes, persistence behavior, path/security assumptions, side effects, or cross-module contracts change.
  • MCPTool is a Tool.
  • MCPTool defines execute(...).
  • 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.
  • 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.
  • Server status and detail responses include scope, and MCP tools resolve through MCPConfig.get_for_agent(agent) before execution.
  • MCP tool names are qualified as server_name.tool_name; server names are normalized without dots, and the tool portion may contain dots.
  • Agent-facing MCP prompt descriptions filter through the central profile tool policy, and MCPTool.execute() rechecks the same policy with the explicit MCP canonical ID before invocation.
  • 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.
  • 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.
  • Server-specific init_timeout and tool_timeout override global MCP client timeout settings for list-tools and call-tool operations.
  • 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.
  • 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.
  • 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.
  • 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.
  • Server status marks initialized server objects with cached initialization errors as disconnected, even if the config object exists.
  • Observed side-effect areas: filesystem writes, network calls, WebSocket state, settings/state persistence, secret handling.
  • 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.

Key Concepts

  • 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.
  • Keep request/response, tool, or helper semantics documented here at the same time as source changes.

Work Guidance

  • Preserve public helper APIs used by core code and plugins unless every caller is updated.
  • Keep path, auth, secret, persistence, network, and subprocess behavior explicit and bounded.
  • Prefer adding cohesive helper functions here only when behavior is reused across modules.
  • Keep MCP timeout and cleanup changes covered by deterministic tests that do not require real MCP servers or network credentials.

Verification

  • Run targeted tests for changed helper behavior; run security regressions for auth, filesystem, WebSocket, tunnel, upload, or secret-handling helpers.
  • Related tests observed by source search:
    • tests/test_mcp_handler_multimodal.py
    • tests/test_tool_policy.py

Child DOX Index

No child DOX files.