openhands-sdk/openhands/sdk/tool/
Core Responsibilities
The Tool System has four primary responsibilities:- Type Safety - Enforce action/observation schemas via Pydantic models
- Schema Generation - Auto-generate LLM-compatible tool descriptions from Pydantic schemas
- Execution Lifecycle - Validate inputs, execute logic, wrap outputs
- Tool Registry - Discover and resolve tools by name or pattern
Tool System
Architecture Overview
Key Components
Action-Observation Pattern
The tool system follows a strict input-output contract:Action → Observation. The Agent layer wraps these in events for conversation management.
Tool System Boundary:
- Input:
dict[str, Any](JSON arguments) → validatedActioninstance - Output:
Observationinstance with structured result - No knowledge of: Events, LLM messages, conversation state
Tool Definition
Tools are defined using two patterns depending on complexity:Pattern 1: Direct Instantiation (Simple Tools)
For stateless tools that don’t need runtime configuration (e.g.,finish, think):
Components:
- Action - Pydantic model with
visualizeproperty for display - Observation - Pydantic model with
to_llm_contentproperty for LLM - ToolExecutor - Stateless executor with
__call__(action) → observation - ToolDefinition - Direct instantiation with executor instance
Pattern 2: Subclass with Factory (Stateful Tools)
For tools requiring runtime configuration or persistent state (e.g.,execute_bash, file_editor, glob):
Components:
- Action/Observation - Same as Pattern 1
- ToolExecutor - Stateful executor with
__init__()for configuration and optionalclose()for cleanup - MyTool(ToolDefinition) - Subclass with
@classmethod create(conv_state, ...)factory method - Factory Method - Returns sequence of configured tool instances
When to Use Each Pattern:
Tool Annotations
Tools include optionalToolAnnotations based on the Model Context Protocol (MCP) spec that provide behavioral hints to LLMs:
Key Behaviors:
- LLM-based Security risk prediction automatically added for tools with
readOnlyHint=False - Annotations help LLMs reason about tool safety and side effects
Tool Registry
The registry enables dynamic tool discovery and instantiation from tool specifications: Resolution Workflow:- Tool (Spec) - Configuration object with
name(e.g., “BashTool”) andparams(e.g.,{"working_dir": "/workspace"}) - Resolver Lookup - Registry finds the registered resolver for the tool name
- Factory Invocation - Resolver calls the tool’s
.create()method with params and conversation state - Instance Creation - Tool instance(s) are created with configured executors
- Agent Usage - Instances are added to the agent’s tools_map for execution
File Organization
Tools follow a consistent file structure for maintainability:
Benefits:
- Separation of Concerns - Public API separate from implementation
- Avoid Circular Imports - Import
implonly insidecreate()method - Consistency - All tools follow same structure for discoverability
terminal/ for complete implementation
MCP Integration
The tool system supports external tools via the Model Context Protocol (MCP). MCP tools are configured separately from the tool registry via themcp_config field in Agent class and are automatically discovered from MCP servers during agent initialization.
Source: openhands-sdk/openhands/sdk/mcp/
Architecture Overview
Key Components
Sync/Async Bridge
MCP protocol is asynchronous, but SDK tools execute synchronously. The bridge pattern in client.py solves this: Bridge Features:- Background Event Loop - Executes async code from sync contexts
- Timeout Support - Configurable timeouts for MCP operations
- Error Handling - Wraps MCP errors in observations
- Connection Pooling - Reuses connections across tool calls
Tool Discovery Flow
Source:create_mcp_tools() | agent._initialize()
Discovery Steps:
- Spawn Server - Launch MCP server via stdio protocol (using
MCPClient) - List Tools - Call MCP
tools/listendpoint to retrieve available tools - Parse Schemas - Extract tool names, descriptions, and
inputSchemafrom MCP response - Create Definitions - For each tool, call
MCPToolDefinition.create()which:- Creates an
MCPToolExecutorinstance bound to the tool name and client - Wraps the MCP tool metadata in
MCPToolDefinition - Uses generic
MCPToolActionas the action type (NOT dynamic models yet)
- Creates an
- Add to Agent - All
MCPToolDefinitioninstances are added to agent’stools_mapduringinitialize()(bypasses ToolRegistry) - Lazy Validation - Dynamic Pydantic models are generated lazily when:
action_from_arguments()is called (argument validation)to_openai_tool()is called (schema export to LLM)
MCP Server Configuration
MCP servers are configured via themcp_config field on the Agent class. Configuration follows FastMCP config format:
Component Relationships
Relationship Characteristics:- Native → Registry → tools_map: Native tools resolved via
ToolRegistry - MCP → tools_map: MCP tools bypass registry, added directly during
initialize() - tools_map → LLM: Generate schemas describing all available capabilities
- Agent → tools_map: Execute actions, receive observations
- tools_map → Conversation: Read state for context-aware execution
- tools_map → Security: Tool annotations inform risk assessment
See Also
- Agent Architecture - How agents select and execute tools
- Events - ActionEvent and ObservationEvent structures
- Security Analyzer - Action risk assessment
- Skill Architecture - Embedding MCP configs in repository skills
- Custom Tools Guide - Building your own tools
- FastMCP Documentation - Underlying MCP client library

