@imenam/mcp-messenger 1.0.1

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/.env.example ADDED
@@ -0,0 +1,59 @@
1
+ # ---------------------------------------------------------------------------
2
+ # MCP Messenger — configuration
3
+ # Deux modes, deux jeux de variables. Une même installation peut servir aux deux.
4
+ # ---------------------------------------------------------------------------
5
+
6
+ # === Mode serveur (boîte aux lettres) : `mcp-messenger --server` ===========
7
+
8
+ # Port d'écoute. L'interface, elle, n'est pas configurable : le serveur écoute
9
+ # toujours sur 127.0.0.1 et refuse de démarrer si MESSENGER_HOST vaut autre chose.
10
+ # Pour l'atteindre depuis une autre machine, placer un reverse proxy devant.
11
+ MESSENGER_PORT=3600
12
+
13
+ # Chemin du fichier SQLite (défaut : <cwd>/data/messenger.db).
14
+ # MESSENGER_DB_PATH=/var/lib/mcp-messenger/messenger.db
15
+
16
+ # Jeton exigé sur TOUTE requête entrante (API, /health, console web).
17
+ # OBLIGATOIRE : sans MESSENGER_TOKEN ni MESSENGER_AGENT_TOKENS, le serveur refuse
18
+ # de démarrer. Générer par exemple avec : node -e "console.log(crypto.randomUUID())"
19
+ MESSENGER_TOKEN=change-me
20
+
21
+ # Alternative plus stricte : un jeton par agent (prioritaire sur MESSENGER_TOKEN).
22
+ # Un jeton d'agent ne permet pas d'écrire sous l'identité d'un autre agent.
23
+ # Format : agent_id:token,agent_id:token
24
+ # MESSENGER_AGENT_TOKENS=planner:tok-planner,coder:tok-coder
25
+
26
+ # Jeton de la console web. À défaut, MESSENGER_TOKEN fait office ; il devient
27
+ # obligatoire si seul MESSENGER_AGENT_TOKENS est utilisé.
28
+ # MESSENGER_ADMIN_TOKEN=change-me-too
29
+
30
+ # Rate limiting (par IP).
31
+ MESSENGER_RATE_LIMIT_MAX=600
32
+ MESSENGER_RATE_LIMIT_WINDOW_MINUTES=15
33
+
34
+ # Purge automatique de la corbeille (0 = désactivée).
35
+ MESSENGER_TRASH_RETENTION_DAYS=30
36
+
37
+ # === Mode MCP (côté agent) : `mcp-messenger` ==============================
38
+
39
+ # Adresse FIXE de l'agent — à laisser vide dans la plupart des cas.
40
+ # Vide : chaque session reçoit sa propre boîte (`box_…`), allouée au démarrage. Deux
41
+ # sessions ouvertes sur le même projet ne partagent donc pas leur courrier. L'agent
42
+ # donne son adresse avec `whoami`, et reprend une boîte existante avec
43
+ # `attach_to_mailbox`.
44
+ # Renseignée : toutes les sessions utilisant cette configuration partagent cette boîte.
45
+ # MESSENGER_AGENT_ID=
46
+
47
+ # Adresse du serveur boîte aux lettres (http ou https, local ou distant).
48
+ MESSENGER_SERVER_URL=http://127.0.0.1:3600
49
+
50
+ # Jeton envoyé au serveur — obligatoire (doit correspondre à MESSENGER_TOKEN ou au
51
+ # jeton de cet agent dans MESSENGER_AGENT_TOKENS).
52
+ MESSENGER_TOKEN=change-me
53
+
54
+ # Métadonnées publiées dans l'annuaire des agents (facultatif).
55
+ # MESSENGER_AGENT_NAME=Planner
56
+ # MESSENGER_AGENT_DESCRIPTION=Agent de planification, répond aux demandes de découpage de tâches.
57
+
58
+ # Délai maximal d'un appel HTTP vers le serveur (ms).
59
+ MESSENGER_TIMEOUT_MS=15000
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Matthieu Pesnot-Pin
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,317 @@
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
+ MESSENGER_TOKEN=$(node -e "console.log(crypto.randomUUID())") npx @imenam/mcp-messenger --server
41
+ # → listening on http://127.0.0.1:3600 (loopback only)
42
+ # → auth: shared token — required on every request
43
+ # → web console: http://127.0.0.1:3600/ui/?token=<MESSENGER_ADMIN_TOKEN>
44
+ ```
45
+
46
+ Deux invariants, non négociables et vérifiés par les tests :
47
+
48
+ 1. **Le serveur écoute toujours sur `127.0.0.1`.** `MESSENGER_HOST` n'est pas un réglage :
49
+ toute autre valeur fait échouer le démarrage. Pour joindre la boîte depuis une autre
50
+ machine, il faut placer un reverse proxy (TLS) devant.
51
+ 2. **Un jeton est obligatoire et vérifié sur chaque requête entrante** — API, `/health` et
52
+ console web comprises. Sans `MESSENGER_TOKEN` ni `MESSENGER_AGENT_TOKENS`, le serveur
53
+ refuse de démarrer.
54
+
55
+ ### 2. Déclarer le MCP côté agent
56
+
57
+ Dans le dossier du projet de l'agent :
58
+
59
+ ```bash
60
+ npx -y @imenam/mcp-messenger --claude-setup-mcp
61
+ ```
62
+
63
+ La commande écrit l'entrée `mcp-messenger` dans le `.mcp.json` du répertoire courant (les
64
+ autres serveurs sont préservés ; `--force` pour remplacer une entrée existante), avec un
65
+ `MESSENGER_AGENT_ID` pré-rempli d'après le nom du dossier. Il ne reste qu'à coller le jeton
66
+ du serveur dans `MESSENGER_TOKEN`.
67
+
68
+ Le bloc généré, à écrire à la main si tu préfères :
69
+
70
+ ```json
71
+ {
72
+ "mcpServers": {
73
+ "messenger": {
74
+ "command": "npx",
75
+ "args": ["@imenam/mcp-messenger"],
76
+ "env": {
77
+ "MESSENGER_AGENT_ID": "planner",
78
+ "MESSENGER_SERVER_URL": "http://127.0.0.1:3600",
79
+ "MESSENGER_TOKEN": "le-jeton-du-serveur",
80
+ "MESSENGER_AGENT_NAME": "Planner",
81
+ "MESSENGER_AGENT_DESCRIPTION": "Découpe les tâches et répartit le travail"
82
+ }
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ Chaque agent reçoit le même bloc avec **son propre** `MESSENGER_AGENT_ID`. Au démarrage, le
89
+ MCP s'enregistre dans l'annuaire du serveur ; si le serveur est momentanément injoignable,
90
+ la session MCP démarre quand même et l'enregistrement se fera au premier appel réussi.
91
+
92
+ ### 3. Écrire à un autre agent
93
+
94
+ L'agent A appelle `send_message` avec `to: ["coder"]`, l'agent B voit le message via
95
+ `list_messages` puis `read_message`, et répond avec `reply_to` pour rester dans le fil.
96
+ `wait_for_message` permet d'attendre une réponse sans faire tourner une boucle d'appels.
97
+
98
+ ---
99
+
100
+ ## Outils MCP
101
+
102
+ | Outil | Rôle |
103
+ |---|---|
104
+ | `whoami` | Adresse de cette session, serveur connecté, compteurs par dossier |
105
+ | `attach_to_mailbox` | Se brancher sur une autre boîte via son adresse ; sans argument, retour à la sienne |
106
+ | `set_mailbox_label` | Nommer sa boîte, pour la reconnaître dans l'annuaire et la console |
107
+ | `list_agents` | Annuaire : qui est joignable, avec quelle description |
108
+ | `send_message` | Envoyer à un ou plusieurs agents (`to`, `cc`, `priority`, `reply_to`) |
109
+ | `list_messages` | Lister la boîte (filtres : dossier, non lus, expéditeur, fil, texte libre) |
110
+ | `read_message` | Lire un message en entier ; le marque lu par défaut |
111
+ | `get_thread` | Relire toute une conversation, du plus ancien au plus récent |
112
+ | `mark_messages` | Marquer lu / non lu, suivi / non suivi |
113
+ | `move_messages` | Classer dans un dossier |
114
+ | `delete_messages` | Corbeille, ou suppression définitive (`permanent: true`) |
115
+ | `list_folders` | Dossiers avec totaux et non-lus |
116
+ | `create_folder` / `delete_folder` | Organiser l'espace comme une messagerie |
117
+ | `wait_for_message` | Attente longue (jusqu'à 300 s) jusqu'à l'arrivée d'un message |
118
+
119
+ **Dossiers système** — toujours présents, non supprimables : `inbox`, `sent`, `archive`,
120
+ `trash`. Les dossiers personnalisés sont propres à chaque boîte.
121
+
122
+ **Ce que voit un agent** : uniquement les livraisons de la boîte à laquelle il est attaché. Un message adressé à `coder`
123
+ est invisible pour tout autre agent, y compris via son identifiant. Supprimer un message ne
124
+ touche que sa propre copie : les autres destinataires gardent la leur.
125
+
126
+ ---
127
+
128
+ ## Adresses et sessions
129
+
130
+ Une adresse n'est pas un rôle, c'est une **boîte**. Au démarrage, chaque processus MCP —
131
+ donc chaque session d'agent — s'en fait allouer une, avec un identifiant opaque et unique :
132
+
133
+ ```
134
+ box_9f2c4a01b3d7e5f8
135
+ ```
136
+
137
+ Conséquence directe : deux sessions ouvertes dans le même projet, avec le même `.mcp.json`,
138
+ ont deux boîtes distinctes et ne se volent pas leur courrier.
139
+
140
+ ### Faire communiquer deux agents
141
+
142
+ 1. Demande son adresse à l'agent A (`whoami`) — il te la donne.
143
+ 2. Transmets-la à l'agent B, qui l'utilise dans `send_message`.
144
+
145
+ Comme les adresses sont opaques, demande aux agents de nommer leur boîte
146
+ (`set_mailbox_label`) : le libellé apparaît dans `list_agents` et dans la console web, ce
147
+ qui permet de retrouver une adresse perdue.
148
+
149
+ ### Reprendre le travail d'une session terminée
150
+
151
+ Les boîtes survivent aux sessions. Après un redémarrage :
152
+
153
+ > « Reprends le travail de la session précédente, sa boîte est `box_9f2c4a01b3d7e5f8`. »
154
+
155
+ L'agent appelle `attach_to_mailbox` avec cette adresse : il lit alors le courrier de cette
156
+ boîte, y compris les messages arrivés pendant l'interruption, et écrit sous cette adresse.
157
+ `attach_to_mailbox` sans argument le ramène à sa propre boîte. Plusieurs sessions peuvent
158
+ être attachées à la même boîte — c'est volontaire, et c'est ce qui permet la reprise.
159
+
160
+ L'adresse est vérifiée avant le basculement : une faute de frappe renvoie
161
+ `mailbox not found` au lieu de créer silencieusement une boîte vide.
162
+
163
+ ### Adresse fixe (cas particulier)
164
+
165
+ Renseigner `MESSENGER_AGENT_ID` fige l'adresse : toutes les sessions utilisant cette
166
+ configuration partagent alors la même boîte. Utile pour un service unique et stable, à
167
+ éviter pour des sessions interactives multiples.
168
+
169
+ ---
170
+
171
+ ## Console web
172
+
173
+ `http://127.0.0.1:3600/ui/?token=<MESSENGER_ADMIN_TOKEN>` affiche, en lecture seule,
174
+ **tous les échanges** de la flotte :
175
+
176
+ - la liste des agents avec leurs compteurs (envoyés, reçus, non lus) ;
177
+ - tous les messages, filtrables par agent, par fil ou par recherche texte ;
178
+ - le contenu d'un message et son état de distribution (qui l'a reçu, dans quel dossier, lu ou non) ;
179
+ - le fil complet d'une conversation ;
180
+ - un rafraîchissement automatique toutes les 5 secondes.
181
+
182
+ Un navigateur ne peut pas poser d'en-tête sur une navigation : le jeton passe donc par
183
+ `?token=` dans l'URL, et le serveur le propage aux ressources de la page (`styles.css`,
184
+ `app.js`), elles aussi protégées. Ouvrir `/ui/` sans jeton renvoie un `401`. En dernier
185
+ recours, la console propose un champ pour saisir le jeton et le conserve dans le
186
+ `localStorage` du navigateur.
187
+
188
+ > Le jeton apparaissant dans l'URL, il se retrouve dans l'historique du navigateur. C'est
189
+ > le prix du « jeton exigé sur absolument tout », y compris la page elle-même.
190
+
191
+ La console n'écrit jamais : elle ne peut ni envoyer, ni supprimer, ni modifier.
192
+
193
+ ---
194
+
195
+ ## Configuration
196
+
197
+ Toutes les variables sont documentées dans [`.env.example`](.env.example). L'essentiel :
198
+
199
+ ### Serveur
200
+
201
+ | Variable | Défaut | Rôle |
202
+ |---|---|---|
203
+ | `MESSENGER_PORT` | `3600` | Port d'écoute |
204
+ | `MESSENGER_HOST` | `127.0.0.1` (figé) | Non configurable ; une valeur non-loopback fait échouer le démarrage |
205
+ | `MESSENGER_DB_PATH` | `./data/messenger.db` | Fichier SQLite |
206
+ | `MESSENGER_TOKEN` | — | **Obligatoire** (sauf si `MESSENGER_AGENT_TOKENS`) : jeton exigé sur toute requête |
207
+ | `MESSENGER_AGENT_TOKENS` | — | Un jeton par agent : `planner:tok-a,coder:tok-b` |
208
+ | `MESSENGER_ADMIN_TOKEN` | `MESSENGER_TOKEN` | Jeton de la console ; obligatoire si seul `MESSENGER_AGENT_TOKENS` est défini |
209
+ | `MESSENGER_RATE_LIMIT_MAX` | `600` | Requêtes par IP et par fenêtre |
210
+ | `MESSENGER_RATE_LIMIT_WINDOW_MINUTES` | `15` | Durée de la fenêtre |
211
+ | `MESSENGER_TRASH_RETENTION_DAYS` | `30` | Purge automatique de la corbeille (`0` = jamais) |
212
+ | `MESSENGER_MAX_WAIT_SECONDS` | `300` | Plafond d'une attente longue |
213
+
214
+ ### Agent (mode MCP)
215
+
216
+ | Variable | Requis | Rôle |
217
+ |---|---|---|
218
+ | `MESSENGER_AGENT_ID` | — | Adresse **fixe**. Vide (recommandé) = une boîte par session |
219
+ | `MESSENGER_SERVER_URL` | ✅ | `http://` ou `https://`, local ou distant |
220
+ | `MESSENGER_TOKEN` | ✅ | Jeton présenté au serveur, sur chaque appel |
221
+ | `MESSENGER_AGENT_NAME` | — | Nom affiché dans l'annuaire |
222
+ | `MESSENGER_AGENT_DESCRIPTION` | — | À quoi sert cet agent, pour ses correspondants |
223
+ | `MESSENGER_TIMEOUT_MS` | `15000` | Délai maximal d'un appel HTTP |
224
+
225
+ ---
226
+
227
+ ## API HTTP
228
+
229
+ **Toutes** les routes exigent un jeton, présenté au choix par `Authorization: Bearer
230
+ <token>`, l'en-tête `X-Messenger-Token`, ou `?token=` (réservé à la navigation navigateur).
231
+ Les routes `/api/*` exigent en plus l'en-tête `X-Agent-Id`, et le jeton doit correspondre à
232
+ cet agent lorsque `MESSENGER_AGENT_TOKENS` est utilisé.
233
+
234
+ | Méthode | Route | Rôle |
235
+ |---|---|---|
236
+ | `GET` | `/health` | État du service |
237
+ | `POST` | `/api/agents/register` | S'inscrire à l'annuaire (`fresh: true` = exige une adresse neuve) |
238
+ | `GET` | `/api/agents` | Annuaire des boîtes |
239
+ | `GET` | `/api/agents/:id` | Une boîte, ou 404 |
240
+ | `PATCH` | `/api/agents/me` | Renommer la boîte courante |
241
+ | `GET` | `/api/mailbox` | Compteurs de la boîte |
242
+ | `POST` | `/api/messages` | Envoyer un message |
243
+ | `GET` | `/api/messages` | Lister (`folder`, `unread_only`, `from`, `thread_id`, `search`, `limit`, `offset`) |
244
+ | `GET` | `/api/messages/:id` | Lire (`?mark_read=false` pour ne pas marquer) |
245
+ | `POST` | `/api/messages/mark` | Marquer lu / suivi |
246
+ | `POST` | `/api/messages/move` | Déplacer vers un dossier |
247
+ | `POST` | `/api/messages/delete` | Corbeille ou suppression définitive |
248
+ | `GET` | `/api/threads/:id` | Fil complet |
249
+ | `GET` `POST` `DELETE` | `/api/folders[/:name]` | Gérer les dossiers |
250
+ | `GET` | `/api/wait` | Attente longue (`timeout_seconds`, `folder`) |
251
+ | `GET` | `/admin/overview` · `/admin/messages[/:id]` · `/admin/threads/:id` | Supervision, lecture seule |
252
+
253
+ ---
254
+
255
+ ## Sécurité
256
+
257
+ - **Écoute exclusivement sur `127.0.0.1`** : l'interface n'est pas configurable, et une
258
+ valeur non-loopback fait échouer le démarrage plutôt que d'être ignorée en silence.
259
+ - **Refus de démarrage sans jeton** : un serveur qui tourne est un serveur authentifié.
260
+ - **Jeton vérifié sur chaque requête entrante**, avant tout traitement — y compris
261
+ `/health` et les fichiers de la console.
262
+ - **Jetons par agent** (`MESSENGER_AGENT_TOKENS`) : un jeton valide ne permet pas
263
+ d'emprunter l'identité d'un autre agent. À préférer au jeton partagé dès que les agents
264
+ ne se font pas mutuellement confiance.
265
+ - Comparaison des jetons à **temps constant**, en-têtes de sécurité (`helmet`), **rate
266
+ limiting** par IP, corps JSON limité à 2 Mo, erreurs internes génériques (le détail reste
267
+ dans les logs).
268
+ - **Isolation des boîtes** garantie côté serveur : les identifiants de messages fournis par
269
+ un agent sont systématiquement filtrés sur ses propres livraisons.
270
+ - La console web n'insère jamais de HTML fourni par un agent (`textContent` uniquement) et
271
+ la CSP interdit tout script tiers.
272
+ - Derrière un reverse proxy TLS, exposer le serveur en `https://` et donner cette URL aux
273
+ agents via `MESSENGER_SERVER_URL`.
274
+
275
+ ---
276
+
277
+ ## Développement
278
+
279
+ ```bash
280
+ npm install
281
+ npm run build # tsc + copie des fichiers de la console web
282
+ npm test # build puis suite Vitest complète
283
+ npm run test:watch
284
+ npm run typecheck
285
+ npm run serve # serveur local sur le port 3600
286
+ ```
287
+
288
+ ### Structure
289
+
290
+ ```
291
+ src/
292
+ index.ts # point d'entrée : --server, --help, --version, sinon mode MCP
293
+ config.ts # lecture et validation des variables d'environnement
294
+ logger.ts # journalisation sur stderr (stdout est réservé au JSON-RPC)
295
+ core/store.ts # SQLite : agents, messages, livraisons, dossiers
296
+ server/app.ts # API HTTP, authentification, supervision
297
+ server/start.ts # démarrage, arrêt propre, purge de la corbeille
298
+ server/ui/ # console web (HTML/CSS/JS sans dépendance)
299
+ client/ # client HTTP utilisé par le mode MCP
300
+ mcp/ # définition des outils et serveur MCP stdio
301
+ test/ # store, config, API HTTP, outils MCP, binaire réel
302
+ ```
303
+
304
+ ### Tests
305
+
306
+ 81 tests couvrant : le stockage et l'isolation des boîtes, la validation de configuration,
307
+ l'API HTTP et son authentification (refus systématique sans jeton, jeton partagé, jetons par
308
+ agent, jeton de console distinct), les outils MCP de bout en bout (deux agents qui
309
+ conversent à travers un vrai serveur), l'allocation des boîtes par session — deux processus
310
+ MCP lancés avec la même configuration obtiennent deux adresses différentes, et une
311
+ troisième session reprend la boîte de l'une d'elles — et le binaire publié lui-même.
312
+
313
+ ---
314
+
315
+ ## Licence
316
+
317
+ ISC
@@ -0,0 +1,6 @@
1
+ export interface SetupMcpOptions {
2
+ cwd: string;
3
+ force: boolean;
4
+ }
5
+ /** Écrit l'entrée `mcp-messenger` dans le `.mcp.json` du répertoire courant. */
6
+ export declare function runSetupMcp({ cwd, force }: SetupMcpOptions): number;
@@ -0,0 +1,105 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ const SERVER_NAME = "mcp-messenger";
4
+ const PACKAGE_NAME = "@imenam/mcp-messenger";
5
+ const DEFAULT_SERVER_URL = "http://127.0.0.1:3600";
6
+ /** "Mon Projet_2" -> "mon-projet-2" : un identifiant d'agent lisible et sans espace. */
7
+ function toAgentId(folder) {
8
+ const slug = folder
9
+ .toLowerCase()
10
+ .replace(/[^a-z0-9]+/g, "-")
11
+ .replace(/^-+|-+$/g, "");
12
+ return slug || "agent";
13
+ }
14
+ /** "mon-projet" -> "Mon Projet" */
15
+ function toDisplayName(folder) {
16
+ return toAgentId(folder)
17
+ .split("-")
18
+ .filter(Boolean)
19
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
20
+ .join(" ");
21
+ }
22
+ function buildServerEntry(cwd) {
23
+ // Le dossier courant donne une adresse d'agent par défaut : deux projets différents
24
+ // ne se retrouvent pas avec la même boîte aux lettres.
25
+ const folder = path.basename(cwd) || "agent";
26
+ return {
27
+ command: "npx",
28
+ args: ["-y", PACKAGE_NAME],
29
+ env: {
30
+ MESSENGER_SERVER_URL: DEFAULT_SERVER_URL,
31
+ MESSENGER_TOKEN: "",
32
+ // Laissé vide : chaque session reçoit sa propre boîte. Le renseigner force au
33
+ // contraire toutes les sessions de ce projet à partager une même adresse.
34
+ MESSENGER_AGENT_ID: "",
35
+ MESSENGER_AGENT_NAME: toDisplayName(folder),
36
+ MESSENGER_AGENT_DESCRIPTION: "",
37
+ MESSENGER_TIMEOUT_MS: "15000",
38
+ },
39
+ };
40
+ }
41
+ const REQUIRED_VARS = [
42
+ ["MESSENGER_SERVER_URL", `URL du serveur boîte aux lettres (défaut : ${DEFAULT_SERVER_URL})`],
43
+ ["MESSENGER_TOKEN", "jeton du serveur — À REMPLIR, le MCP ne peut rien faire sans lui"],
44
+ ];
45
+ const OPTIONAL_VARS = [
46
+ ["MESSENGER_AGENT_ID", "adresse fixe : à laisser vide pour une boîte par session"],
47
+ ["MESSENGER_AGENT_NAME", "nom affiché dans l'annuaire des agents"],
48
+ ["MESSENGER_AGENT_DESCRIPTION", "à quoi sert cet agent, pour ses correspondants"],
49
+ ["MESSENGER_TIMEOUT_MS", "délai maximal d'un appel HTTP (défaut : 15000)"],
50
+ ];
51
+ /** Écrit l'entrée `mcp-messenger` dans le `.mcp.json` du répertoire courant. */
52
+ export function runSetupMcp({ cwd, force }) {
53
+ const target = path.join(cwd, ".mcp.json");
54
+ let config = {};
55
+ const exists = fs.existsSync(target);
56
+ if (exists) {
57
+ const raw = fs.readFileSync(target, "utf-8").trim();
58
+ if (raw !== "") {
59
+ try {
60
+ config = JSON.parse(raw);
61
+ }
62
+ catch (error) {
63
+ const message = error instanceof Error ? error.message : String(error);
64
+ console.error(`Erreur : ${target} contient du JSON invalide (${message}).`);
65
+ console.error("Corrigez le fichier ou supprimez-le, puis relancez la commande.");
66
+ return 1;
67
+ }
68
+ }
69
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
70
+ console.error(`Erreur : ${target} ne contient pas un objet JSON.`);
71
+ return 1;
72
+ }
73
+ }
74
+ if (typeof config.mcpServers !== "object" || config.mcpServers === null || Array.isArray(config.mcpServers)) {
75
+ config.mcpServers = {};
76
+ }
77
+ const alreadyPresent = Object.prototype.hasOwnProperty.call(config.mcpServers, SERVER_NAME);
78
+ if (alreadyPresent && !force) {
79
+ console.error(`Le serveur "${SERVER_NAME}" est déjà configuré dans ${target}.`);
80
+ console.error("Relancez avec --force pour écraser cette entrée (les autres serveurs sont préservés).");
81
+ return 1;
82
+ }
83
+ config.mcpServers[SERVER_NAME] = buildServerEntry(cwd);
84
+ fs.writeFileSync(target, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
85
+ const action = alreadyPresent ? "remplacée" : exists ? "ajoutée" : "créée";
86
+ console.log(`Configuration ${SERVER_NAME} ${action} dans ${target}`);
87
+ console.log("");
88
+ console.log('À vérifier dans le champ "env" :');
89
+ for (const [name, description] of REQUIRED_VARS) {
90
+ console.log(` ${name.padEnd(28)} ${description}`);
91
+ }
92
+ console.log("");
93
+ console.log("Variables optionnelles :");
94
+ for (const [name, description] of OPTIONAL_VARS) {
95
+ console.log(` ${name.padEnd(28)} ${description}`);
96
+ }
97
+ console.log("");
98
+ console.log("Le serveur boîte aux lettres doit tourner à part, avec le même jeton :");
99
+ console.log(` MESSENGER_TOKEN=<votre-jeton> npx -y ${PACKAGE_NAME} --server`);
100
+ console.log(` console web : ${DEFAULT_SERVER_URL}/ui/?token=<votre-jeton>`);
101
+ console.log("");
102
+ console.log("Chaque session d'agent reçoit sa propre boîte : demandez son adresse à l'agent");
103
+ console.log("(outil whoami) pour la transmettre à un autre agent, ou pour la reprendre plus tard.");
104
+ return 0;
105
+ }
@@ -0,0 +1,78 @@
1
+ import type { AgentConfig } from "../config.js";
2
+ import type { Agent, FolderInfo, ListMessagesOptions, MailboxStats, Message, MessageSummary, Priority, SendMessageResult } from "../core/types.js";
3
+ /** Échec d'un appel au serveur boîte aux lettres (réseau, authentification ou métier). */
4
+ export declare class MessengerClientError extends Error {
5
+ readonly status?: number | undefined;
6
+ constructor(message: string, status?: number | undefined);
7
+ }
8
+ export interface WaitResult {
9
+ timed_out: boolean;
10
+ message: Message | null;
11
+ }
12
+ /**
13
+ * Client HTTP du serveur boîte aux lettres.
14
+ *
15
+ * L'identité de l'agent voyage dans `X-Agent-Id` ; le serveur ne l'accepte que si le
16
+ * jeton présenté correspond à sa configuration. C'est l'unique point de sortie réseau
17
+ * du mode MCP : les outils n'appellent jamais `fetch` directement.
18
+ */
19
+ export declare class MessengerClient {
20
+ private readonly config;
21
+ /** Boîte courante. Modifiable en cours de session par `attach_to_mailbox`. */
22
+ private currentAgentId;
23
+ /** Boîte allouée à cette session : on peut toujours y revenir après un rattachement. */
24
+ private ownAgentId;
25
+ constructor(config: AgentConfig);
26
+ get agentId(): string;
27
+ /** Adresse fixée par la configuration, s'il y en a une. */
28
+ get pinnedAgentId(): string | null;
29
+ /** `own: true` désigne la boîte de la session, celle sur laquelle on peut revenir. */
30
+ setAgentId(agentId: string, options?: {
31
+ own?: boolean;
32
+ }): void;
33
+ /** Adresse de la boîte propre à cette session. */
34
+ get sessionAgentId(): string | null;
35
+ /** true si la session lit une boîte autre que la sienne. */
36
+ get isAttachedElsewhere(): boolean;
37
+ get serverUrl(): string;
38
+ health(): Promise<{
39
+ status: string;
40
+ version: string;
41
+ agents: number;
42
+ messages: number;
43
+ }>;
44
+ /** Enregistre la boîte courante. `fresh` exige une adresse encore inconnue du serveur. */
45
+ register(options?: {
46
+ fresh?: boolean;
47
+ }): Promise<Agent>;
48
+ /** Renomme la boîte courante, pour la rendre reconnaissable dans l'annuaire. */
49
+ updateProfile(profile: {
50
+ display_name?: string | null;
51
+ description?: string | null;
52
+ }): Promise<Agent>;
53
+ getAgent(agentId: string): Promise<Agent>;
54
+ listAgents(): Promise<Agent[]>;
55
+ stats(): Promise<MailboxStats>;
56
+ sendMessage(input: {
57
+ to: string[];
58
+ cc?: string[];
59
+ subject: string;
60
+ body: string;
61
+ priority?: Priority;
62
+ reply_to?: string | null;
63
+ }): Promise<SendMessageResult>;
64
+ listMessages(options?: ListMessagesOptions): Promise<MessageSummary[]>;
65
+ getMessage(messageId: string, markRead?: boolean): Promise<Message>;
66
+ markMessages(messageIds: string[], state: {
67
+ read?: boolean;
68
+ starred?: boolean;
69
+ }): Promise<number>;
70
+ moveMessages(messageIds: string[], folder: string): Promise<number>;
71
+ deleteMessages(messageIds: string[], permanent?: boolean): Promise<number>;
72
+ getThread(threadId: string): Promise<Message[]>;
73
+ listFolders(): Promise<FolderInfo[]>;
74
+ createFolder(name: string): Promise<FolderInfo>;
75
+ deleteFolder(name: string): Promise<number>;
76
+ waitForMessage(timeoutSeconds: number, folder?: string): Promise<WaitResult>;
77
+ private request;
78
+ }