Appearance
The AI-control plane
hopbox ships an MCP control plane: the same box fleet a human reaches over SSH, an AI drives over the Model Context Protocol. It is event-driven (subscribe and react to pushed changes; never poll) and shares the daemon's engine — so an AI spawns real boxes, not a simulation.
Enable it on the daemon with --mcp-addr (the installer defaults it on, at unix:/run/hopboxd-mcp.sock).
Resources (subscribe, then react to notifications/resources/updated)
| URI | What |
|---|---|
hopbox://fleet | Every box with its live phase and the agent's self-reported status. |
hopbox://surface/<name>/events | A rendered surface's interaction events (the canvas loop). |
hopbox://images | The box image catalog with a per-image skill summary — pick one for box_delegate / fleet_apply. |
hopbox://guide | This plane's own how-to, also delivered as the MCP instructions. |
Tools
A box ref below is a box id from hopbox://fleet.
Fan work out
| Tool | What |
|---|---|
box_delegate {task, agent?, keep?, ttl?} | Spawn a box, run a task in the background; the result surfaces on hopbox://fleet. agent:true runs a Claude Code agent (on your token). Fire-and-forget by default — the box is archived (disk freed, record kept) when done; keep:true leaves it persistent + resumable. ttl:"30m" sets a hard deadline (see below). |
fleet_apply {boxes:[{key,image,task,ttl?}]} | Declare a desired set of task-boxes; hopbox converges to it, idempotent per key. Prefer over many box_delegate calls. |
box_spawn {name?, ttl?} / fleet_get | Spawn an empty box / snapshot the fleet. |
Bound work you aren't watching
ttl is a deadline from creation, enforced by the daemon. The box is torn down when it expires whatever it is doing — it beats an attached session, a keep-alive pin and the durable flag, so an agent that loops or a build that wedges stops costing you at a time you chose. It also survives the orchestrator that started the box crashing, which a timeout held in your own process cannot.
Set it on any delegation you won't be watching. Omit it and your tier decides: a fire-and-forget delegation gets its tier's default, and anything you ask for is clamped to the tier ceiling.
Drive a box (everything a human does over SSH, over MCP)
| Tool | What |
|---|---|
box_exec {box, cmd, timeout_s?} | Run a command and get the output back in the call — stdout/stderr/exit, synchronously. Auto-resumes a suspended box. The workhorse. |
box_write {box, path, content} · box_read {box, path} | Stage a file into a box / read one out (bytes-safe). |
box_suspend · box_resume · box_rm {box} | Lifecycle. An AI cleans up and checkpoints its own fleet. |
session_start {box, prompt?, model?, effort?} / session_send / session_list / session_stop | A persistent, conversational agent session in a box; events stream on hopbox://session/<id>/events?from=<seq> (cursor-replayable — reattach from anywhere, and both sides of the conversation are in the buffer). model / effort are fixed for the session — see model and effort. |
Share state across boxes — the shared workspace as the AI's hand-off drive
| Tool | What |
|---|---|
wrk_write {path, content, ws?} · wrk_read {path, ws?} · wrk_list {path?, ws?} | The plane's /wrk: box A writes a shard, the orchestrator reads it, box B picks it up. Pass ws to target a specific workspace's drive (omit = default). The same drive the plane's boxes in that workspace mount at /wrk. |
Talk to a human
| Tool | What |
|---|---|
surface_render {name, html} | Render an interactive canvas at a URL — the canvas loop, below. Arbitrary HTML, fire-and-forget. |
ask_answer {id, choice} + hopbox://asks | Answer an ask — a structured question an (often in-box) agent raised and is blocked on. hopbox://asks lists the pending ones; answering unblocks the agent. The request/response, form-shaped cousin of surface_render. |
An AI can drive a box entirely without a human: box_exec to run, box_read / wrk_read to collect, surface_render to report. Everything is owner-scoped — the plane drives only its own boxes, never a human's. On connect, the server sends an instructions guide teaching all of the above.
The canvas loop
When an AI needs a human's decision, input, or attention, it isn't limited to chat — it can render an interactive UI and watch the human use it, live:
surface_render {name:"approve", html:"<button id=ok>Approve</button>"}returns a URL. The AI gives it to the human.- The AI subscribes to
hopbox://surface/approve/events. - Each click/input is pushed to the AI as
{kind, target, value}— it reacts: re-render, branch its work, or unblock a waiting task.
Serve surfaces over HTTP with --surface-addr — on the live host they appear at box.hopbox.dev/s/…. The loop is bidirectional: the AI renders, the human acts, the AI observes.
Your own plane — ssh cli@host mcp
The plane is yours, by SSH key — like every other hopbox surface. Point any MCP client at hopbox using ssh as its transport; your key is your login, and the plane binds to your identity.
Claude Code — one command:
sh
claude mcp add hopbox -- ssh -T -o StrictHostKeyChecking=accept-new cli@box.hopbox.dev mcp
claude mcp list # hopbox … ✔ ConnectedClaude Desktop (and any stdio MCP client) — add to its config (claude_desktop_config.json → mcpServers):
jsonc
{
"hopbox": {
"command": "ssh",
"args": ["-T", "-o", "StrictHostKeyChecking=accept-new", "cli@box.hopbox.dev", "mcp"]
}
}The flags matter: -T runs ssh without a pty (the plane is a byte stream), and StrictHostKeyChecking=accept-new accepts the host key on first use instead of hanging on the interactive "continue connecting?" prompt — the #1 reason a GUI MCP client silently never starts.
"Permission denied (publickey)" — esp. Claude Desktop on macOS
Test the transport from a terminal first: ssh cli@box.hopbox.dev ls. If that lists your boxes but the app logs Permission denied (publickey), the app launched sshwithout SSH_AUTH_SOCK — a GUI app doesn't inherit your shell's environment, so ssh can't reach the agent holding your key (and if your key is agent-only, there's no file to fall back to). Recover the agent with a one-line wrapper and point the server's command at it:
sh
# ~/.hopbox/mcp-ssh (then: chmod +x ~/.hopbox/mcp-ssh)
#!/bin/sh
[ -n "$SSH_AUTH_SOCK" ] || export SSH_AUTH_SOCK="$(launchctl getenv SSH_AUTH_SOCK)"
exec /usr/bin/ssh "$@"jsonc
{ "hopbox": {
"command": "/Users/you/.hopbox/mcp-ssh",
"args": ["-T", "-o", "StrictHostKeyChecking=accept-new", "cli@box.hopbox.dev", "mcp"] } }If your hopbox identity is instead a key file, skip the wrapper and add "-i", "/path/to/key" to the args (it must be the key that owns your boxes — a different key gets a different, empty fleet). Can't use SSH at all (a browser, a locked-down client)? Use the hbx_ WebSocket transport below.
hopbox://fleet then shows only your boxes; box_spawn/delegate create boxes you own; wrk.* is your /wrk; and rendered surfaces are yours. A different key gets a different, fully isolated plane. Because your boxes are yours, an agent box delegated from here runs on your stored token — box_delegate {task, agent:true} just works, no operator key.
From a browser or a third-party app — anything that can't present an SSH key — connect over a WebSocket with your hbx_ key instead: wss://host/v1/mcp?key=hbx_…. Same per-owner plane, same tools; it is what the web console is built on. Workspaces (ws.*) and secrets (secret.*, values never returned) are on the plane too, so a UI can manage them.
The client — hopbox-mcp
sh
hopbox-mcp --ssh cli@box.hopbox.dev # your plane over SSH (wraps the above)
hopbox-mcp ps --connect unix:/run/hopboxd-mcp.sock # the operator/local plane (root socket)
hopbox-mcp watch --connect <sock> hopbox://surface/<name>/events # print interactions live
hopbox-mcp --demo # self-drive a demo against box.hopbox.devThe --connect socket is the operator/local plane (its own owner); the ssh cli@host mcp path is the per-customer one.
Humans get the same engine
Everything here has a human twin over SSH — ssh cli@host lists and removes boxes, key-authed, zero-install. One fleet, one engine, two front doors.