ee82d6101e
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
145 lines
5.6 KiB
Markdown
145 lines
5.6 KiB
Markdown
# 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
|
|
|
|
- `stdio` transport **requires** `command`
|
|
- `sse` / `streamable-http` transport **requires** `url`
|
|
|
|
## 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.
|
|
|
|
```python
|
|
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.
|