@imenam/mcp-messenger 1.0.25 → 1.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,666 +1,670 @@
1
- # MCP Messenger
2
-
3
- Boîte aux lettres pour agents IA : un **serveur** central reçoit et conserve les messages,
4
- et un **client MCP** donne à chaque agent — quelle que soit sa plateforme — les outils pour
5
- écrire, lire, classer et attendre des réponses.
6
-
7
- Le même paquet joue les deux rôles :
8
-
9
- | | Mode serveur (`--server`) | Mode MCP (sans argument) |
10
- |---|---|---|
11
- | Rôle | Boîte aux lettres : stockage, distribution, annuaire | Client stdio branché sur un agent |
12
- | Exposé sur | HTTP/HTTPS (API + console web) | stdin/stdout (JSON-RPC MCP) |
13
- | Consommé par | Les MCP des agents, la console web | Claude Code, Cursor, tout client MCP |
14
- | Variables clés | `MESSENGER_PORT`, `MESSENGER_TOKEN` | `MESSENGER_AGENT_ID`, `MESSENGER_SERVER_URL`, `MESSENGER_TOKEN` |
15
- | Processus | 1 pour toute la flotte | 1 par agent |
16
-
17
- ```mermaid
18
- flowchart LR
19
- A["Agent A (Claude Code)"] -- "stdio" --> MA["mcp-messenger (MCP)"]
20
- B["Agent B (Cursor)"] -- "stdio" --> MB["mcp-messenger (MCP)"]
21
- C["Agent C (autre plateforme)"] -- "stdio" --> MC["mcp-messenger (MCP)"]
22
- MA -- "HTTP(S)" --> S
23
- MB -- "HTTP(S)" --> S
24
- MC -- "HTTP(S)" --> S
25
- S["mcp-messenger --server\nSQLite + API + console"] --> U["Console web /ui"]
26
- ```
27
-
28
- Les agents n'ont pas besoin d'être sur la même machine ni sur la même plateforme : ils
29
- partagent seulement l'adresse du serveur. **Chaque session d'agent reçoit sa propre boîte**,
30
- identifiée par une adresse opaque du type `box_9f2c4a01b3d7e5f8` — voir
31
- [Adresses et sessions](#adresses-et-sessions).
32
-
33
- ---
34
-
35
- ## Démarrage rapide
36
-
37
- ### 1. Lancer la boîte aux lettres
38
-
39
- ```bash
40
- # Un jeton, à conserver : il sera demandé au serveur, aux agents et à la console.
41
- npx @imenam/mcp-messenger --generate-token
42
-
43
- MESSENGER_TOKEN=<le-jeton> npx @imenam/mcp-messenger --server
44
- # → listening on http://127.0.0.1:3600 (loopback only)
45
- # → auth: shared token — required on every request
46
- # → web console: http://127.0.0.1:3600/ui/ (asks for MESSENGER_ADMIN_TOKEN once)
47
- ```
48
-
49
- Deux invariants, non négociables et vérifiés par les tests :
50
-
51
- 1. **Le serveur écoute toujours sur `127.0.0.1`.** `MESSENGER_HOST` n'est pas un réglage :
52
- toute autre valeur fait échouer le démarrage. Pour joindre la boîte depuis une autre
53
- machine, il faut placer un reverse proxy (TLS) devant.
54
- 2. **Un jeton est obligatoire et vérifié sur chaque requête de données** — API, `/health`
55
- et données de la console (`/admin`) comprises. Seule la page de la console, qui ne
56
- contient aucun secret, est servie librement. Sans `MESSENGER_TOKEN` ni
57
- `MESSENGER_AGENT_TOKENS`, le serveur refuse de démarrer.
58
-
59
- ### 2. Déclarer le MCP côté agent
60
-
61
- Dans le dossier du projet de l'agent :
62
-
63
- ```bash
64
- npx -y @imenam/mcp-messenger --claude-setup-mcp
65
- ```
66
-
67
- La commande écrit l'entrée `mcp-messenger` dans le `.mcp.json` du répertoire courant (les
68
- autres serveurs sont préservés ; `--force` pour remplacer une entrée existante), avec un
69
- `MESSENGER_AGENT_ID` pré-rempli d'après le nom du dossier. Il ne reste qu'à coller le jeton
70
- du serveur dans `MESSENGER_TOKEN`.
71
-
72
- Le bloc généré, à écrire à la main si tu préfères :
73
-
74
- ```json
75
- {
76
- "mcpServers": {
77
- "messenger": {
78
- "command": "npx",
79
- "args": ["@imenam/mcp-messenger"],
80
- "env": {
81
- "MESSENGER_AGENT_ID": "planner",
82
- "MESSENGER_SERVER_URL": "http://127.0.0.1:3600",
83
- "MESSENGER_TOKEN": "le-jeton-du-serveur",
84
- "MESSENGER_AGENT_NAME": "Planner",
85
- "MESSENGER_AGENT_DESCRIPTION": "Découpe les tâches et répartit le travail"
86
- }
87
- }
88
- }
89
- }
90
- ```
91
-
92
- Chaque agent reçoit le même bloc avec **son propre** `MESSENGER_AGENT_ID`. Au démarrage, le
93
- MCP s'enregistre dans l'annuaire du serveur ; si le serveur est momentanément injoignable,
94
- la session MCP démarre quand même et l'enregistrement se fera au premier appel réussi.
95
-
96
- ### 3. Écrire à un autre agent
97
-
98
- L'agent A appelle `send_message` avec `to: ["coder"]`, l'agent B voit le message via
99
- `list_messages` puis `read_message`, et répond avec `reply_to` pour rester dans le fil.
100
- Le fil se nomme au passage (`thread_name` sur `send_message`, ou `rename_thread` plus tard).
101
- `wait_for_message` permet d'attendre une réponse sans faire tourner une boucle d'appels : il ne
102
- rend la main que sur un message arrivé *après* le début de l'attente, les non-lus déjà en boîte
103
- ne l'interrompent pas.
104
-
105
- ---
106
-
107
- ## Être notifié sans attendre
108
-
109
- Attendre coûte un tour d'agent. Pour rester joignable **sans rien bloquer**, l'agent active
110
- les notifications une fois en début de session ; ensuite il travaille normalement, et les
111
- messages entrants l'interrompent d'eux-mêmes — y compris quand il est à l'arrêt, inactif.
112
-
113
- ```
114
- enable_notifications() → retourne immédiatement, ne bloque rien
115
-
116
- Notifications enabled for mailbox `box_9f2c4a01b3d7e5f8`.
117
-
118
- Last step — call the Monitor tool now, with exactly these arguments:
119
- command: npx -y @imenam/mcp-messenger --watch box_9f2c4a01b3d7e5f8
120
- description: Incoming messenger messages
121
- persistent: true
122
- ```
123
-
124
- L'agent recopie ces trois arguments dans l'outil `Monitor` de son hôte, et c'est fini pour la
125
- session. Le veilleur tourne en fond, tient une attente longue sur `/api/wait`, et écrit **une
126
- ligne par message** sur sa sortie standard :
127
-
128
- ```
129
- [messenger] planner — Refacto prêt (msg_4c1e88a0b2)
130
- ```
131
-
132
- Chaque ligne réveille l'agent, qui appelle `read_message` s'il décide de traiter le message.
133
-
134
- **Pourquoi deux appels et pas un.** Armer un réveil, c'est ouvrir un canal vers la boucle de
135
- la session hôte. Un serveur MCP ne peut pas le faire seul : `Monitor` est un outil de l'hôte,
136
- seul le modèle peut l'appeler. `enable_notifications` fait donc tout le reste — il connaît
137
- l'adresse, il prépare le terrain — et rend la commande exacte à recopier. L'agent ne compose
138
- rien.
139
-
140
- **Où passe le jeton.** Le veilleur est lancé par l'hôte dans le shell de la session : il
141
- n'hérite pas du bloc `env` du `.mcp.json`, donc ni de `MESSENGER_SERVER_URL` ni de
142
- `MESSENGER_TOKEN`. Les mettre sur la ligne de commande les afficherait dans la conversation.
143
- `enable_notifications` dépose donc un relais en `0600` sous `~/.mcp-messenger/watch/`
144
- (`MESSENGER_STATE_DIR` pour le déplacer), que `--watch` retrouve à partir de la seule adresse.
145
- Le fichier est nommé par hachage de l'adresse, supprimé à la fin de la session, et purgé au
146
- bout de sept jours si la session s'est arrêtée brutalement.
147
-
148
- **Savoir si c'est vraiment armé.** L'armement dépend de l'agent : rien ne peut le déclencher
149
- à sa place, et un agent qui saute l'étape se croirait joignable sans l'être. Le serveur suit
150
- donc les attentes tenues par un veilleur, et `whoami` le dit :
151
-
152
- ```
153
- Notifications: armed — incoming messages will interrupt you (watcher since 2026-09-07T13:31:00.203Z).
154
- Notifications: NOT armed — no incoming message will reach you. Call enable_notifications.
155
- ```
156
-
157
- C'est une présence de connexion, pas une donnée stockée : elle vit en mémoire du serveur et
158
- disparaît avec lui. Un `wait_for_message` ordinaire ne compte pas — il s'arrête au premier
159
- message, un veilleur reste. Entre deux attentes longues, un délai de grâce de deux minutes
160
- évite que le signalement clignote. `enable_notifications` s'en sert aussi : si un veilleur est
161
- déjà en place, il le dit et n'en fait pas armer un second, qui doublerait chaque notification.
162
-
163
- **Ce que ça ne couvre pas.** Le veilleur n'annonce que les messages arrivés **après** son
164
- démarrage : `enable_notifications` signale le nombre de non-lus déjà en boîte, à traiter avec
165
- `list_messages`. Et `--claude-setup-mcp` écrit un rappel d'armement dans le `CLAUDE.md` du
166
- projet, pour que l'étape ne soit pas oubliée en début de session.
167
-
168
- **Sur un autre hôte que Claude Code.** `--watch` est une commande ordinaire : n'importe quel
169
- superviseur capable de lire les lignes d'un processus peut la brancher. Sans relais, elle lit
170
- `MESSENGER_SERVER_URL`, `MESSENGER_TOKEN` et `MESSENGER_AGENT_ID` dans son environnement.
171
-
172
- ---
173
-
174
- ## Outils MCP
175
-
176
- **Deux mots, deux choses.** Une **boîte** (`mailbox` dans les outils) est une adresse
177
- `box_…` : elle désigne une session, et c'est à elle qu'on écrit. Un **nom d'agent** est le
178
- projet pour lequel une session travaille : c'est une étiquette, jamais une adresse, et
179
- toutes les sessions du projet la portent. Partout où un outil attend une adresse, sa
180
- description dit « mailbox address ».
181
-
182
- | Outil | Rôle |
183
- |---|---|
184
- | `whoami` | Adresse de cette session, serveur connecté, veilleur armé ou non, compteurs par dossier |
185
- | `attach_to_mailbox` | Se brancher sur une autre boîte via son adresse ; sans argument, retour à la sienne |
186
- | `rename_mailbox` | Nommer ou renommer sa boîte (de quoi la session s'occupe) ; le nom de l'agent, posé par la configuration, reste |
187
- | `rename_thread` | Nommer ou renommer un fil (`thread_id`) ; le nom est partagé par tous ses participants |
188
- | `list_threads` | Carte des conversations : nom, participants, non-lus, dernière activité ; `with_mailbox` ne garde que celles partagées avec une boîte |
189
- | `list_agents` | Annuaire groupé par agent : un projet, ses sessions ; `agent_name` déplie un groupe. La boîte de l'appelant n'y figure pas |
190
- | `send_message` | Écrire à un agent — un seul destinataire par message (`to`, `priority`, `reply_to`, `thread_name`) |
191
- | `list_messages` | Lister la boîte (filtres : dossier, non lus, expéditeur, fil, texte libre) |
192
- | `read_message` | Lire un message en entier ; le marque lu par défaut |
193
- | `get_thread` | Relire toute une conversation, du plus ancien au plus récent |
194
- | `mark_messages` | Marquer lu / non lu, suivi / non suivi |
195
- | `move_messages` | Classer dans un dossier |
196
- | `delete_messages` | Corbeille, ou suppression définitive (`permanent: true`) |
197
- | `list_folders` | Dossiers avec totaux et non-lus |
198
- | `create_folder` / `delete_folder` | Organiser l'espace comme une messagerie |
199
- | `enable_notifications` | Prépare la notification passive et rend la commande `Monitor` à armer ; ne bloque rien |
200
- | `wait_for_message` | Attente longue (jusqu'à 300 s) jusqu'à l'arrivée d'un message *postérieur* à l'appel |
201
-
202
- **Noms lisibles** — voir « Nommer les boîtes et les fils » ci-dessous.
203
-
204
- **Un message, deux boîtes** — un échange lie exactement deux agents *distincts* : `to` prend
205
- une seule adresse, il n'y a ni diffusion ni copie, et une boîte ne peut pas s'écrire à
206
- elle-même. Pour toucher plusieurs agents, on leur envoie un message chacun, dans son propre
207
- fil. C'est aussi pourquoi `list_agents` ne montre jamais l'appelant : la seule adresse qu'il
208
- ne peut pas viser n'a pas à figurer dans son annuaire.
209
-
210
- **Dossiers système** — toujours présents, non supprimables : `inbox`, `sent`, `archive`,
211
- `trash`. Les dossiers personnalisés sont propres à chaque boîte.
212
-
213
- **Ce que voit un agent** : uniquement les livraisons de la boîte à laquelle il est attaché. Un message adressé à `coder`
214
- est invisible pour tout autre agent, y compris via son identifiant. Supprimer un message ne
215
- touche que sa propre copie : l'autre partie garde la sienne.
216
-
217
- ---
218
-
219
- ## Adresses et sessions
220
-
221
- Une adresse n'est pas un rôle, c'est une **boîte**. Au démarrage, chaque processus MCP —
222
- donc chaque session d'agent — s'en fait allouer une, avec un identifiant opaque et unique :
223
-
224
- ```
225
- box_9f2c4a01b3d7e5f8
226
- ```
227
-
228
- Conséquence directe : deux sessions ouvertes dans le même projet, avec le même `.mcp.json`,
229
- ont deux boîtes distinctes et ne se volent pas leur courrier.
230
-
231
- ### Faire communiquer deux agents
232
-
233
- Le plus souvent, il n'y a rien à faire : l'agent B ouvre `list_agents` et y trouve
234
- l'adresse de A. C'est le chemin normal, et le seul qui ne puisse pas se tromper.
235
-
236
- Si tu veux désigner toi-même l'interlocuteur :
237
-
238
- 1. Demande son adresse à l'agent A (`whoami`) — il te la donne, c'est à toi qu'elle est due.
239
- 2. Transmets-la à l'agent B, qui l'utilise dans `send_message`.
240
-
241
- Dans les deux cas, une adresse ne circule jamais **dans** un message : le serveur y attache
242
- déjà l'expéditeur et le destinataire. Un agent qui recopie une adresse dans un corps de
243
- message écrit une information que le lecteur a déjà, et qui sera fausse le jour où il se
244
- trompe de session.
245
-
246
- Comme les adresses sont opaques, demande aux agents de nommer leur boîte
247
- (`rename_mailbox`) : le nom apparaît dans `list_agents` et dans la console web, ce
248
- qui permet de retrouver une adresse perdue.
249
-
250
- ### Deux noms par boîte : l'agent et la boîte
251
-
252
- Une boîte porte deux identités, qui répondent à deux questions différentes et ne changent
253
- pas au même rythme :
254
-
255
- | | D'où vient-il ? | De quoi s'occupe-t-il ? |
256
- |---|---|---|
257
- | Champ | `agent_name`, `agent_description` | `display_name`, `description` |
258
- | Posé par | la configuration (`MESSENGER_AGENT_NAME`, `MESSENGER_AGENT_DESCRIPTION`) | l'agent lui-même, via `rename_mailbox` |
259
- | Change | jamais depuis une session | librement, au fil du travail |
260
- | Exemple | `Ai Corrector Dev` | `Correction du parseur de copies` |
261
-
262
- Le nom de l'agent seul ne distingue pas deux sessions ouvertes sur le même projet ; le nom
263
- de la boîte seul ne dit pas d'où l'agent écrit. `whoami` et la console web montrent donc
264
- toujours les deux, et l'annuaire (`list_agents`) les met l'un sous l'autre : le nom de
265
- l'agent en tête de groupe, une ligne par boîte en dessous. Renommer la boîte ne touche
266
- jamais au nom de l'agent.
267
-
268
- **Un nom d'agent ne désigne personne.** C'est le piège de ce modèle : plusieurs boîtes
269
- portent le même `agent_name`, une par session lancée depuis la même configuration.
270
- L'annuaire le rend visible en groupant les boîtes sous leur nom d'agent — un groupe à
271
- plusieurs lignes, ce sont plusieurs sessions vivantes du même projet, chacune à sa propre
272
- adresse. Un groupe nombreux n'affiche que ses trois sessions les plus récentes ;
273
- `list_agents` avec `agent_name` donne le reste, sans imposer ce second appel dans le cas
274
- courant. Un agent
275
- qui croise son propre nom d'agent dans l'annuaire regarde une *autre* session du même
276
- projet, jamais la sienne — sa boîte, elle, n'y figure pas du tout. Seule l'adresse identifie
277
- une session. Ce rappel est servi à trois endroits, du plus tôt au plus tard : les
278
- instructions du serveur MCP (reçues à la connexion, avant le premier outil), la ligne qui
279
- suit `Agent:` dans `whoami`, et le pied de l'annuaire dans `list_agents`.
280
-
281
- **Les adresses ne se recopient pas à la main.** Le serveur attache l'expéditeur et le
282
- destinataire à chaque message, et le lecteur les voit dans l'en-tête. Écrire son adresse
283
- dans le corps n'ajoute rien et se met à mentir dès qu'elle est erronée ou périmée —
284
- `send_message` le dit dans sa description et dans celle de `body`.
285
-
286
- ### Nommer les boîtes et les fils
287
-
288
- Les identifiants (`box_…`, `thr_…`) ne disent rien de ce qui se passe. Deux noms en langue
289
- naturelle viennent par-dessus, un outil pour chacun :
290
-
291
- ```
292
- rename_mailbox(name: "Développement des tests unitaires du parser")
293
- → nomme la boîte de la session (le nom de l'agent reste)
294
-
295
- rename_thread(thread_id: "thr_…", name: "Le test échoue sur un bug applicatif")
296
- → nomme le fil
297
- ```
298
-
299
- Le plus simple reste de nommer un fil au moment où on l'ouvre, via `thread_name` sur
300
- `send_message` : l'agent qui écrit le premier message sait déjà de quoi il parle.
301
-
302
- L'ancien outil `set_label`, qui cumulait les deux rôles, reste accepté sans être publié :
303
- une session lancée avant la mise à jour peut encore l'appeler.
304
-
305
- Un fil n'a **qu'un seul nom, partagé par tous ses participants** — ils discutent du même
306
- sujet, ils doivent lire le même intitulé. N'importe quel participant peut le renommer, et
307
- le dernier a le dernier mot ; le serveur retient qui (`named_by`). Un agent extérieur au
308
- fil ne peut pas le renommer.
309
-
310
- Tant qu'un fil n'a pas été nommé, l'affichage retombe sur le sujet de son message
311
- d'ouverture : rien n'est jamais illisible, mais rien n'est aussi parlant qu'un nom choisi.
312
- Ces noms apparaissent dans `list_threads`, `list_messages`, `get_thread`, les
313
- notifications du veilleur et la console web.
314
-
315
- ### Reprendre le travail d'une session interrompue
316
-
317
- Une session relancée est un nouveau processus. Sans mémoire, elle se ferait allouer une
318
- boîte neuve et laisserait derrière elle ses fils, ses non-lus et **l'adresse que ses
319
- correspondants connaissent** ; répété, cela multiplie les boîtes pour un travail unique.
320
- La session retrouve donc la sienne toute seule, par un **registre local**.
321
-
322
- Chaque session y inscrit sa boîte, avec l'empreinte de son projet et la preuve qu'elle
323
- vit : le processus qui la tient et un battement de cœur. **Une boîte tenue par une session
324
- vivante n'est jamais reprise** — c'est ce qui distingue ce mécanisme d'une adresse
325
- partagée, et ce qui protège deux sessions ouvertes en parallèle sur le même projet. Une
326
- boîte n'est reprenable qu'une fois **orpheline** : session close proprement, processus
327
- mort, ou battement arrêté depuis plusieurs minutes.
328
-
329
- Au démarrage, trois chemins, du plus certain au plus approximatif :
330
-
331
- 1. **Le même identifiant de session.** L'hôte en donne un à sa session, et le processus MCP
332
- en hérite. Une session reprise sous le même identifiant retrouve exactement sa boîte.
333
- Rien n'est à configurer : la variable est lue dans l'environnement
334
- (`CLAUDE_CODE_SESSION_ID` et équivalents), et `MESSENGER_SESSION_KEY` permet de la poser
335
- soi-même pour des sessions lancées par script. Ni le projet ni cet identifiant ne sont
336
- écrits en clair : le registre n'en garde que l'empreinte.
337
- 2. **La seule boîte orpheline du projet, si elle a servi.** Sans identifiant, ou s'il a
338
- changé, une boîte du même projet que plus personne ne tient est reprise quand elle est
339
- la seule à l'être. Une boîte vide ne l'est jamais : il n'y a rien à y reprendre.
340
- 3. **Le choix de l'agent.** Plusieurs orphelines, ou une reprise incertaine : rien n'est
341
- pris en silence. `whoami` les liste avec leur nom, leur nombre de messages et leurs
342
- non-lus, et l'agent rattache la bonne avec `attach_to_mailbox`. Les instructions du
343
- serveur MCP lui disent aussi de rattacher une adresse `box_…` qu'il reconnaîtrait dans
344
- son propre contexte : une session reprise le reçoit en entier.
345
-
346
- `MESSENGER_RESUME` tranche pour ceux que la reprise automatique gêne : `auto` par défaut,
347
- `ask` pour que tout passe par l'agent, `off` pour repartir chaque fois d'une boîte neuve.
348
-
349
- Le journal du démarrage dit lequel des trois chemins a servi, et quelle variable a fourni
350
- l'identifiant de session — c'est là qu'on vérifie ce que l'hôte transmet réellement.
351
-
352
- Le rattachement manuel reste disponible en toutes circonstances :
353
-
354
- > « Reprends le travail de la session précédente, sa boîte est `box_9f2c4a01b3d7e5f8`. »
355
-
356
- L'agent appelle `attach_to_mailbox` avec cette adresse : il lit alors le courrier de cette
357
- boîte, y compris les messages arrivés pendant l'interruption, et écrit sous cette adresse.
358
- `attach_to_mailbox` sans argument le ramène à sa propre boîte. Plusieurs sessions peuvent
359
- être attachées à la même boîte — c'est volontaire, et c'est ce qui permet la reprise. La
360
- boîte rattachée devient celle que la session travaille : c'est elle que sa prochaine
361
- reprise retrouvera.
362
-
363
- L'adresse est vérifiée avant le basculement : une faute de frappe renvoie
364
- `mailbox not found` au lieu de créer silencieusement une boîte vide.
365
-
366
- Le registre est un fichier par boîte, en 0600, sous `~/.mcp-messenger/mailboxes/`
367
- (`MESSENGER_STATE_DIR` pour le déplacer). Il est purement local : le serveur n'en sait
368
- rien, et une entrée qu'aucune session n'a touchée depuis un mois est oubliée.
369
-
370
- ### Adresse fixe (cas particulier)
371
-
372
- Renseigner `MESSENGER_AGENT_ID` fige l'adresse : toutes les sessions utilisant cette
373
- configuration partagent alors la même boîte. Utile pour un service unique et stable, à
374
- éviter pour des sessions interactives multiples.
375
-
376
- ---
377
-
378
- ## Console web
379
-
380
- `http://127.0.0.1:3600/ui/` affiche **tous les échanges** de la flotte,
381
- organisés autour du fil — l'unité d'une correspondance, celle qui porte le sujet, les deux
382
- correspondants et l'état de lecture :
383
-
384
- - **une colonne d'agents à deux niveaux** : un agent tient une boîte par session ouverte,
385
- la colonne l'écrit donc ainsi. Une boîte que son agent n'a pas nommée s'affiche à son
386
- adresse, la seule chose qui la distingue alors de ses sœurs.
387
-
388
- **Deux gestes, deux effets, sans recouvrement.** Cliquer le nom d'un agent filtre les
389
- fils sur **toutes ses sessions**, cliquer une boîte sur **cette seule session** ; le
390
- chevron, lui, déplie ou replie les boîtes sans toucher au filtre. Chaque agent est
391
- **replié par défaut**, et sélectionner un agent n'ouvre rien. Recliquer ne relâche rien
392
- non plus : on revient à la vue générale par « Tous les agents », en tête de colonne. La
393
- colonne se replie sur les seuls avatars d'agents quand la place manque, son bouton de
394
- repli restant en place : c'est lui qui la rouvre. **Repliée, elle garde deux signes
395
- distincts** : l'anneau désigne l'agent sélectionné, la pastille bleue dit qu'il reste du
396
- non-lu — la même convention que le point de la liste des fils.
397
-
398
- **Le classement se fait en deux temps : d'abord ce qui a servi, ensuite ce qui est
399
- vide ; puis, dans chacun des deux, la dernière activité, la plus récente en tête.** Une
400
- boîte est vide tant que rien n'y est passé, ni envoi ni réception — le serveur en alloue
401
- une par session ouverte, et beaucoup n'échangeront jamais rien. Ces boîtes-là sont
402
- **masquées par défaut**, derrière un bouton en fin de dépli qui dit combien il y en a ;
403
- les masquer ne cache jamais de courrier, une boîte sans réception n'ayant par
404
- construction aucun non-lu. **Un agent dont aucune session n'a rien échangé est masqué de
405
- la même façon**, derrière un bouton en pied de colonne : même geste, un cran plus haut.
406
- Chaque boîte affiche la date qui la classe, et deux dates
407
- identiques sont départagées par l'adresse pour que l'ordre ne bouge pas d'un
408
- rafraîchissement à l'autre ;
409
- - **la liste des fils**, du plus récemment actif au plus ancien, chacun résumé par son
410
- nom, ses correspondants, son dernier message et sa priorité la plus haute. Les deux
411
- correspondants y sont d'abord **deux pastilles côte à côte**, séparées et non superposées
412
- — leurs initiales doivent rester lisibles. **Ces pastilles sont celles des agents, pas
413
- des boîtes** : dans une liste, ce qu'on cherche du regard est le projet, et deux sessions
414
- d'un même agent portent donc la même. Ce qui les distingue, le nom de la boîte, se lit au
415
- survol, avec le projet, l'adresse et la mention de celle qui a écrit en dernier. Le
416
- lecteur du fil suit la même règle, en tête de chaque message ;
417
- - **un en-tête qui dit sur quoi la liste est filtrée** : sans filtre elle s'intitule
418
- « Fils », et dès qu'un agent est choisi c'est lui le sujet — sa pastille en grand, son
419
- nom en titre, et en dessous ce que le filtre recouvre, toutes ses sessions ou une seule ;
420
- - **trois puces** au-dessus de la liste : tous, non lus, priorité haute ;
421
- - **le lecteur du fil** : la conversation entière dans l'ordre, un bloc par message, avec
422
- qui écrit à qui — en clair, pas en adresses — et une pastille disant si le destinataire
423
- l'a lu. **Les messages s'ouvrent repliés** : chacun tient alors sur sa ligne d'identité —
424
- pastille, qui écrit à qui, heure — et sur **l'objet du message**, qui est ce qui
425
- l'identifie. Les deux forment le bandeau qui l'ouvre et le referme, cliquable d'un bout à
426
- l'autre et grisé au survol pour se donner à voir comme une cible. Ouvrir un message ne
427
- change rien à ce bandeau : le corps vient simplement se poser dessous. Le destinataire
428
- n'y figure pas — un fil ne lie que deux boîtes, le répéter à chaque ligne n'apprend
429
- rien ;
430
- Un fil se parcourt avant de se lire, et le rafraîchissement automatique ne referme pas
431
- ce qui est en train d'être lu. **Le corps d'un message est rendu comme du markdown** : titres, listes,
432
- citations, tableaux, blocs de code, emphase et liens. C'est la langue dans laquelle les
433
- agents s'écrivent, la console la lit donc ainsi plutôt que d'afficher sa syntaxe. Le
434
- rendu construit des nœuds un par un et n'insère jamais de HTML : rien de ce qu'un agent
435
- écrit ne peut devenir une balise, et seuls les liens `http`, `https` et `mailto` sont
436
- suivis. Une image n'est jamais chargée, elle est montrée par son intitulé et son lien.
437
- Les aperçus, eux, sont débarrassés de leur syntaxe par le serveur : ils tiennent sur une
438
- ligne, où un dièse ou une paire d'astérisques ne serait que du bruit ;
439
- - **les détails d'un message**, dans une modale ouverte par l'icône d'information au bout
440
- de sa ligne : l'objet, le nom et l'identifiant du fil, les identifiants, la date, la
441
- priorité, les deux noms et l'adresse de chaque boîte, et le tableau de distribution
442
- (rôle, dossier, état). Tout ce qui se recopie a son bouton de copie ;
443
- - une recherche unique, qui couvre le nom d'un fil, sa description et le texte de ses
444
- messages ;
445
- - **des panneaux redimensionnables** : chaque ligne de séparation se saisit à la souris,
446
- se règle aussi aux flèches du clavier une fois qu'elle a le focus, et rend sa largeur
447
- d'origine sur un double-clic. Les largeurs choisies sont retenues par le navigateur d'une
448
- visite à l'autre ;
449
- - un rafraîchissement automatique toutes les 5 secondes, débrayable.
450
-
451
- **Les adresses ne sont pas de l'information de lecture.** La console résout partout
452
- `box_9f2c4a01b3d7e5f8` en nom d'agent, et ne montre l'adresse qu'aux endroits où elle sert
453
- vraiment : les détails d'un message, l'en-tête d'un fil et les boîtes sans nom, chaque fois
454
- avec un bouton de copie. Quand plusieurs sessions d'un même projet apparaissent, le nom de
455
- l'agent est suivi de celui de la boîte entre parenthèses — sans quoi un fil se lirait
456
- « Documentor Dev → Documentor Dev » sans dire de quelles sessions il parle. Une boîte jamais connectée se distingue à son avatar en contour et à la mention
457
- « jamais connectée » — elle reste listée, puisqu'elle a du courrier en attente.
458
-
459
- À l'ouverture, la console demande `MESSENGER_ADMIN_TOKEN` dans un formulaire. Le serveur
460
- le vérifie (`POST /ui/login`) et ouvre une **session** de 12 heures, portée par un cookie
461
- `HttpOnly` + `SameSite=Strict` (+ `Secure` en HTTPS). Le cookie ne contient jamais le
462
- jeton, seulement un identifiant de session opaque, oublié au redémarrage du serveur ou
463
- sur « Se déconnecter ». Le navigateur ne stocke rien d'autre, et le jeton n'apparaît
464
- jamais dans une URL.
465
-
466
- Cette session n'est reconnue que sur `/ui` et `/admin` : elle ne permet pas d'appeler
467
- l'API des agents. Un site tiers ne peut donc pas faire agir un agent à travers le
468
- navigateur de l'administrateur. Les routes `/admin` acceptent toujours le jeton admin en
469
- `Authorization: Bearer` pour les scripts. Le formulaire est limité à 10 tentatives par
470
- quart d'heure et par IP.
471
-
472
- La console n'envoie ni ne modifie aucun message. Les seules écritures qu'elle commande
473
- sont des suppressions, derrière l'icône de corbeille de la barre du haut. **Deux
474
- nettoyages y cohabitent, avec chacun son bouton, parce qu'ils ne visent pas la même
475
- chose :**
476
-
477
- - **les boîtes vides** : rien n'y est jamais passé, ni envoi ni réception. Elles n'ont pas
478
- d'âge, une boîte vide est vide qu'elle date d'un mois ou d'une minute. C'est le
479
- nettoyage à passer sur un serveur qui a vu défiler beaucoup de sessions muettes. **Un
480
- agent dont toutes les boîtes disparaissent quitte l'annuaire avec elles**, faute de
481
- boîte à regrouper ;
482
- - **les boîtes inactives depuis une date** : elles ont servi, mais plus rien ne les a
483
- touchées depuis. **Une boîte créée après cette date est épargnée, même vide** — une
484
- session qui vient de démarrer et n'a pas encore parlé n'est pas une boîte à l'abandon.
485
-
486
- S'y ajoute la suppression d'**une boîte en particulier** : chaque ligne de la colonne de
487
- gauche porte une corbeille, révélée au survol ou au clavier. Elle n'efface rien d'elle-même,
488
- elle ouvre la même fenêtre sur cette seule boîte, avec ce qu'elle contient sous les yeux.
489
- C'est la seule des trois qui ne demande aucun critère : l'adresse vient d'être montrée du
490
- doigt, il n'y a rien à revérifier.
491
-
492
- Toutes se font en deux temps, parce qu'elles ne se rattrapent pas :
493
-
494
- 1. **L'aperçu** liste les boîtes concernées, chacune avec son nom, son adresse, son nombre
495
- de messages et sa dernière activité. Chaque ligne se décoche.
496
- 2. **La confirmation** demande deux clics, et n'envoie que les adresses affichées, avec le
497
- critère qui les a désignées. Le serveur, lui, reconfronte chacune à ce critère avant de
498
- supprimer : une boîte redevenue active, ou qui vient de recevoir son premier message,
499
- fait échouer le nettoyage entier, nommément. Changer de critère, changer la date ou
500
- décocher une ligne annule une confirmation déjà donnée.
501
-
502
- **Ce qui disparaît.** Un message a deux copies, une par correspondant : supprimer une
503
- boîte n'emporte que sa moitié. Le message lui-même ne s'efface qu'une fois sa dernière
504
- copie partie, autrement dit quand plus personne ne pourrait le lire ; un fil vidé de ses
505
- messages s'en va avec. Ce que l'autre côté a reçu reste intact, expéditeur compris.
506
-
507
- Le registre local des boîtes n'a pas à être nettoyé après une purge : une entrée qui
508
- désigne une boîte disparue n'est jamais reprise, et s'oublie d'elle-même.
509
-
510
- ---
511
-
512
- ## Configuration
513
-
514
- Toutes les variables sont documentées dans [`.env.example`](.env.example). L'essentiel :
515
-
516
- ### Serveur
517
-
518
- | Variable | Défaut | Rôle |
519
- |---|---|---|
520
- | `MESSENGER_PORT` | `3600` | Port d'écoute |
521
- | `MESSENGER_HOST` | `127.0.0.1` (figé) | Non configurable ; une valeur non-loopback fait échouer le démarrage |
522
- | `MESSENGER_DB_PATH` | `./data/messenger.db` | Fichier SQLite |
523
- | `MESSENGER_TOKEN` | — | **Obligatoire** (sauf si `MESSENGER_AGENT_TOKENS`) : jeton exigé sur toute requête. `--generate-token` en produit un |
524
- | `MESSENGER_AGENT_TOKENS` | — | Un jeton par agent : `planner:tok-a,coder:tok-b` |
525
- | `MESSENGER_ADMIN_TOKEN` | `MESSENGER_TOKEN` | Jeton de la console ; obligatoire si seul `MESSENGER_AGENT_TOKENS` est défini |
526
- | `MESSENGER_RATE_LIMIT_MAX` | `600` | Requêtes par IP et par fenêtre |
527
- | `MESSENGER_RATE_LIMIT_WINDOW_MINUTES` | `15` | Durée de la fenêtre |
528
- | `MESSENGER_TRASH_RETENTION_DAYS` | `30` | Purge automatique de la corbeille (`0` = jamais) |
529
- | `MESSENGER_MAX_WAIT_SECONDS` | `300` | Plafond d'une attente longue |
530
-
531
- ### Agent (mode MCP)
532
-
533
- | Variable | Requis | Rôle |
534
- |---|---|---|
535
- | `MESSENGER_AGENT_ID` | — | Adresse **fixe**. Vide (recommandé) = une boîte par session |
536
- | `MESSENGER_SERVER_URL` | ✅ | `http://` ou `https://`, local ou distant |
537
- | `MESSENGER_TOKEN` | ✅ | Jeton présenté au serveur, sur chaque appel |
538
- | `MESSENGER_AGENT_NAME` | — | Nom de l'agent (le projet, typiquement), affiché à côté du nom de la boîte |
539
- | `MESSENGER_AGENT_DESCRIPTION` | — | À quoi sert cet agent, pour ses correspondants |
540
- | `MESSENGER_TIMEOUT_MS` | `15000` | Délai maximal d'un appel HTTP |
541
- | `MESSENGER_STATE_DIR` | — | Répertoire des relais vers le veilleur (défaut : `~/.mcp-messenger`) |
542
-
543
- ---
544
-
545
- ## API HTTP
546
-
547
- **Toutes** les routes de données exigent un jeton, présenté par `Authorization: Bearer
548
- <token>` ou par l'en-tête `X-Messenger-Token` — jamais dans l'URL. Les routes `/api/*`
549
- exigent en plus l'en-tête `X-Agent-Id`, et le jeton doit correspondre à cet agent lorsque
550
- `MESSENGER_AGENT_TOKENS` est utilisé. Les routes `/admin/*` acceptent aussi la session de
551
- la console (cookie), qui elle n'ouvre rien d'autre.
552
-
553
- | Méthode | Route | Rôle |
554
- |---|---|---|
555
- | `GET` | `/health` | État du service |
556
- | `POST` | `/api/agents/register` | S'inscrire à l'annuaire (`fresh: true` = exige une adresse neuve) |
557
- | `GET` | `/api/agents` | Annuaire des boîtes, sauf celle de l'appelant |
558
- | `GET` | `/api/agents/:id` | Une boîte, ou 404 |
559
- | `PATCH` | `/api/agents/me` | Renommer la boîte courante |
560
- | `GET` | `/api/mailbox` | Compteurs de la boîte, et présence d'un veilleur (`watcher`) |
561
- | `POST` | `/api/messages` | Envoyer un message |
562
- | `GET` | `/api/messages` | Lister (`folder`, `unread_only`, `from`, `thread_id`, `search`, `limit`, `offset`) |
563
- | `GET` | `/api/messages/:id` | Lire (`?mark_read=false` pour ne pas marquer) |
564
- | `POST` | `/api/messages/mark` | Marquer lu / suivi |
565
- | `POST` | `/api/messages/move` | Déplacer vers un dossier |
566
- | `POST` | `/api/messages/delete` | Corbeille ou suppression définitive |
567
- | `GET` | `/api/threads` | Fils de la boîte (`unread_only`, `search`, `limit`, `offset`) |
568
- | `GET` | `/api/threads/:id` | Fil complet, avec son intitulé |
569
- | `PATCH` | `/api/threads/:id` | Nommer le fil (`title`, `description`) — participants uniquement |
570
- | `GET` `POST` `DELETE` | `/api/folders[/:name]` | Gérer les dossiers |
571
- | `GET` | `/api/wait` | Attente longue (`timeout_seconds`, `folder`, `watcher`) |
572
- | `GET` | `/admin/overview` · `/admin/messages[/:id]` | Supervision, lecture seule |
573
- | `GET` | `/admin/threads` | Fils du serveur entier (`agent` — une ou plusieurs adresses séparées par des virgules —, `search`, `unread_only`, `priority`, `limit`, `offset`) |
574
- | `GET` | `/admin/threads/:id` | Fil complet, corps inclus |
575
- | `GET` | `/admin/mailboxes/stale` | Boîtes sans activité depuis `before`, avec ce que chacune contient |
576
- | `GET` | `/admin/mailboxes/empty` | Boîtes par lesquelles rien n'est jamais passé, sans critère de date |
577
- | `POST` | `/admin/mailboxes/purge` | Supprime les boîtes nommées (`mailboxes`, plus `before` **ou** `empty: true`) — refusé si l'une d'elles ne répond plus au critère |
578
- | `DELETE` | `/admin/mailboxes/:id` | Supprime une boîte désignée, quel que soit son état — 404 si l'adresse est inconnue |
579
- | `POST` | `/ui/login` · `/ui/logout` | Session de la console (`{ "token": … }` → cookie `HttpOnly`) |
580
-
581
- ---
582
-
583
- ## Sécurité
584
-
585
- - **Écoute exclusivement sur `127.0.0.1`** : l'interface n'est pas configurable, et une
586
- valeur non-loopback fait échouer le démarrage plutôt que d'être ignorée en silence.
587
- - **Refus de démarrage sans jeton** : un serveur qui tourne est un serveur authentifié.
588
- - **Jeton vérifié sur chaque requête entrante**, avant tout traitement — y compris
589
- `/health` et les fichiers de la console.
590
- - **Jetons par agent** (`MESSENGER_AGENT_TOKENS`) : un jeton valide ne permet pas
591
- d'emprunter l'identité d'un autre agent. À préférer au jeton partagé dès que les agents
592
- ne se font pas mutuellement confiance.
593
- - Comparaison des jetons à **temps constant**, en-têtes de sécurité (`helmet`), **rate
594
- limiting** par IP, corps JSON limité à 2 Mo, erreurs internes génériques (le détail reste
595
- dans les logs).
596
- - **Isolation des boîtes** garantie côté serveur : les identifiants de messages fournis par
597
- un agent sont systématiquement filtrés sur ses propres livraisons.
598
- - La console web n'insère jamais de HTML fourni par un agent : le corps des messages est
599
- rendu en markdown par construction de nœuds, jamais par assignation de balisage, et la
600
- CSP interdit tout script tiers.
601
- - La console ne peut ni écrire ni modifier un message. Ses seules commandes destructrices
602
- suppriment des boîtes : réservées au jeton d'administration, confirmées deux fois, et
603
- revérifiées par le serveur quand elles portent sur un lot.
604
- - Derrière un reverse proxy TLS, exposer le serveur en `https://` et donner cette URL aux
605
- agents via `MESSENGER_SERVER_URL`.
606
-
607
- ---
608
-
609
- ## Développement
610
-
611
- ```bash
612
- npm install
613
- npm run generate-token # jeton aléatoire (32 octets, base64url)
614
- npm run build # tsc + copie des fichiers de la console web
615
- npm test # build puis suite Vitest complète
616
- npm run test:watch
617
- npm run typecheck
618
- npm run serve # serveur local sur le port 3600
619
- ```
620
-
621
- ### Structure
622
-
623
- ```
624
- src/
625
- index.ts # point d'entrée : --server, --watch, --help, --version, sinon mode MCP
626
- config.ts # lecture et validation des variables d'environnement
627
- logger.ts # journalisation sur stderr (stdout est réservé au JSON-RPC)
628
- core/store.ts # SQLite : agents, messages, livraisons, dossiers
629
- server/app.ts # API HTTP, authentification, supervision
630
- server/watchers.ts # présence des veilleurs, en mémoire : état de connexion
631
- server/start.ts # démarrage, arrêt propre, purge de la corbeille
632
- server/ui/ # console web (HTML/CSS/JS sans dépendance)
633
- server/ui/markdown.js # rendu markdown du corps des messages, en nœuds DOM
634
- cli/watch.ts # mode veilleur : une ligne sur stdout par message entrant
635
- cli/watch-handoff.ts # relais 0600 entre la session MCP et son veilleur
636
- mcp/mailbox-registry.ts # registre local des boîtes : reprise d'une session interrompue
637
- client/ # client HTTP utilisé par le mode MCP
638
- mcp/ # définition des outils et serveur MCP stdio
639
- test/ # store, config, API HTTP, outils MCP, binaire réel
640
- ```
641
-
642
- ### Tests
643
-
644
- 287 tests couvrant : les suppressions de boîtes commandées depuis la console — une boîte
645
- désignée à la main, les vides, celles sans activité depuis une date — avec leur aperçu,
646
- leur double confirmation et le refus nominatif d'une boîte qui ne répond plus au critère, la reprise de la boîte d'une session interrompue (clé de session de
647
- l'hôte, boîte orpheline, boîte d'une session vivante jamais reprise), le rendu markdown des messages et l'aplatissement des aperçus (dont
648
- l'innocuité du rendu : ni HTML interprété, ni lien vers un autre schéma qu'`http`, `https`
649
- ou `mailto`), le stockage et l'isolation des boîtes, la validation de configuration,
650
- l'API HTTP et son authentification (refus systématique sans jeton, jeton partagé, jetons par
651
- agent, jeton de console distinct), les outils MCP de bout en bout (deux agents qui
652
- conversent à travers un vrai serveur), l'allocation des boîtes par session — deux processus
653
- MCP lancés avec la même configuration obtiennent deux adresses différentes, et une
654
- troisième session reprend la boîte de l'une d'elles — et le binaire publié lui-même.
655
-
656
- La notification passive est couverte de bout en bout : le relais vers le veilleur (isolation
657
- entre boîtes, adresse jamais transformée en chemin, nettoyage), le format d'évènement (une
658
- ligne, quel que soit le sujet), et un veilleur réellement lancé comme processus séparé, sans
659
- jeton dans son environnement, qui émet sa ligne à l'arrivée d'un message — et `whoami` qui
660
- passe de « NOT armed » à « armed » quand ce veilleur prend son poste.
661
-
662
- ---
663
-
664
- ## Licence
665
-
666
- ISC
1
+ # MCP Messenger
2
+
3
+ Boîte aux lettres pour agents IA : un **serveur** central reçoit et conserve les messages,
4
+ et un **client MCP** donne à chaque agent — quelle que soit sa plateforme — les outils pour
5
+ écrire, lire, classer et attendre des réponses.
6
+
7
+ Le même paquet joue les deux rôles :
8
+
9
+ | | Mode serveur (`--server`) | Mode MCP (sans argument) |
10
+ |---|---|---|
11
+ | Rôle | Boîte aux lettres : stockage, distribution, annuaire | Client stdio branché sur un agent |
12
+ | Exposé sur | HTTP/HTTPS (API + console web) | stdin/stdout (JSON-RPC MCP) |
13
+ | Consommé par | Les MCP des agents, la console web | Claude Code, Cursor, tout client MCP |
14
+ | Variables clés | `MESSENGER_PORT`, `MESSENGER_TOKEN` | `MESSENGER_AGENT_ID`, `MESSENGER_SERVER_URL`, `MESSENGER_TOKEN` |
15
+ | Processus | 1 pour toute la flotte | 1 par agent |
16
+
17
+ ```mermaid
18
+ flowchart LR
19
+ A["Agent A (Claude Code)"] -- "stdio" --> MA["mcp-messenger (MCP)"]
20
+ B["Agent B (Cursor)"] -- "stdio" --> MB["mcp-messenger (MCP)"]
21
+ C["Agent C (autre plateforme)"] -- "stdio" --> MC["mcp-messenger (MCP)"]
22
+ MA -- "HTTP(S)" --> S
23
+ MB -- "HTTP(S)" --> S
24
+ MC -- "HTTP(S)" --> S
25
+ S["mcp-messenger --server\nSQLite + API + console"] --> U["Console web /ui"]
26
+ ```
27
+
28
+ Les agents n'ont pas besoin d'être sur la même machine ni sur la même plateforme : ils
29
+ partagent seulement l'adresse du serveur. **Chaque session d'agent reçoit sa propre boîte**,
30
+ identifiée par une adresse opaque du type `box_9f2c4a01b3d7e5f8` — voir
31
+ [Adresses et sessions](#adresses-et-sessions).
32
+
33
+ ---
34
+
35
+ ## Démarrage rapide
36
+
37
+ ### 1. Lancer la boîte aux lettres
38
+
39
+ ```bash
40
+ # Un jeton, à conserver : il sera demandé au serveur, aux agents et à la console.
41
+ npx @imenam/mcp-messenger --generate-token
42
+
43
+ MESSENGER_TOKEN=<le-jeton> npx @imenam/mcp-messenger --server
44
+ # → listening on http://127.0.0.1:3600 (loopback only)
45
+ # → auth: shared token — required on every request
46
+ # → web console: http://127.0.0.1:3600/ui/ (asks for MESSENGER_ADMIN_TOKEN once)
47
+ ```
48
+
49
+ Deux invariants, non négociables et vérifiés par les tests :
50
+
51
+ 1. **Le serveur écoute toujours sur `127.0.0.1`.** `MESSENGER_HOST` n'est pas un réglage :
52
+ toute autre valeur fait échouer le démarrage. Pour joindre la boîte depuis une autre
53
+ machine, il faut placer un reverse proxy (TLS) devant.
54
+ 2. **Un jeton est obligatoire et vérifié sur chaque requête de données** — API, `/health`
55
+ et données de la console (`/admin`) comprises. Seule la page de la console, qui ne
56
+ contient aucun secret, est servie librement. Sans `MESSENGER_TOKEN` ni
57
+ `MESSENGER_AGENT_TOKENS`, le serveur refuse de démarrer.
58
+
59
+ ### 2. Déclarer le MCP côté agent
60
+
61
+ Dans le dossier du projet de l'agent :
62
+
63
+ ```bash
64
+ npx -y @imenam/mcp-messenger --claude-setup-mcp
65
+ ```
66
+
67
+ La commande écrit l'entrée `mcp-messenger` dans le `.mcp.json` du répertoire courant (les
68
+ autres serveurs sont préservés ; `--force` pour remplacer une entrée existante), avec un
69
+ `MESSENGER_AGENT_ID` pré-rempli d'après le nom du dossier. Il ne reste qu'à coller le jeton
70
+ du serveur dans `MESSENGER_TOKEN`.
71
+
72
+ Le bloc généré, à écrire à la main si tu préfères :
73
+
74
+ ```json
75
+ {
76
+ "mcpServers": {
77
+ "messenger": {
78
+ "command": "npx",
79
+ "args": ["@imenam/mcp-messenger"],
80
+ "env": {
81
+ "MESSENGER_AGENT_ID": "planner",
82
+ "MESSENGER_SERVER_URL": "http://127.0.0.1:3600",
83
+ "MESSENGER_TOKEN": "le-jeton-du-serveur",
84
+ "MESSENGER_AGENT_NAME": "Planner",
85
+ "MESSENGER_AGENT_DESCRIPTION": "Découpe les tâches et répartit le travail"
86
+ }
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ Chaque agent reçoit le même bloc avec **son propre** `MESSENGER_AGENT_ID`. Au démarrage, le
93
+ MCP s'enregistre dans l'annuaire du serveur ; si le serveur est momentanément injoignable,
94
+ la session MCP démarre quand même et l'enregistrement se fera au premier appel réussi.
95
+
96
+ ### 3. Écrire à un autre agent
97
+
98
+ L'agent A appelle `send_message` avec `to: ["coder"]`, l'agent B voit le message via
99
+ `list_messages` puis `read_message`, et répond avec `reply_to` pour rester dans le fil.
100
+ Le fil se nomme au passage (`thread_name` sur `send_message`, ou `rename_thread` plus tard).
101
+ `wait_for_message` permet d'attendre une réponse sans faire tourner une boucle d'appels : il ne
102
+ rend la main que sur un message arrivé *après* le début de l'attente, les non-lus déjà en boîte
103
+ ne l'interrompent pas.
104
+
105
+ ---
106
+
107
+ ## Être notifié sans attendre
108
+
109
+ Attendre coûte un tour d'agent. Pour rester joignable **sans rien bloquer**, l'agent active
110
+ les notifications une fois en début de session ; ensuite il travaille normalement, et les
111
+ messages entrants l'interrompent d'eux-mêmes — y compris quand il est à l'arrêt, inactif.
112
+
113
+ ```
114
+ enable_notifications() → retourne immédiatement, ne bloque rien
115
+
116
+ Notifications enabled for mailbox `box_9f2c4a01b3d7e5f8`.
117
+
118
+ Last step — call the Monitor tool now, with exactly these arguments:
119
+ command: npx -y @imenam/mcp-messenger --watch box_9f2c4a01b3d7e5f8
120
+ description: Incoming messenger messages
121
+ persistent: true
122
+ ```
123
+
124
+ L'agent recopie ces trois arguments dans l'outil `Monitor` de son hôte, et c'est fini pour la
125
+ session. Le veilleur tourne en fond, tient une attente longue sur `/api/wait`, et écrit **une
126
+ ligne par message** sur sa sortie standard :
127
+
128
+ ```
129
+ [messenger] planner — Refacto prêt (msg_4c1e88a0b2)
130
+ ```
131
+
132
+ Chaque ligne réveille l'agent, qui appelle `read_message` s'il décide de traiter le message.
133
+
134
+ **Pourquoi deux appels et pas un.** Armer un réveil, c'est ouvrir un canal vers la boucle de
135
+ la session hôte. Un serveur MCP ne peut pas le faire seul : `Monitor` est un outil de l'hôte,
136
+ seul le modèle peut l'appeler. `enable_notifications` fait donc tout le reste — il connaît
137
+ l'adresse, il prépare le terrain — et rend la commande exacte à recopier. L'agent ne compose
138
+ rien.
139
+
140
+ **Où passe le jeton.** Le veilleur est lancé par l'hôte dans le shell de la session : il
141
+ n'hérite pas du bloc `env` du `.mcp.json`, donc ni de `MESSENGER_SERVER_URL` ni de
142
+ `MESSENGER_TOKEN`. Les mettre sur la ligne de commande les afficherait dans la conversation.
143
+ `enable_notifications` dépose donc un relais en `0600` sous `~/.mcp-messenger/watch/`
144
+ (`MESSENGER_STATE_DIR` pour le déplacer), que `--watch` retrouve à partir de la seule adresse.
145
+ Le fichier est nommé par hachage de l'adresse, supprimé à la fin de la session, et purgé au
146
+ bout de sept jours si la session s'est arrêtée brutalement.
147
+
148
+ **Savoir si c'est vraiment armé.** L'armement dépend de l'agent : rien ne peut le déclencher
149
+ à sa place, et un agent qui saute l'étape se croirait joignable sans l'être. Le serveur suit
150
+ donc les attentes tenues par un veilleur, et `whoami` le dit :
151
+
152
+ ```
153
+ Notifications: armed — incoming messages will interrupt you (watcher since 2026-09-07T13:31:00.203Z).
154
+ Notifications: NOT armed — no incoming message will reach you. Call enable_notifications.
155
+ ```
156
+
157
+ C'est une présence de connexion, pas une donnée stockée : elle vit en mémoire du serveur et
158
+ disparaît avec lui. Un `wait_for_message` ordinaire ne compte pas — il s'arrête au premier
159
+ message, un veilleur reste. Entre deux attentes longues, un délai de grâce de deux minutes
160
+ évite que le signalement clignote. `enable_notifications` s'en sert aussi : si un veilleur est
161
+ déjà en place, il le dit et n'en fait pas armer un second, qui doublerait chaque notification.
162
+
163
+ **Ce que ça ne couvre pas.** Le veilleur n'annonce que les messages arrivés **après** son
164
+ démarrage : `enable_notifications` signale le nombre de non-lus déjà en boîte, à traiter avec
165
+ `list_messages`. Et `--claude-setup-mcp` écrit un rappel d'armement dans le `CLAUDE.md` du
166
+ projet, pour que l'étape ne soit pas oubliée en début de session.
167
+
168
+ **Sur un autre hôte que Claude Code.** `--watch` est une commande ordinaire : n'importe quel
169
+ superviseur capable de lire les lignes d'un processus peut la brancher. Sans relais, elle lit
170
+ `MESSENGER_SERVER_URL`, `MESSENGER_TOKEN` et `MESSENGER_AGENT_ID` dans son environnement.
171
+
172
+ ---
173
+
174
+ ## Outils MCP
175
+
176
+ **Deux mots, deux choses.** Une **boîte** (`mailbox` dans les outils) est une adresse
177
+ `box_…` : elle désigne une session, et c'est à elle qu'on écrit. Un **nom d'agent** est le
178
+ projet pour lequel une session travaille : c'est une étiquette, jamais une adresse, et
179
+ toutes les sessions du projet la portent. Partout où un outil attend une adresse, sa
180
+ description dit « mailbox address ».
181
+
182
+ | Outil | Rôle |
183
+ |---|---|
184
+ | `whoami` | Adresse de cette session, serveur connecté, veilleur armé ou non, compteurs par dossier |
185
+ | `attach_to_mailbox` | Se brancher sur une autre boîte via son adresse ; sans argument, retour à la sienne |
186
+ | `rename_mailbox` | Nommer ou renommer sa boîte (de quoi la session s'occupe) ; le nom de l'agent, posé par la configuration, reste |
187
+ | `rename_thread` | Nommer ou renommer un fil (`thread_id`) ; le nom est partagé par tous ses participants |
188
+ | `list_threads` | Carte des conversations : nom, participants, non-lus, dernière activité ; `with_mailbox` ne garde que celles partagées avec une boîte |
189
+ | `list_agents` | Annuaire groupé par agent : un projet, ses sessions ; `agent_name` déplie un groupe. La boîte de l'appelant n'y figure pas |
190
+ | `send_message` | Écrire à un agent — un seul destinataire par message (`to`, `priority`, `reply_to`, `thread_name`) |
191
+ | `list_messages` | Lister la boîte (filtres : dossier, non lus, expéditeur, fil, texte libre) |
192
+ | `read_message` | Lire un message en entier ; le marque lu par défaut |
193
+ | `get_thread` | Relire toute une conversation, du plus ancien au plus récent |
194
+ | `mark_messages` | Marquer lu / non lu, suivi / non suivi |
195
+ | `move_messages` | Classer dans un dossier |
196
+ | `delete_messages` | Corbeille, ou suppression définitive (`permanent: true`) |
197
+ | `list_folders` | Dossiers avec totaux et non-lus |
198
+ | `create_folder` / `delete_folder` | Organiser l'espace comme une messagerie |
199
+ | `enable_notifications` | Prépare la notification passive et rend la commande `Monitor` à armer ; ne bloque rien |
200
+ | `wait_for_message` | Attente longue (jusqu'à 300 s) jusqu'à l'arrivée d'un message *postérieur* à l'appel |
201
+
202
+ **Noms lisibles** — voir « Nommer les boîtes et les fils » ci-dessous.
203
+
204
+ **Un message, deux boîtes** — un échange lie exactement deux agents *distincts* : `to` prend
205
+ une seule adresse, il n'y a ni diffusion ni copie, et une boîte ne peut pas s'écrire à
206
+ elle-même. Pour toucher plusieurs agents, on leur envoie un message chacun, dans son propre
207
+ fil. C'est aussi pourquoi `list_agents` ne montre jamais l'appelant : la seule adresse qu'il
208
+ ne peut pas viser n'a pas à figurer dans son annuaire.
209
+
210
+ **Dossiers système** — toujours présents, non supprimables : `inbox`, `sent`, `archive`,
211
+ `trash`. Les dossiers personnalisés sont propres à chaque boîte.
212
+
213
+ **Ce que voit un agent** : uniquement les livraisons de la boîte à laquelle il est attaché. Un message adressé à `coder`
214
+ est invisible pour tout autre agent, y compris via son identifiant. Supprimer un message ne
215
+ touche que sa propre copie : l'autre partie garde la sienne.
216
+
217
+ ---
218
+
219
+ ## Adresses et sessions
220
+
221
+ Une adresse n'est pas un rôle, c'est une **boîte**. Au démarrage, chaque processus MCP —
222
+ donc chaque session d'agent — s'en fait allouer une, avec un identifiant opaque et unique :
223
+
224
+ ```
225
+ box_9f2c4a01b3d7e5f8
226
+ ```
227
+
228
+ Conséquence directe : deux sessions ouvertes dans le même projet, avec le même `.mcp.json`,
229
+ ont deux boîtes distinctes et ne se volent pas leur courrier.
230
+
231
+ ### Faire communiquer deux agents
232
+
233
+ Le plus souvent, il n'y a rien à faire : l'agent B ouvre `list_agents` et y trouve
234
+ l'adresse de A. C'est le chemin normal, et le seul qui ne puisse pas se tromper.
235
+
236
+ Si tu veux désigner toi-même l'interlocuteur :
237
+
238
+ 1. Demande son adresse à l'agent A (`whoami`) — il te la donne, c'est à toi qu'elle est due.
239
+ 2. Transmets-la à l'agent B, qui l'utilise dans `send_message`.
240
+
241
+ Dans les deux cas, une adresse ne circule jamais **dans** un message : le serveur y attache
242
+ déjà l'expéditeur et le destinataire. Un agent qui recopie une adresse dans un corps de
243
+ message écrit une information que le lecteur a déjà, et qui sera fausse le jour où il se
244
+ trompe de session.
245
+
246
+ Comme les adresses sont opaques, demande aux agents de nommer leur boîte
247
+ (`rename_mailbox`) : le nom apparaît dans `list_agents` et dans la console web, ce
248
+ qui permet de retrouver une adresse perdue.
249
+
250
+ ### Deux noms par boîte : l'agent et la boîte
251
+
252
+ Une boîte porte deux identités, qui répondent à deux questions différentes et ne changent
253
+ pas au même rythme :
254
+
255
+ | | D'où vient-il ? | De quoi s'occupe-t-il ? |
256
+ |---|---|---|
257
+ | Champ | `agent_name`, `agent_description` | `display_name`, `description` |
258
+ | Posé par | la configuration (`MESSENGER_AGENT_NAME`, `MESSENGER_AGENT_DESCRIPTION`) | l'agent lui-même, via `rename_mailbox` |
259
+ | Change | jamais depuis une session | librement, au fil du travail |
260
+ | Exemple | `Ai Corrector Dev` | `Correction du parseur de copies` |
261
+
262
+ Le nom de l'agent seul ne distingue pas deux sessions ouvertes sur le même projet ; le nom
263
+ de la boîte seul ne dit pas d'où l'agent écrit. `whoami` et la console web montrent donc
264
+ toujours les deux, et l'annuaire (`list_agents`) les met l'un sous l'autre : le nom de
265
+ l'agent en tête de groupe, une ligne par boîte en dessous. Renommer la boîte ne touche
266
+ jamais au nom de l'agent.
267
+
268
+ **Un nom d'agent ne désigne personne.** C'est le piège de ce modèle : plusieurs boîtes
269
+ portent le même `agent_name`, une par session lancée depuis la même configuration.
270
+ L'annuaire le rend visible en groupant les boîtes sous leur nom d'agent — un groupe à
271
+ plusieurs lignes, ce sont plusieurs sessions vivantes du même projet, chacune à sa propre
272
+ adresse. Un groupe nombreux n'affiche que ses trois sessions les plus récentes ;
273
+ `list_agents` avec `agent_name` donne le reste, sans imposer ce second appel dans le cas
274
+ courant. Un agent
275
+ qui croise son propre nom d'agent dans l'annuaire regarde une *autre* session du même
276
+ projet, jamais la sienne — sa boîte, elle, n'y figure pas du tout. Seule l'adresse identifie
277
+ une session. Ce rappel est servi à trois endroits, du plus tôt au plus tard : les
278
+ instructions du serveur MCP (reçues à la connexion, avant le premier outil), la ligne qui
279
+ suit `Agent:` dans `whoami`, et le pied de l'annuaire dans `list_agents`.
280
+
281
+ **Les adresses ne se recopient pas à la main.** Le serveur attache l'expéditeur et le
282
+ destinataire à chaque message, et le lecteur les voit dans l'en-tête. Écrire son adresse
283
+ dans le corps n'ajoute rien et se met à mentir dès qu'elle est erronée ou périmée —
284
+ `send_message` le dit dans sa description et dans celle de `body`.
285
+
286
+ ### Nommer les boîtes et les fils
287
+
288
+ Les identifiants (`box_…`, `thr_…`) ne disent rien de ce qui se passe. Deux noms en langue
289
+ naturelle viennent par-dessus, un outil pour chacun :
290
+
291
+ ```
292
+ rename_mailbox(name: "Développement des tests unitaires du parser")
293
+ → nomme la boîte de la session (le nom de l'agent reste)
294
+
295
+ rename_thread(thread_id: "thr_…", name: "Le test échoue sur un bug applicatif")
296
+ → nomme le fil
297
+ ```
298
+
299
+ Le plus simple reste de nommer un fil au moment où on l'ouvre, via `thread_name` sur
300
+ `send_message` : l'agent qui écrit le premier message sait déjà de quoi il parle.
301
+
302
+ L'ancien outil `set_label`, qui cumulait les deux rôles, reste accepté sans être publié :
303
+ une session lancée avant la mise à jour peut encore l'appeler.
304
+
305
+ Un fil n'a **qu'un seul nom, partagé par tous ses participants** — ils discutent du même
306
+ sujet, ils doivent lire le même intitulé. N'importe quel participant peut le renommer, et
307
+ le dernier a le dernier mot ; le serveur retient qui (`named_by`). Un agent extérieur au
308
+ fil ne peut pas le renommer.
309
+
310
+ Tant qu'un fil n'a pas été nommé, l'affichage retombe sur le sujet de son message
311
+ d'ouverture : rien n'est jamais illisible, mais rien n'est aussi parlant qu'un nom choisi.
312
+ Ces noms apparaissent dans `list_threads`, `list_messages`, `get_thread`, les
313
+ notifications du veilleur et la console web.
314
+
315
+ ### Reprendre le travail d'une session interrompue
316
+
317
+ Une session relancée est un nouveau processus. Sans mémoire, elle se ferait allouer une
318
+ boîte neuve et laisserait derrière elle ses fils, ses non-lus et **l'adresse que ses
319
+ correspondants connaissent** ; répété, cela multiplie les boîtes pour un travail unique.
320
+ La session retrouve donc la sienne toute seule, par un **registre local**.
321
+
322
+ Chaque session y inscrit sa boîte, avec l'empreinte de son projet et la preuve qu'elle
323
+ vit : le processus qui la tient et un battement de cœur. **Une boîte tenue par une session
324
+ vivante n'est jamais reprise** — c'est ce qui distingue ce mécanisme d'une adresse
325
+ partagée, et ce qui protège deux sessions ouvertes en parallèle sur le même projet. Une
326
+ boîte n'est reprenable qu'une fois **orpheline** : session close proprement, processus
327
+ mort, ou battement arrêté depuis plusieurs minutes.
328
+
329
+ Au démarrage, une seule chose est attribuée d'office, et seulement sur certitude :
330
+
331
+ 1. **Le même identifiant de session.** L'hôte en donne un à sa session, et le processus MCP
332
+ en hérite. Une session reprise sous le même identifiant retrouve exactement sa boîte.
333
+ Rien n'est à configurer : la variable est lue dans l'environnement
334
+ (`CLAUDE_CODE_SESSION_ID` et équivalents), et `MESSENGER_SESSION_KEY` permet de la poser
335
+ soi-même pour des sessions lancées par script. Ni le projet ni cet identifiant ne sont
336
+ écrits en clair : le registre n'en garde que l'empreinte. Le passage d'une autre session
337
+ par la boîte n'efface pas ce lien : le registre retient toutes les sessions qui l'ont
338
+ tenue, et la session d'origine la retrouve dès qu'elle redevient libre.
339
+ 2. **Le choix de l'agent, pour tout le reste.** Aucune boîte n'est attribuée sur une
340
+ ressemblance. Être la seule boîte libre du projet ne prouve rien : le répertoire et le
341
+ nom d'agent sont communs à toutes les sessions, y compris celles qui n'existaient pas
342
+ quand cette boîte s'est remplie. Les orphelines qui ont servi sont donc **proposées** :
343
+ `whoami` les liste avec leur nom, leur nombre de messages et leurs non-lus, et l'agent
344
+ rattache la bonne avec `attach_to_mailbox`. Les instructions du serveur MCP lui disent
345
+ aussi de rattacher une adresse `box_…` qu'il reconnaîtrait dans son propre contexte —
346
+ reconnaître sa propre adresse est une preuve, être la seule boîte libre n'en est pas une.
347
+
348
+ Quand aucune boîte n'a pu être rattachée, `whoami` le dit en toutes lettres : cette adresse
349
+ est neuve, et si l'agent écrivait d'une autre avant, ses correspondants écrivent toujours à
350
+ celle-là. Sans cette ligne, une conversation reprise croirait garder son adresse d'avant.
351
+
352
+ Il n'y a rien à régler : la recherche a toujours lieu, et son résultat ne dépend que de ce
353
+ que l'hôte transmet. Le journal du démarrage dit quel chemin a servi et quelle variable a
354
+ fourni l'identifiant de session — c'est là qu'on vérifie ce que l'hôte transmet réellement.
355
+
356
+ Le rattachement manuel reste disponible en toutes circonstances :
357
+
358
+ > « Reprends le travail de la session précédente, sa boîte est `box_9f2c4a01b3d7e5f8`. »
359
+
360
+ L'agent appelle `attach_to_mailbox` avec cette adresse : il lit alors le courrier de cette
361
+ boîte, y compris les messages arrivés pendant l'interruption, et écrit sous cette adresse.
362
+ `attach_to_mailbox` sans argument le ramène à sa propre boîte. Plusieurs sessions peuvent
363
+ être attachées à la même boîte — c'est volontaire, et c'est ce qui permet la reprise. La
364
+ boîte rattachée devient celle que la session travaille : c'est elle que sa prochaine
365
+ reprise retrouvera.
366
+
367
+ L'adresse est vérifiée avant le basculement : une faute de frappe renvoie
368
+ `mailbox not found` au lieu de créer silencieusement une boîte vide.
369
+
370
+ Le registre est un fichier par boîte, en 0600, sous `~/.mcp-messenger/mailboxes/`
371
+ (`MESSENGER_STATE_DIR` pour le déplacer). Il est purement local : le serveur n'en sait
372
+ rien, et une entrée qu'aucune session n'a touchée depuis un mois est oubliée.
373
+
374
+ ### Adresse fixe (cas particulier)
375
+
376
+ Renseigner `MESSENGER_AGENT_ID` fige l'adresse : toutes les sessions utilisant cette
377
+ configuration partagent alors la même boîte. Utile pour un service unique et stable, à
378
+ éviter pour des sessions interactives multiples.
379
+
380
+ ---
381
+
382
+ ## Console web
383
+
384
+ `http://127.0.0.1:3600/ui/` affiche **tous les échanges** de la flotte,
385
+ organisés autour du fil — l'unité d'une correspondance, celle qui porte le sujet, les deux
386
+ correspondants et l'état de lecture :
387
+
388
+ - **une colonne d'agents à deux niveaux** : un agent tient une boîte par session ouverte,
389
+ la colonne l'écrit donc ainsi. Une boîte que son agent n'a pas nommée s'affiche à son
390
+ adresse, la seule chose qui la distingue alors de ses sœurs.
391
+
392
+ **Deux gestes, deux effets, sans recouvrement.** Cliquer le nom d'un agent filtre les
393
+ fils sur **toutes ses sessions**, cliquer une boîte sur **cette seule session** ; le
394
+ chevron, lui, déplie ou replie les boîtes sans toucher au filtre. Chaque agent est
395
+ **replié par défaut**, et sélectionner un agent n'ouvre rien. Recliquer ne relâche rien
396
+ non plus : on revient à la vue générale par « Tous les agents », en tête de colonne. La
397
+ colonne se replie sur les seuls avatars d'agents quand la place manque, son bouton de
398
+ repli restant en place : c'est lui qui la rouvre. **Repliée, elle garde deux signes
399
+ distincts** : l'anneau désigne l'agent sélectionné, la pastille bleue dit qu'il reste du
400
+ non-lu — la même convention que le point de la liste des fils.
401
+
402
+ **Le classement se fait en deux temps : d'abord ce qui a servi, ensuite ce qui est
403
+ vide ; puis, dans chacun des deux, la dernière activité, la plus récente en tête.** Une
404
+ boîte est vide tant que rien n'y est passé, ni envoi ni réception — le serveur en alloue
405
+ une par session ouverte, et beaucoup n'échangeront jamais rien. Ces boîtes-là sont
406
+ **masquées par défaut**, derrière un bouton en fin de dépli qui dit combien il y en a ;
407
+ les masquer ne cache jamais de courrier, une boîte sans réception n'ayant par
408
+ construction aucun non-lu. **Un agent dont aucune session n'a rien échangé est masqué de
409
+ la même façon**, derrière un bouton en pied de colonne : même geste, un cran plus haut.
410
+ Chaque boîte affiche la date qui la classe, et deux dates
411
+ identiques sont départagées par l'adresse pour que l'ordre ne bouge pas d'un
412
+ rafraîchissement à l'autre ;
413
+ - **la liste des fils**, du plus récemment actif au plus ancien, chacun résumé par son
414
+ nom, ses correspondants, son dernier message et sa priorité la plus haute. Les deux
415
+ correspondants y sont d'abord **deux pastilles côte à côte**, séparées et non superposées
416
+ — leurs initiales doivent rester lisibles. **Ces pastilles sont celles des agents, pas
417
+ des boîtes** : dans une liste, ce qu'on cherche du regard est le projet, et deux sessions
418
+ d'un même agent portent donc la même. Ce qui les distingue, le nom de la boîte, se lit au
419
+ survol, avec le projet, l'adresse et la mention de celle qui a écrit en dernier. Le
420
+ lecteur du fil suit la même règle, en tête de chaque message ;
421
+ - **un en-tête qui dit sur quoi la liste est filtrée** : sans filtre elle s'intitule
422
+ « Fils », et dès qu'un agent est choisi c'est lui le sujet — sa pastille en grand, son
423
+ nom en titre, et en dessous ce que le filtre recouvre, toutes ses sessions ou une seule ;
424
+ - **trois puces** au-dessus de la liste : tous, non lus, priorité haute ;
425
+ - **le lecteur du fil** : la conversation entière dans l'ordre, un bloc par message, avec
426
+ qui écrit à qui — en clair, pas en adresses — et une pastille disant si le destinataire
427
+ l'a lu. **Les messages s'ouvrent repliés** : chacun tient alors sur sa ligne d'identité —
428
+ pastille, qui écrit à qui, heure — et sur **l'objet du message**, qui est ce qui
429
+ l'identifie. Les deux forment le bandeau qui l'ouvre et le referme, cliquable d'un bout à
430
+ l'autre et grisé au survol pour se donner à voir comme une cible. Ouvrir un message ne
431
+ change rien à ce bandeau : le corps vient simplement se poser dessous. Le destinataire
432
+ n'y figure pas — un fil ne lie que deux boîtes, le répéter à chaque ligne n'apprend
433
+ rien ;
434
+ Un fil se parcourt avant de se lire, et le rafraîchissement automatique ne referme pas
435
+ ce qui est en train d'être lu. **Le corps d'un message est rendu comme du markdown** : titres, listes,
436
+ citations, tableaux, blocs de code, emphase et liens. C'est la langue dans laquelle les
437
+ agents s'écrivent, la console la lit donc ainsi plutôt que d'afficher sa syntaxe. Le
438
+ rendu construit des nœuds un par un et n'insère jamais de HTML : rien de ce qu'un agent
439
+ écrit ne peut devenir une balise, et seuls les liens `http`, `https` et `mailto` sont
440
+ suivis. Une image n'est jamais chargée, elle est montrée par son intitulé et son lien.
441
+ Les aperçus, eux, sont débarrassés de leur syntaxe par le serveur : ils tiennent sur une
442
+ ligne, où un dièse ou une paire d'astérisques ne serait que du bruit ;
443
+ - **les détails d'un message**, dans une modale ouverte par l'icône d'information au bout
444
+ de sa ligne : l'objet, le nom et l'identifiant du fil, les identifiants, la date, la
445
+ priorité, les deux noms et l'adresse de chaque boîte, et le tableau de distribution
446
+ (rôle, dossier, état). Tout ce qui se recopie a son bouton de copie ;
447
+ - une recherche unique, qui couvre le nom d'un fil, sa description et le texte de ses
448
+ messages ;
449
+ - **des panneaux redimensionnables** : chaque ligne de séparation se saisit à la souris,
450
+ se règle aussi aux flèches du clavier une fois qu'elle a le focus, et rend sa largeur
451
+ d'origine sur un double-clic. Les largeurs choisies sont retenues par le navigateur d'une
452
+ visite à l'autre ;
453
+ - un rafraîchissement automatique toutes les 5 secondes, débrayable.
454
+
455
+ **Les adresses ne sont pas de l'information de lecture.** La console résout partout
456
+ `box_9f2c4a01b3d7e5f8` en nom d'agent, et ne montre l'adresse qu'aux endroits où elle sert
457
+ vraiment : les détails d'un message, l'en-tête d'un fil et les boîtes sans nom, chaque fois
458
+ avec un bouton de copie. Quand plusieurs sessions d'un même projet apparaissent, le nom de
459
+ l'agent est suivi de celui de la boîte entre parenthèses — sans quoi un fil se lirait
460
+ « Documentor Dev → Documentor Dev » sans dire de quelles sessions il parle. Une boîte jamais connectée se distingue à son avatar en contour et à la mention
461
+ « jamais connectée » — elle reste listée, puisqu'elle a du courrier en attente.
462
+
463
+ À l'ouverture, la console demande `MESSENGER_ADMIN_TOKEN` dans un formulaire. Le serveur
464
+ le vérifie (`POST /ui/login`) et ouvre une **session** de 12 heures, portée par un cookie
465
+ `HttpOnly` + `SameSite=Strict` (+ `Secure` en HTTPS). Le cookie ne contient jamais le
466
+ jeton, seulement un identifiant de session opaque, oublié au redémarrage du serveur ou
467
+ sur « Se déconnecter ». Le navigateur ne stocke rien d'autre, et le jeton n'apparaît
468
+ jamais dans une URL.
469
+
470
+ Cette session n'est reconnue que sur `/ui` et `/admin` : elle ne permet pas d'appeler
471
+ l'API des agents. Un site tiers ne peut donc pas faire agir un agent à travers le
472
+ navigateur de l'administrateur. Les routes `/admin` acceptent toujours le jeton admin en
473
+ `Authorization: Bearer` pour les scripts. Le formulaire est limité à 10 tentatives par
474
+ quart d'heure et par IP.
475
+
476
+ La console n'envoie ni ne modifie aucun message. Les seules écritures qu'elle commande
477
+ sont des suppressions, derrière l'icône de corbeille de la barre du haut. **Deux
478
+ nettoyages y cohabitent, avec chacun son bouton, parce qu'ils ne visent pas la même
479
+ chose :**
480
+
481
+ - **les boîtes vides** : rien n'y est jamais passé, ni envoi ni réception. Elles n'ont pas
482
+ d'âge, une boîte vide est vide qu'elle date d'un mois ou d'une minute. C'est le
483
+ nettoyage à passer sur un serveur qui a vu défiler beaucoup de sessions muettes. **Un
484
+ agent dont toutes les boîtes disparaissent quitte l'annuaire avec elles**, faute de
485
+ boîte à regrouper ;
486
+ - **les boîtes inactives depuis une date** : elles ont servi, mais plus rien ne les a
487
+ touchées depuis. **Une boîte créée après cette date est épargnée, même vide** — une
488
+ session qui vient de démarrer et n'a pas encore parlé n'est pas une boîte à l'abandon.
489
+
490
+ S'y ajoute la suppression d'**une boîte en particulier** : chaque ligne de la colonne de
491
+ gauche porte une corbeille, révélée au survol ou au clavier. Elle n'efface rien d'elle-même,
492
+ elle ouvre la même fenêtre sur cette seule boîte, avec ce qu'elle contient sous les yeux.
493
+ C'est la seule des trois qui ne demande aucun critère : l'adresse vient d'être montrée du
494
+ doigt, il n'y a rien à revérifier.
495
+
496
+ Toutes se font en deux temps, parce qu'elles ne se rattrapent pas :
497
+
498
+ 1. **L'aperçu** liste les boîtes concernées, chacune avec son nom, son adresse, son nombre
499
+ de messages et sa dernière activité. Chaque ligne se décoche.
500
+ 2. **La confirmation** demande deux clics, et n'envoie que les adresses affichées, avec le
501
+ critère qui les a désignées. Le serveur, lui, reconfronte chacune à ce critère avant de
502
+ supprimer : une boîte redevenue active, ou qui vient de recevoir son premier message,
503
+ fait échouer le nettoyage entier, nommément. Changer de critère, changer la date ou
504
+ décocher une ligne annule une confirmation déjà donnée.
505
+
506
+ **Ce qui disparaît.** Un message a deux copies, une par correspondant : supprimer une
507
+ boîte n'emporte que sa moitié. Le message lui-même ne s'efface qu'une fois sa dernière
508
+ copie partie, autrement dit quand plus personne ne pourrait le lire ; un fil vidé de ses
509
+ messages s'en va avec. Ce que l'autre côté a reçu reste intact, expéditeur compris.
510
+
511
+ Le registre local des boîtes n'a pas à être nettoyé après une purge : une entrée qui
512
+ désigne une boîte disparue n'est jamais reprise, et s'oublie d'elle-même.
513
+
514
+ ---
515
+
516
+ ## Configuration
517
+
518
+ Toutes les variables sont documentées dans [`.env.example`](.env.example). L'essentiel :
519
+
520
+ ### Serveur
521
+
522
+ | Variable | Défaut | Rôle |
523
+ |---|---|---|
524
+ | `MESSENGER_PORT` | `3600` | Port d'écoute |
525
+ | `MESSENGER_HOST` | `127.0.0.1` (figé) | Non configurable ; une valeur non-loopback fait échouer le démarrage |
526
+ | `MESSENGER_DB_PATH` | `./data/messenger.db` | Fichier SQLite |
527
+ | `MESSENGER_TOKEN` | — | **Obligatoire** (sauf si `MESSENGER_AGENT_TOKENS`) : jeton exigé sur toute requête. `--generate-token` en produit un |
528
+ | `MESSENGER_AGENT_TOKENS` | — | Un jeton par agent : `planner:tok-a,coder:tok-b` |
529
+ | `MESSENGER_ADMIN_TOKEN` | `MESSENGER_TOKEN` | Jeton de la console ; obligatoire si seul `MESSENGER_AGENT_TOKENS` est défini |
530
+ | `MESSENGER_RATE_LIMIT_MAX` | `600` | Requêtes par IP et par fenêtre |
531
+ | `MESSENGER_RATE_LIMIT_WINDOW_MINUTES` | `15` | Durée de la fenêtre |
532
+ | `MESSENGER_TRASH_RETENTION_DAYS` | `30` | Purge automatique de la corbeille (`0` = jamais) |
533
+ | `MESSENGER_MAX_WAIT_SECONDS` | `300` | Plafond d'une attente longue |
534
+
535
+ ### Agent (mode MCP)
536
+
537
+ | Variable | Requis | Rôle |
538
+ |---|---|---|
539
+ | `MESSENGER_AGENT_ID` | — | Adresse **fixe**. Vide (recommandé) = une boîte par session |
540
+ | `MESSENGER_SERVER_URL` | ✅ | `http://` ou `https://`, local ou distant |
541
+ | `MESSENGER_TOKEN` | ✅ | Jeton présenté au serveur, sur chaque appel |
542
+ | `MESSENGER_AGENT_NAME` | — | Nom de l'agent (le projet, typiquement), affiché à côté du nom de la boîte |
543
+ | `MESSENGER_AGENT_DESCRIPTION` | — | À quoi sert cet agent, pour ses correspondants |
544
+ | `MESSENGER_TIMEOUT_MS` | `15000` | Délai maximal d'un appel HTTP |
545
+ | `MESSENGER_STATE_DIR` | — | Répertoire des relais vers le veilleur (défaut : `~/.mcp-messenger`) |
546
+
547
+ ---
548
+
549
+ ## API HTTP
550
+
551
+ **Toutes** les routes de données exigent un jeton, présenté par `Authorization: Bearer
552
+ <token>` ou par l'en-tête `X-Messenger-Token` — jamais dans l'URL. Les routes `/api/*`
553
+ exigent en plus l'en-tête `X-Agent-Id`, et le jeton doit correspondre à cet agent lorsque
554
+ `MESSENGER_AGENT_TOKENS` est utilisé. Les routes `/admin/*` acceptent aussi la session de
555
+ la console (cookie), qui elle n'ouvre rien d'autre.
556
+
557
+ | Méthode | Route | Rôle |
558
+ |---|---|---|
559
+ | `GET` | `/health` | État du service |
560
+ | `POST` | `/api/agents/register` | S'inscrire à l'annuaire (`fresh: true` = exige une adresse neuve) |
561
+ | `GET` | `/api/agents` | Annuaire des boîtes, sauf celle de l'appelant |
562
+ | `GET` | `/api/agents/:id` | Une boîte, ou 404 |
563
+ | `PATCH` | `/api/agents/me` | Renommer la boîte courante |
564
+ | `GET` | `/api/mailbox` | Compteurs de la boîte, et présence d'un veilleur (`watcher`) |
565
+ | `POST` | `/api/messages` | Envoyer un message |
566
+ | `GET` | `/api/messages` | Lister (`folder`, `unread_only`, `from`, `thread_id`, `search`, `limit`, `offset`) |
567
+ | `GET` | `/api/messages/:id` | Lire (`?mark_read=false` pour ne pas marquer) |
568
+ | `POST` | `/api/messages/mark` | Marquer lu / suivi |
569
+ | `POST` | `/api/messages/move` | Déplacer vers un dossier |
570
+ | `POST` | `/api/messages/delete` | Corbeille ou suppression définitive |
571
+ | `GET` | `/api/threads` | Fils de la boîte (`unread_only`, `search`, `limit`, `offset`) |
572
+ | `GET` | `/api/threads/:id` | Fil complet, avec son intitulé |
573
+ | `PATCH` | `/api/threads/:id` | Nommer le fil (`title`, `description`) — participants uniquement |
574
+ | `GET` `POST` `DELETE` | `/api/folders[/:name]` | Gérer les dossiers |
575
+ | `GET` | `/api/wait` | Attente longue (`timeout_seconds`, `folder`, `watcher`) |
576
+ | `GET` | `/admin/overview` · `/admin/messages[/:id]` | Supervision, lecture seule |
577
+ | `GET` | `/admin/threads` | Fils du serveur entier (`agent` — une ou plusieurs adresses séparées par des virgules —, `search`, `unread_only`, `priority`, `limit`, `offset`) |
578
+ | `GET` | `/admin/threads/:id` | Fil complet, corps inclus |
579
+ | `GET` | `/admin/mailboxes/stale` | Boîtes sans activité depuis `before`, avec ce que chacune contient |
580
+ | `GET` | `/admin/mailboxes/empty` | Boîtes par lesquelles rien n'est jamais passé, sans critère de date |
581
+ | `POST` | `/admin/mailboxes/purge` | Supprime les boîtes nommées (`mailboxes`, plus `before` **ou** `empty: true`) — refusé si l'une d'elles ne répond plus au critère |
582
+ | `DELETE` | `/admin/mailboxes/:id` | Supprime une boîte désignée, quel que soit son état — 404 si l'adresse est inconnue |
583
+ | `POST` | `/ui/login` · `/ui/logout` | Session de la console (`{ "token": … }` → cookie `HttpOnly`) |
584
+
585
+ ---
586
+
587
+ ## Sécurité
588
+
589
+ - **Écoute exclusivement sur `127.0.0.1`** : l'interface n'est pas configurable, et une
590
+ valeur non-loopback fait échouer le démarrage plutôt que d'être ignorée en silence.
591
+ - **Refus de démarrage sans jeton** : un serveur qui tourne est un serveur authentifié.
592
+ - **Jeton vérifié sur chaque requête entrante**, avant tout traitement — y compris
593
+ `/health` et les fichiers de la console.
594
+ - **Jetons par agent** (`MESSENGER_AGENT_TOKENS`) : un jeton valide ne permet pas
595
+ d'emprunter l'identité d'un autre agent. À préférer au jeton partagé dès que les agents
596
+ ne se font pas mutuellement confiance.
597
+ - Comparaison des jetons à **temps constant**, en-têtes de sécurité (`helmet`), **rate
598
+ limiting** par IP, corps JSON limité à 2 Mo, erreurs internes génériques (le détail reste
599
+ dans les logs).
600
+ - **Isolation des boîtes** garantie côté serveur : les identifiants de messages fournis par
601
+ un agent sont systématiquement filtrés sur ses propres livraisons.
602
+ - La console web n'insère jamais de HTML fourni par un agent : le corps des messages est
603
+ rendu en markdown par construction de nœuds, jamais par assignation de balisage, et la
604
+ CSP interdit tout script tiers.
605
+ - La console ne peut ni écrire ni modifier un message. Ses seules commandes destructrices
606
+ suppriment des boîtes : réservées au jeton d'administration, confirmées deux fois, et
607
+ revérifiées par le serveur quand elles portent sur un lot.
608
+ - Derrière un reverse proxy TLS, exposer le serveur en `https://` et donner cette URL aux
609
+ agents via `MESSENGER_SERVER_URL`.
610
+
611
+ ---
612
+
613
+ ## Développement
614
+
615
+ ```bash
616
+ npm install
617
+ npm run generate-token # jeton aléatoire (32 octets, base64url)
618
+ npm run build # tsc + copie des fichiers de la console web
619
+ npm test # build puis suite Vitest complète
620
+ npm run test:watch
621
+ npm run typecheck
622
+ npm run serve # serveur local sur le port 3600
623
+ ```
624
+
625
+ ### Structure
626
+
627
+ ```
628
+ src/
629
+ index.ts # point d'entrée : --server, --watch, --help, --version, sinon mode MCP
630
+ config.ts # lecture et validation des variables d'environnement
631
+ logger.ts # journalisation sur stderr (stdout est réservé au JSON-RPC)
632
+ core/store.ts # SQLite : agents, messages, livraisons, dossiers
633
+ server/app.ts # API HTTP, authentification, supervision
634
+ server/watchers.ts # présence des veilleurs, en mémoire : état de connexion
635
+ server/start.ts # démarrage, arrêt propre, purge de la corbeille
636
+ server/ui/ # console web (HTML/CSS/JS sans dépendance)
637
+ server/ui/markdown.js # rendu markdown du corps des messages, en nœuds DOM
638
+ cli/watch.ts # mode veilleur : une ligne sur stdout par message entrant
639
+ cli/watch-handoff.ts # relais 0600 entre la session MCP et son veilleur
640
+ mcp/mailbox-registry.ts # registre local des boîtes : reprise d'une session interrompue
641
+ client/ # client HTTP utilisé par le mode MCP
642
+ mcp/ # définition des outils et serveur MCP stdio
643
+ test/ # store, config, API HTTP, outils MCP, binaire réel
644
+ ```
645
+
646
+ ### Tests
647
+
648
+ 287 tests couvrant : les suppressions de boîtes commandées depuis la console — une boîte
649
+ désignée à la main, les vides, celles sans activité depuis une date — avec leur aperçu,
650
+ leur double confirmation et le refus nominatif d'une boîte qui ne répond plus au critère, la reprise de la boîte d'une session interrompue (clé de session de
651
+ l'hôte, boîte seulement proposée à défaut, boîte d'une session vivante jamais reprise), le rendu markdown des messages et l'aplatissement des aperçus (dont
652
+ l'innocuité du rendu : ni HTML interprété, ni lien vers un autre schéma qu'`http`, `https`
653
+ ou `mailto`), le stockage et l'isolation des boîtes, la validation de configuration,
654
+ l'API HTTP et son authentification (refus systématique sans jeton, jeton partagé, jetons par
655
+ agent, jeton de console distinct), les outils MCP de bout en bout (deux agents qui
656
+ conversent à travers un vrai serveur), l'allocation des boîtes par session — deux processus
657
+ MCP lancés avec la même configuration obtiennent deux adresses différentes, et une
658
+ troisième session reprend la boîte de l'une d'elles — et le binaire publié lui-même.
659
+
660
+ La notification passive est couverte de bout en bout : le relais vers le veilleur (isolation
661
+ entre boîtes, adresse jamais transformée en chemin, nettoyage), le format d'évènement (une
662
+ ligne, quel que soit le sujet), et un veilleur réellement lancé comme processus séparé, sans
663
+ jeton dans son environnement, qui émet sa ligne à l'arrivée d'un message — et `whoami` qui
664
+ passe de « NOT armed » à « armed » quand ce veilleur prend son poste.
665
+
666
+ ---
667
+
668
+ ## Licence
669
+
670
+ ISC