This is the exact surface the mobile app is built on. If you can open a WebSocket
and send JSON, you can build a host.
How it works
1
The desktop is already listening
When the Agent Panel is open, the orchestrator runs a token-gated pairing
listener on the LAN (see Endpoints). Each open panel tab is a
desktop tab with a stable
tab_id and a live agent session.2
Your host connects with the pairing token
Open a WebSocket to the pairing URL with the token in the query string. Without a
valid token the connection is refused — pairing is the whole security boundary.
3
List and attach to a tab
Send
list_tabs to discover the open desktop tabs, then attach_tab to mirror
one. Your host now receives that tab’s activity (streamed) and can drive it.4
Drive the shared session
Send
user_message frames. While attached, the server routes them to the
mirrored tab — so your message enters the same conversation the desktop
agent is in. That’s what makes it “one agent, shared context” rather than a
second session.Endpoints
The pairing listener is derived from the bridge port (COMFYUI_MCP_BRIDGE_PORT,
default 9180):
Pairing URL:
- The token is either pinned by the user via
COMFYUI_MCP_PAIR_TOKEN(always-on pairing) or minted per session and handed out through the panel’s QR / pair flow. Your host obtains it the same way the mobile app does: the user pairs it once. - Bound to the LAN only. There is no built-in public exposure — if a user wants remote reach they front it with their own tunnel, and the token still gates it.
Message shapes
All frames are JSON objects with atype. Request/response frames carry a cid
(correlation id) you choose, echoed back on the matching reply.
Inbound — host → orchestrator
Any other panel event you send while attached is likewise routed to the mirrored
tab.
Outbound — orchestrator → host
Plus the mirrored tab’s live agent activity (streamed replies, status, cards),
which your host renders.
Security invariants — a host MUST preserve these
These are the guarantees that make pairing safe. Building a host that respects them is the whole contract; a host that tries to bypass them is exactly what the listener is designed to reject.Preserving these isn’t a constraint on your host — it is the feature. They stop
a client from hijacking a session it never paired to.
- Token gate. The listener refuses any connection without a valid pair token
(
verifyClient). Never build a flow that ships or embeds the token automatically — the user pairs, once, deliberately. - Authoritative
attach_tabstamping. The server, not the client, decides which tab your frames target. Don’t rely on client-suppliedtab_idfor routing; attach first, then send. - Non-headless targets only. You may attach to a real desktop tab, never to another headless client (you can’t mirror another phone/host).
- One tab at a time. Attaching to B drops your subscription to A. Model a single active mirror per connection.
- Pinned socket kind. A connection’s kind (headless vs desktop) is fixed on
its first
hello; don’t try to flip it to escape the takeover guards.
Minimal reference client
Teach an LLM to build your adapter
Paste the prompt below into Claude, ChatGPT, or your coding agent to have it scaffold a host adapter for your platform. It carries the full protocol contract, so the model doesn’t have to guess.Copy this into your LLM
Register your integration
Built something? Register it so it can be listed and so we can flag protocol changes to you before they ship:Register a third-party host
Open the registration template on GitHub — name, platform, repo, and which
protocol version you built against.
Stability: the frames above are what the mobile app ships on, but this is not
yet a frozen, versioned contract — read the source
(
src/services/ui-bridge.ts)
as the authority, and register your host so you’re notified when the shapes move.