116 lines
4.2 KiB
Markdown
116 lines
4.2 KiB
Markdown
# 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.
|