From ee3e32f2df8a08aab26cd35a5d3517a74bc37962 Mon Sep 17 00:00:00 2001 From: Engel Nyst Date: Sun, 20 Sep 2026 15:51:08 +0200 Subject: [PATCH] docs: correct MCP architecture and include it in navigation --- docs.json | 1 + sdk/arch/mcp.mdx | 138 +++++++++++++++++------------------------------ 2 files changed, 49 insertions(+), 90 deletions(-) diff --git a/docs.json b/docs.json index d060b89d8..92058d474 100644 --- a/docs.json +++ b/docs.json @@ -449,6 +449,7 @@ "sdk/arch/agent", "sdk/arch/conversation", "sdk/arch/tool-system", + "sdk/arch/mcp", "sdk/arch/events", "sdk/arch/workspace", "sdk/arch/llm", diff --git a/sdk/arch/mcp.mdx b/sdk/arch/mcp.mdx index 804437127..656db882b 100644 --- a/sdk/arch/mcp.mdx +++ b/sdk/arch/mcp.mdx @@ -32,7 +32,7 @@ flowchart TB end subgraph Integration["Agent Integration"] - Action["MCPToolAction
Dynamic model"] + Action["MCPToolAction
Argument wrapper"] Obs["MCPToolObservation
Result wrapper"] end @@ -67,9 +67,9 @@ flowchart TB | Component | Purpose | Design | |-----------|---------|--------| | **[`MCPClient`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/mcp/client.py)** | Client wrapper | Extends FastMCP with sync/async bridge | -| **[`MCPToolDefinition`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/mcp/definition.py)** | Tool metadata | Converts MCP schemas to SDK format | +| **[`MCPToolDefinition`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/mcp/tool.py)** | Tool metadata | Converts MCP schemas to SDK format | | **[`MCPToolExecutor`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/mcp/tool.py)** | Execution handler | Bridges agent actions to MCP calls | -| **[`MCPToolAction`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/mcp/definition.py)** | Dynamic action model | Runtime-generated Pydantic model | +| **[`MCPToolAction`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/mcp/definition.py)** | Action wrapper | Stores validated arguments in `data` | | **[`MCPToolObservation`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/mcp/definition.py)** | Result wrapper | Wraps MCP tool results | ## MCP Client @@ -109,7 +109,7 @@ flowchart TB - **Lifecycle Management:** `__enter__`/`__exit__` for context manager - **Timeout Support:** Configurable timeouts for MCP operations - **Error Handling:** Wraps MCP errors in observations -- **Connection Pooling:** Reuses connections across tool calls +- **Connection Reuse:** Tools share their connected MCP client ### MCP Server Configuration @@ -143,12 +143,12 @@ mcp_config = { %%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30, "rankSpacing": 40}} }%% flowchart TB Config["MCP Config"] - Spawn["Spawn Server"] + Spawn["Connect to Server"] List["List Tools"] subgraph Convert["Convert Each Tool"] Schema["MCP Schema"] - Action["Generate Action Model"] + Action["Generate Validation Model"] Def["Create ToolDefinition"] end @@ -169,67 +169,43 @@ flowchart TB **Discovery Steps:** -1. **Spawn Server:** Launch MCP server via stdio +1. **Connect:** Launch a stdio server or connect to a configured HTTP server 2. **List Tools:** Call `tools/list` MCP endpoint 3. **Parse Schemas:** Extract tool names, descriptions, parameters -4. **Generate Models:** Dynamically create Pydantic models for actions +4. **Generate Models:** Create Pydantic models from input schemas for argument validation 5. **Create Definitions:** Wrap in `ToolDefinition` objects 6. **Register:** Add to agent's tool registry ### Schema Conversion -MCP tool schemas are converted to SDK tool definitions: +`MCPToolDefinition` keeps the original MCP tool metadata and input schema. The +LLM-facing schema is built from that input schema, preserving nested properties. +A separate Pydantic model derived from `Schema` validates the arguments. -```mermaid -%%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30}} }%% -flowchart LR - MCP["MCP Tool Schema
JSON Schema"] - Parse["Parse Parameters"] - Model["Dynamic Pydantic Model
MCPToolAction"] - Def["ToolDefinition
SDK format"] - - MCP --> Parse - Parse --> Model - Model --> Def - - style Parse fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px - style Model fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px -``` +`MCPToolAction` is a wrapper with a `data` dictionary. Its fields do not change for +each discovered tool. `action_from_arguments()` validates the arguments, removes +null values and internal fields, and stores the sanitized result in `data`. +The definition validates `action.data` again before execution. -**Conversion Rules:** - -| MCP Schema | SDK Action Model | -|------------|------------------| -| **name** | Class name (camelCase) | -| **description** | Docstring | -| **inputSchema** | Pydantic fields | -| **required** | Field(required=True) | -| **type** | Python type hints | - -**Example:** +For a discovered `fetch_url` tool whose input schema accepts a string `url` and a +numeric `timeout`, argument conversion looks like this: ```python -# MCP Schema -{ - "name": "fetch_url", - "description": "Fetch content from URL", - "inputSchema": { - "type": "object", - "properties": { - "url": {"type": "string"}, - "timeout": {"type": "number"} - }, - "required": ["url"] - } -} +from openhands.sdk.mcp.tool import MCPToolDefinition + -# Generated Action Model -class FetchUrl(MCPToolAction): - """Fetch content from URL""" - url: str - timeout: float | None = None +def prepare_fetch(tool_definition: MCPToolDefinition): + action = tool_definition.action_from_arguments( + {"url": "https://example.com", "timeout": 10} + ) + # action.data holds the validated arguments. + # The executor forwards these arguments to the MCP server. + return action.to_mcp_arguments() ``` +See [`MCPToolDefinition`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/mcp/tool.py) +for schema generation and argument validation. + ## Tool Execution ### Execution Flow @@ -267,9 +243,9 @@ flowchart TB 1. **Action Creation:** LLM generates tool call, parsed into `MCPToolAction` 2. **Executor Lookup:** Find `MCPToolExecutor` for tool name -3. **Format Conversion:** Convert action fields to MCP arguments +3. **Format Conversion:** Read the argument dictionary using `action.to_mcp_arguments()` 4. **MCP Call:** Execute `call_tool` via MCP client -5. **Result Parsing:** Parse MCP result (text, images, resources) +5. **Result Parsing:** Convert text and image blocks; log and skip unsupported blocks, including resources 6. **Observation Creation:** Wrap in `MCPToolObservation` 7. **Error Handling:** Catch exceptions, return error observations @@ -294,7 +270,7 @@ flowchart LR **Executor Responsibilities:** - **Client Management:** Hold reference to MCP client - **Tool Identification:** Know which MCP tool to call -- **Argument Conversion:** Transform action fields to MCP format +- **Argument Conversion:** Forward the action’s `data` dictionary as MCP arguments - **Result Handling:** Parse MCP responses - **Error Recovery:** Handle connection errors, timeouts, server failures @@ -352,41 +328,23 @@ flowchart TB ## MCP Annotations -MCP tools can include metadata hints for agents: - -```mermaid -%%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30}} }%% -flowchart LR - Tool["MCP Tool"] - - subgraph Annotations - ReadOnly["readOnlyHint"] - Destructive["destructiveHint"] - Progress["progressEnabled"] - end - - Security["Security Analysis"] - - Tool --> ReadOnly - Tool --> Destructive - Tool --> Progress - - ReadOnly --> Security - Destructive --> Security - - style Destructive fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px - style Security fill:#fff4df,stroke:#b7791f,stroke-width:2px -``` +MCP tool annotations are copied into the SDK's `ToolAnnotations` model: -**Annotation Types:** +| Annotation | Meaning | +|------------|---------| +| **title** | Human-readable tool title | +| **readOnlyHint** | Tool reports that it does not modify its environment | +| **destructiveHint** | Tool may perform destructive updates | +| **idempotentHint** | Repeated calls with the same arguments have no additional effect | +| **openWorldHint** | Tool may interact with external entities | -| Annotation | Meaning | Use Case | -|------------|---------|----------| -| **readOnlyHint** | Tool doesn't modify state | Lower security risk | -| **destructiveHint** | Tool modifies/deletes data | Require confirmation | -| **progressEnabled** | Tool reports progress | Show progress UI | +When `readOnlyHint` is true, the MCP schema adapter omits the additional +`security_risk` prediction field from the LLM-facing schema. These annotations +are hints, not enforcement guarantees. `destructiveHint` does not by itself +require confirmation: confirmation depends on the configured security analyzer +and confirmation policy. See [Security](/sdk/arch/security). -These annotations feed into the security analyzer for risk assessment. +The SDK's `ToolAnnotations` model does not define `progressEnabled`. ## Component Relationships @@ -415,7 +373,7 @@ flowchart LR - **Skills → MCP**: Repository skills can embed MCP configurations - **MCP → Tools**: MCP tools registered alongside native tools - **Agent → Tools**: Agents use MCP tools like any other tool -- **MCP → Security**: Annotations inform security risk assessment +- **MCP → Security**: Read-only hints affect risk-prediction schema generation; the configured policy governs confirmation - **Transparent Integration**: Agent doesn't distinguish MCP from native tools ## Design Rationale @@ -428,7 +386,7 @@ flowchart LR **FastMCP Foundation:** Building on FastMCP (MCP SDK for Python) provides battle-tested client implementation, protocol compliance, and ongoing updates as MCP evolves. -**Annotation Support:** Exposing MCP hints (readOnly, destructive) enables intelligent security analysis and user confirmation flows based on tool characteristics. +**Annotation Support:** MCP hints are preserved as tool metadata. Read-only hints affect risk-prediction schema generation, while confirmation is controlled by the configured policy. **Lifecycle Management:** Automatic spawn/cleanup of MCP servers in conversation lifecycle ensures resources are properly managed without manual bookkeeping.