> ## Documentation Index
> Fetch the complete documentation index at: https://comfyui-mcp.artokun.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Hôtes tiers

> Construisez votre propre frontend (panneau Blender, extension navigateur, une autre appli) sur le même protocole d'appairage que l'appli mobile — un agent, un contexte partagé, un client mince. Les formes de messages, les endpoints, les invariants de sécurité, et un prompt à coller pour qu'un LLM échafaude votre adaptateur.

Le panneau agent, l'[appli mobile](/docs/docs/fr/mobile), et tout outil qui
s'appaire à une session en cours parlent **un petit protocole WebSocket**
vers l'écouteur d'appairage de l'orchestrateur. Un **hôte tiers** est
n'importe quel client que vous construisez sur ce protocole — un panneau
Blender, une extension navigateur, un CLI, un autre éditeur — qui
**s'attache à un onglet bureau en direct et pilote sa session d'agent**.
Même agent, même contexte, pas de second processus Claude Code, rien de
plus à installer pour l'utilisateur.

<Note>
  C'est exactement la surface sur laquelle l'appli mobile est construite. Si
  vous pouvez ouvrir un WebSocket et envoyer du JSON, vous pouvez construire
  un hôte.
</Note>

## Comment ça marche

<Steps>
  <Step title="Le bureau écoute déjà">
    Quand le panneau agent est ouvert, l'orchestrateur fait tourner un
    **écouteur d'appairage filtré par jeton** sur le LAN (voir
    [Endpoints](#endpoints)). Chaque onglet de panneau ouvert est un **onglet
    bureau** avec un `tab_id` stable et une session d'agent en direct.
  </Step>

  <Step title="Votre hôte se connecte avec le jeton d'appairage">
    Ouvrez un WebSocket vers l'URL d'appairage avec le jeton dans la query
    string. Sans jeton valide la connexion est refusée — l'appairage est toute
    la frontière de sécurité.
  </Step>

  <Step title="Lister et s'attacher à un onglet">
    Envoyez `list_tabs` pour découvrir les onglets bureau ouverts, puis
    `attach_tab` pour en mirorer un. Votre hôte **reçoit maintenant l'activité
    de cet onglet** (en flux) et peut **le piloter**.
  </Step>

  <Step title="Piloter la session partagée">
    Envoyez des trames `user_message`. Tant que vous êtes attaché, le serveur
    les route vers l'**onglet miroré** — donc votre message entre dans la
    *même* conversation que l'agent de bureau. C'est ce qui en fait « un
    agent, un contexte partagé » plutôt qu'une seconde session.
  </Step>
</Steps>

## Endpoints

L'écouteur d'appairage est dérivé du port du pont (`COMFYUI_MCP_BRIDGE_PORT`,
défaut **9180**) :

| Port                    | Rôle                                                                                                                                     |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `bridge` (9180)         | Le pont UI panneau ⇄ orchestrateur (la propre connexion du bureau).                                                                      |
| `bridge + 1` (9181)     | La surface HTTP-MCP `panel_*` — **pas** ce protocole.                                                                                    |
| **`bridge + 2` (9182)** | **L'écouteur d'appairage / contrôle à distance auquel vous vous connectez.** Filtré par jeton, lié sur `0.0.0.0` (joignable sur le LAN). |

**URL d'appairage :**

```
ws://<desktop-machine-ip>:9182/?token=<PAIR_TOKEN>
```

* Le jeton est soit **épinglé** par l'utilisateur via
  `COMFYUI_MCP_PAIR_TOKEN` (appairage toujours actif) soit **frappé par
  session** et remis via le flux QR / appairage du panneau. Votre hôte
  l'obtient de la même façon que l'appli mobile : l'utilisateur l'appaire
  une fois.
* **Lié au LAN par défaut**, mais l'exposition publique est intégrée quand
  l'utilisateur la demande : la fenêtre d'appairage du panneau propose un
  mode **Internet** qui ouvre un quick tunnel cloudflared chiffré, et les
  entreprises peuvent le router via un relais auto-hébergé
  (`COMFYUI_MCP_TUNNEL_BACKEND=relay`). Le jeton le filtre dans les deux
  cas.

## Formes de messages

Toutes les trames sont des objets JSON avec un `type`. Les trames
requête/réponse portent un `cid` (id de corrélation) que vous choisissez,
répété sur la réponse correspondante.

### Entrant — hôte → orchestrateur

| `type`         | Champs                                     | Effet                                                                                               |
| -------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| `hello`        | `tab_id`, `headless: true`                 | Enregistrer votre connexion. Les hôtes tiers sont des clients **headless** (pas de canevas propre). |
| `list_tabs`    | `cid`                                      | Demander les onglets bureau ouverts.                                                                |
| `attach_tab`   | `cid`, `target_tab_id`                     | Mirorer + piloter cet onglet bureau. Valide seulement pour un onglet bureau **réel, non-headless**. |
| `detach_tab`   | —                                          | Arrêter de mirorer/piloter ; votre saisie revient à votre propre session (vide).                    |
| `user_message` | `text` (+ vos champs de message habituels) | Un tour dans la conversation de l'onglet **miroré**. Tamponné par le serveur vers l'onglet attaché. |

Tout autre événement de panneau que vous envoyez tant que vous êtes attaché
est de même routé vers l'onglet miroré.

### Sortant — orchestrateur → hôte

| `type`          | Champs                          | Signification                                                                                |
| --------------- | ------------------------------- | -------------------------------------------------------------------------------------------- |
| `tab_list`      | `cid`, `tabs[]`                 | Réponse à `list_tabs` : les onglets bureau attachables.                                      |
| `tab_attached`  | `cid`, `tab_id`, `ok`, `error?` | Réponse à `attach_tab`. `ok:false` + `error` si la cible est périmée/headless.               |
| `mailbox_flush` | trames tamponnées               | Rejeu de tout ce que l'onglet a produit pendant que vous n'aviez pas de connexion en direct. |

Plus l'activité agent en direct de l'onglet miroré (réponses en flux, statut,
cartes), que votre hôte rend.

<Warning>
  **`attach_tab` est autoritaire — vous ne pouvez pas forger la cible.** Le
  serveur écrase tout `tab_id` que vous mettez sur une trame sortante avec
  l'onglet auquel vous vous êtes réellement attaché. Un hôte ne peut jamais
  piloter qu'un onglet auquel il s'est explicitement attaché. C'est
  volontaire ; voir ci-dessous.
</Warning>

## Invariants de sécurité — un hôte DOIT les préserver

Ce sont les garanties qui rendent l'appairage sûr. Construire un hôte qui
les respecte est tout le contrat ; un hôte qui essaie de les contourner est
exactement ce que l'écouteur est conçu pour rejeter.

<Note>
  Préserver ces invariants n'est pas une contrainte sur votre hôte — c'*est*
  la fonctionnalité. Ils empêchent un client de détourner une session à
  laquelle il ne s'est jamais appairé.
</Note>

1. **Barrière de jeton.** L'écouteur refuse toute connexion sans un jeton
   d'appairage valide (`verifyClient`). Ne construisez jamais un flux qui
   livre ou embarque le jeton automatiquement — l'utilisateur appairé, une
   fois, délibérément.
2. **Tamponnage autoritaire de `attach_tab`.** Le serveur, pas le client,
   décide quel onglet vos trames ciblent. Ne vous fiez pas au `tab_id`
   fourni par le client pour le routage ; attachez d'abord, puis envoyez.
3. **Cibles non-headless seulement.** Vous pouvez vous attacher à un vrai
   onglet bureau, jamais à un autre client headless (vous ne pouvez pas
   mirorer un autre téléphone/hôte).
4. **Un onglet à la fois.** S'attacher à B abandonne votre abonnement à A.
   Modélisez un seul miroir actif par connexion.
5. **Genre de socket épinglé.** Le genre d'une connexion (headless vs
   bureau) est fixé à son premier `hello` ; n'essayez pas de le basculer
   pour échapper aux gardes de prise de contrôle.

## Client de référence minimal

```js theme={null}
const token = "<PAIR_TOKEN>";            // obtained via the user's pair flow
const ws = new WebSocket(`ws://192.168.1.50:9182/?token=${token}`);
let cid = 0;

ws.onopen = () => {
  ws.send(JSON.stringify({ type: "hello", tab_id: "myhost:" + crypto.randomUUID(), headless: true }));
  ws.send(JSON.stringify({ type: "list_tabs", cid: ++cid }));
};

ws.onmessage = (ev) => {
  const m = JSON.parse(ev.data);
  if (m.type === "tab_list") {
    // pick a desktop tab and attach to it
    const target = m.tabs[0]?.tab_id;
    if (target) ws.send(JSON.stringify({ type: "attach_tab", cid: ++cid, target_tab_id: target }));
  } else if (m.type === "tab_attached" && m.ok) {
    // now you're driving that tab's session
    ws.send(JSON.stringify({ type: "user_message", text: "Add a KSampler and wire it up." }));
  } else {
    // render streamed agent activity for the mirrored tab
    console.log("from session:", m);
  }
};
```

## Apprendre à un LLM à construire votre adaptateur

Collez le prompt ci-dessous dans Claude, ChatGPT, ou votre agent de code
pour qu'il échafaude un adaptateur d'hôte pour votre plateforme. Il porte
le contrat de protocole complet, donc le modèle n'a pas à deviner.

```text Copy this into your LLM theme={null}
You are building a THIRD-PARTY HOST ("adapter") for comfyui-mcp. A host connects
to a running comfyui-mcp orchestrator over WebSocket, attaches to a live desktop
"tab", and drives that tab's agent session — same agent, shared context, no second
session. Build the adapter for THIS platform: <describe your platform, e.g. a
Blender sidebar panel / a Chrome extension / a Neovim plugin>.

CONNECTION
- WebSocket to:  ws://<desktop-ip>:9182/?token=<PAIR_TOKEN>
  (port = bridge port + 2; bridge default 9180. Token is provided by the user via
  their pairing flow — NEVER hardcode, embed, or auto-provision it.)
- On open, send:  {"type":"hello","tab_id":"<your-unique-id>","headless":true}

DISCOVER + ATTACH
- Send {"type":"list_tabs","cid":1}; you receive {"type":"tab_list","cid":1,"tabs":[...]}.
- Send {"type":"attach_tab","cid":2,"target_tab_id":"<a tab_id from tab_list>"};
  you receive {"type":"tab_attached","cid":2,"tab_id":"...","ok":true|false,"error"?}.
  Only real, non-headless desktop tabs are attachable.

DRIVE
- Send {"type":"user_message","text":"..."} to post a turn into the ATTACHED tab's
  conversation. The server routes it to the mirrored tab automatically.
- Send {"type":"detach_tab"} to stop.

RENDER
- After attaching you receive the mirrored tab's live activity (streamed agent
  replies, status, interactive cards) and a {"type":"mailbox_flush"} replay of
  anything produced while you were disconnected. Render these in your UI.

SECURITY — these are non-negotiable; preserve every one:
1. Only connect with a user-provided pair token; never embed or auto-ship it.
2. Never assume you can target a tab you did not attach_tab to — the server stamps
   the target authoritatively; trust tab_attached.ok, don't spoof tab_id.
3. Attach only to non-headless desktop tabs; never to another headless client.
4. One active attachment per connection (attaching to a new tab drops the old).
5. Do not try to change your connection's kind after the first hello.

DELIVERABLE
- A minimal, working adapter for the platform above: connect → list → attach →
  send a user_message → render streamed replies → detach. Handle reconnects and
  the mailbox_flush replay. Keep the pairing/token handling explicit and
  user-driven.
```

## Enregistrer votre intégration

Vous avez construit quelque chose ? **Enregistrez-le** pour qu'il puisse
être listé et pour que nous puissions vous signaler les changements de
protocole avant qu'ils se livrent :

<Card title="Enregistrer un hôte tiers" icon="plug" href="https://github.com/artokun/comfyui-mcp/issues/new?template=third-party-host.yml">
  Ouvrez le modèle d'enregistrement sur GitHub — nom, plateforme, dépôt, et
  contre quelle version de protocole vous avez construit.
</Card>

<Note>
  **Stabilité :** les trames ci-dessus sont celles sur lesquelles l'appli
  mobile se livre, mais ce n'est pas encore un contrat figé et versionné —
  lisez le source
  ([`src/services/ui-bridge.ts`](https://github.com/artokun/comfyui-mcp/blob/main/src/services/ui-bridge.ts))
  comme autorité, et enregistrez votre hôte pour être prévenu quand les
  formes bougent.
</Note>
