Implement MCPToolAdapter to connect to external MCP servers, enumerate tools, and register them in ToolRegistry with source="mcp". Includes connect/reconnect/disconnect lifecycle with timeout enforcement, input validation on invoke, capability inference, Behave/Robot/ASV tests, and docs/reference/mcp_adapter.md. ISSUES CLOSED: #159
5.6 KiB
MCP Tool Adapter
This document covers the MCP (Model Context Protocol) adapter introduced in M2.3.mcp-runtime: connecting to external MCP servers, discovering tools, invoking them with schema validation, and registering them in the ToolRegistry.
Overview
The MCPToolAdapter bridges any MCP-compatible tool server into the
CleverAgents runtime. It manages the full lifecycle:
| Operation | Method | Description |
|---|---|---|
| Connect | connect(timeout?) |
Start the server process or HTTP session, perform handshake |
| Disconnect | disconnect() |
Clean shutdown of the server connection |
| Reconnect | reconnect(timeout?) |
Disconnect and re-connect; clears cached tool list |
| Discover | discover_tools(filter?) |
Enumerate available tools from the server |
| Invoke | invoke(name, args) |
Call a tool with automatic input validation |
| Register | register_tools(registry, namespace) |
Bulk-register into a ToolRegistry |
Server Configuration
MCPServerConfig Fields
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Server identifier |
transport |
string |
Yes | stdio, sse, or streamable-http |
command |
string |
stdio only | Command to spawn the server process |
args |
list[string] |
No | Command-line arguments |
env |
dict |
No | Environment variables for the server process |
url |
string |
sse/http only | Server URL for remote connections |
headers |
dict |
No | HTTP headers for remote connections |
Validation Rules
stdiotransport requirescommandsse/streamable-httptransport requiresurl
Tool Discovery
After connecting, call discover_tools() to enumerate all tools exposed by
the MCP server. Each returned MCPToolDescriptor includes the tool name,
description, and JSON Schema for inputs.
Filtering
Use MCPToolFilter with include or exclude lists to control which
tools are exposed.
MCPToolDescriptor Fields
| Field | Type | Description |
|---|---|---|
name |
string |
Tool name as exposed by the server |
description |
string |
Human-readable description |
input_schema |
dict |
JSON Schema for tool inputs |
Tool Invocation
Call invoke(name, arguments) to execute a discovered tool. Input arguments
are validated against the tool's JSON Schema before the call is made. If
validation fails, the result is returned immediately with success=False.
MCPToolResult Fields
| Field | Type | Description |
|---|---|---|
success |
bool |
Whether the invocation succeeded |
data |
dict |
Result payload from the server |
error |
string or null |
Error message on failure |
duration_ms |
float |
Wall-clock execution time in milliseconds |
Reconnect
Use reconnect(timeout?) to recover from a dropped connection or after a
server restart. It performs a clean disconnect() followed by a fresh
connect() and clears the cached tool list. Call discover_tools() again
after reconnecting.
try:
adapter.invoke("create_issue", {"title": "Bug"})
except RuntimeError:
# Connection dropped — attempt recovery
adapter.reconnect(timeout=10.0)
adapter.discover_tools()
If the new connection attempt fails or times out, reconnect() raises
ConnectionError.
ToolRegistry Integration
Discovered MCP tools can be bulk-registered into any ToolRegistry via
register_tools(registry, namespace). Each tool is registered with the
name {namespace}/{tool_name}. Re-registration after tool list changes
replaces stale entries automatically.
Registered tools always carry source="mcp" and
source_metadata={"server": "<server-name>"} for provenance tracking.
checkpointable is always False for MCP tools — external server calls
cannot be rolled back transactionally.
Capability Inference
MCP tools expose limited metadata (name, description, inputSchema). The adapter
automatically infers extended ToolCapability metadata using name-based heuristics:
| Tool name keywords | Inferred capability |
|---|---|
read, get, list, search, find |
read_only: true |
write, create, update, delete, set |
writes: true |
| Both read and write keywords | writes: true (write takes precedence) |
| Neither | read_only: false, writes: false |
Inferences are applied automatically when registering tools via
register_tools(). They can be overridden via the overrides block in
tool or skill YAML.
Use MCPToolAdapter.infer_capabilities(tool_name) directly to check
what would be inferred for a given tool name.
Error Classification
| Error Type | When | Exception / Field |
|---|---|---|
| Connection failure | connect() with unreachable server |
ConnectionError |
| Not connected | invoke() / discover_tools() before connect() |
RuntimeError |
| Tool not found | invoke() with unknown tool name |
result.error contains "not found" |
| Validation failure | invoke() with invalid input |
result.error contains "validation" |
| Timeout | Server call exceeds deadline | result.error contains "timeout" |
| Server error | Server returns isError: true |
result.error contains "server error" |
Transport Layer
The MCPTransport base class defines the abstract interface. Concrete
implementations handle stdio (child process) and HTTP (SSE / streamable-http)
protocols. For testing, provide a mock transport to the adapter constructor.
Thread Safety
MCPToolAdapter uses threading.RLock internally. All public methods are
safe to call from multiple threads concurrently.