docs: add MCP integration guide
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
# Host-Agent MCP Server Integration
|
||||
|
||||
The host-agent process exposes a Streamable HTTP MCP server on the same
|
||||
port as the local console (default `127.0.0.1:8765`), at path `/mcp`. This
|
||||
lets any MCP-compatible client — Hermes Agent, Claude Desktop, custom
|
||||
scripts using the `mcp` Python SDK — drive devices directly through the
|
||||
same `DeviceManager` the cloud worker uses.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Host-agent built from this repo (see `docs/MACOS_IPHONE_SETUP.md`).
|
||||
- An MCP client that supports the Streamable HTTP transport (mcp SDK
|
||||
1.20+ on the client side).
|
||||
|
||||
## Get the bearer token
|
||||
|
||||
The first time host-agent starts after this feature ships, it generates
|
||||
a random bearer token and writes it to:
|
||||
|
||||
<identity_path.parent>/host_mcp_token.json
|
||||
|
||||
(Default: `tasks/host_mcp_token.json` next to `host_identity.json`.)
|
||||
|
||||
To print it for copy/paste:
|
||||
|
||||
device-host-agent mcp-token
|
||||
|
||||
To rotate: delete the file and restart host-agent. Old tokens stop
|
||||
working immediately.
|
||||
|
||||
## Hermes Agent configuration
|
||||
|
||||
Add to `~/.hermes/config.yaml`:
|
||||
|
||||
```yaml
|
||||
mcp_servers:
|
||||
apex_device:
|
||||
url: "http://127.0.0.1:8765/mcp"
|
||||
headers:
|
||||
Authorization: "Bearer <paste-token-here>"
|
||||
```
|
||||
|
||||
Start (or restart) Hermes. Verify by asking Hermes to list devices:
|
||||
|
||||
> Use the apex_device MCP to list connected devices.
|
||||
|
||||
## Tools exposed
|
||||
|
||||
All 11 device tools from `api/mcp.py`:
|
||||
|
||||
- `take_screenshot(device_id?)`
|
||||
- `tap(x, y, device_id?)`
|
||||
- `swipe(start_x, start_y, end_x, end_y, duration_ms?, device_id?)`
|
||||
- `input_text(text, device_id?)`
|
||||
- `launch_app(app_id, device_id?)`
|
||||
- `find_text(query, device_id?)`
|
||||
- `find_icon(name, device_id?)`
|
||||
- `get_ui_tree(device_id?, include_app_info?)`
|
||||
- `describe_screen(device_id?)`
|
||||
- `list_devices()`
|
||||
- `device_status(device_id)`
|
||||
|
||||
## Concurrency model
|
||||
|
||||
- The cloud worker and MCP clients share the same `DeviceManager`.
|
||||
- Per-device, session-level locking: the first caller (cloud or MCP) to
|
||||
touch a device holds it; the other side sees a busy error.
|
||||
- MCP sessions hold their lock until the session ends OR 60 seconds of
|
||||
inactivity. Cloud assignments hold theirs until the assignment
|
||||
terminates.
|
||||
- The cloud scheduler is told about MCP-held devices via the heartbeat
|
||||
`mcp_busy_device_ids` field, so it normally won't even try to dispatch
|
||||
to them. A 30-second window exists between an MCP acquire and the next
|
||||
heartbeat; during that window cloud may dispatch, and the host-agent
|
||||
will fail-fast the assignment with `failure_reason="device held by an
|
||||
active MCP session"`.
|
||||
|
||||
## Network binding
|
||||
|
||||
The MCP endpoint is bound to the same address as the local console. By
|
||||
default this is `127.0.0.1` (loopback only). To expose on a different
|
||||
interface, set `HOST_AGENT_CONSOLE_BIND_HOST` AND
|
||||
`HOST_AGENT_CONSOLE_ALLOW_NON_LOOPBACK=true` — both are required. This
|
||||
is the same escape hatch the local console uses; there is no MCP-only
|
||||
override.
|
||||
|
||||
## Error responses
|
||||
|
||||
| Condition | HTTP / JSON-RPC | Body |
|
||||
|---|---|---|
|
||||
| Missing/wrong bearer token | HTTP 401 | `{"error": "invalid token"}` + `WWW-Authenticate: Bearer` |
|
||||
| Device busy (cloud) | JSON-RPC `-32000` | `"device X is busy (held by cloud assignment)"`, `data.busy_owner = "cloud_assignment"` |
|
||||
| Device busy (other MCP) | JSON-RPC `-32000` | `"..."`, `data.busy_owner = "mcp_session:<prefix>"` |
|
||||
| Unknown device | JSON-RPC `-32602` | `"unknown device: X"` |
|
||||
| Tool error | JSON-RPC `-32000` | Original exception message |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **`list_devices` returns `[]`**: no devices registered. Use the local
|
||||
console at `http://127.0.0.1:8765/` to add one (Login → Devices).
|
||||
- **`device X is busy` even when cloud console says device is idle**:
|
||||
check whether another MCP session is holding it. The local console
|
||||
dashboard shows active MCP sessions and held device_ids.
|
||||
- **Token verification fails after restart**: confirm you copied the
|
||||
token from the current `host_mcp_token.json`, not an older one.
|
||||
Rotation = delete file + restart.
|
||||
|
||||
## Out of scope (current version)
|
||||
|
||||
- `wait_until_usable` MCP tool: implemented internally but not exposed.
|
||||
MVP callers must handle busy errors themselves.
|
||||
- MCP call history in the local console: only current state is surfaced,
|
||||
not a call log.
|
||||
- Token rotation CLI: use delete-and-restart for now.
|
||||
- Non-loopback binding without explicit opt-in.
|
||||
Reference in New Issue
Block a user