@imenam/mcp-messenger 1.0.33 → 1.0.35
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 +59 -58
- package/README.md +17 -10
- package/dist/src/core/access-store.d.ts +9 -1
- package/dist/src/core/access-store.js +26 -5
- package/dist/src/mcp/start.js +13 -3
- package/dist/src/server/app.js +41 -10
- package/dist/src/server/ui/app.js +76 -3
- package/dist/src/server/ui/styles.css +37 -0
- package/package.json +61 -61
package/.env.example
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
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
|
-
|
|
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
16
|
# Aucun secret à poser ici : au premier démarrage, le serveur écrit un code d'installation
|
|
17
17
|
# dans son journal ; la console (/ui) le demande pour créer le compte administrateur
|
|
18
18
|
# (nom d'utilisateur + mot de passe). Les clés des agents se créent ensuite dans la
|
|
@@ -20,49 +20,50 @@ MESSENGER_PORT=3600
|
|
|
20
20
|
# MESSENGER_ADMIN_TOKEN ne sont plus lus : le serveur refuse de démarrer s'ils sont posés.
|
|
21
21
|
# Mot de passe perdu : npx @imenam/mcp-messenger --reset-password
|
|
22
22
|
|
|
23
|
-
# Rate limiting (par IP).
|
|
24
|
-
MESSENGER_RATE_LIMIT_MAX=600
|
|
25
|
-
MESSENGER_RATE_LIMIT_WINDOW_MINUTES=15
|
|
26
|
-
|
|
27
|
-
# Purge automatique de la corbeille (0 = désactivée).
|
|
28
|
-
MESSENGER_TRASH_RETENTION_DAYS=30
|
|
29
|
-
|
|
30
|
-
# === Mode MCP (côté agent) : `mcp-messenger` ==============================
|
|
31
|
-
|
|
32
|
-
# Adresse FIXE de l'agent — à laisser vide dans la plupart des cas.
|
|
33
|
-
# Vide : chaque session reçoit sa propre boîte (`box_…`), allouée au démarrage. Deux
|
|
34
|
-
# sessions ouvertes sur le même projet ne partagent donc pas leur courrier. L'agent
|
|
35
|
-
# donne son adresse avec `whoami`, et reprend une boîte existante avec
|
|
36
|
-
# `attach_to_mailbox`.
|
|
37
|
-
# Renseignée : toutes les sessions utilisant cette configuration partagent cette boîte.
|
|
38
|
-
# MESSENGER_AGENT_ID=
|
|
39
|
-
|
|
40
|
-
# Adresse du serveur boîte aux lettres (http ou https, local ou distant).
|
|
41
|
-
MESSENGER_SERVER_URL=http://127.0.0.1:3600
|
|
42
|
-
|
|
23
|
+
# Rate limiting (par IP).
|
|
24
|
+
MESSENGER_RATE_LIMIT_MAX=600
|
|
25
|
+
MESSENGER_RATE_LIMIT_WINDOW_MINUTES=15
|
|
26
|
+
|
|
27
|
+
# Purge automatique de la corbeille (0 = désactivée).
|
|
28
|
+
MESSENGER_TRASH_RETENTION_DAYS=30
|
|
29
|
+
|
|
30
|
+
# === Mode MCP (côté agent) : `mcp-messenger` ==============================
|
|
31
|
+
|
|
32
|
+
# Adresse FIXE de l'agent — à laisser vide dans la plupart des cas.
|
|
33
|
+
# Vide : chaque session reçoit sa propre boîte (`box_…`), allouée au démarrage. Deux
|
|
34
|
+
# sessions ouvertes sur le même projet ne partagent donc pas leur courrier. L'agent
|
|
35
|
+
# donne son adresse avec `whoami`, et reprend une boîte existante avec
|
|
36
|
+
# `attach_to_mailbox`.
|
|
37
|
+
# Renseignée : toutes les sessions utilisant cette configuration partagent cette boîte.
|
|
38
|
+
# MESSENGER_AGENT_ID=
|
|
39
|
+
|
|
40
|
+
# Adresse du serveur boîte aux lettres (http ou https, local ou distant).
|
|
41
|
+
MESSENGER_SERVER_URL=http://127.0.0.1:3600
|
|
42
|
+
|
|
43
43
|
# Clé API créée dans la console (onglet Clés API) — obligatoire.
|
|
44
44
|
MESSENGER_API_KEY=mk_change-me
|
|
45
45
|
|
|
46
|
-
# Métadonnées publiées dans l'annuaire des agents (facultatif).
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
46
|
+
# Métadonnées publiées dans l'annuaire des agents (facultatif). Elles ne servent qu'avec
|
|
47
|
+
# une clé valable pour tous les agents : une clé liée à un agent porte son nom et sa
|
|
48
|
+
# description, et refuse d'autres valeurs.
|
|
49
|
+
# MESSENGER_AGENT_NAME=Planner
|
|
50
|
+
# MESSENGER_AGENT_DESCRIPTION=Agent de planification, répond aux demandes de découpage de tâches.
|
|
51
|
+
|
|
52
|
+
# Délai maximal d'un appel HTTP vers le serveur (ms).
|
|
53
|
+
MESSENGER_TIMEOUT_MS=15000
|
|
54
|
+
|
|
55
|
+
# Reprise de la boîte d'une session interrompue puis relancée.
|
|
56
|
+
# auto (défaut) : la session retrouve sa boîte quand c'est certain (l'hôte a redonné le
|
|
57
|
+
# même identifiant de session) ou sans ambiguïté (une seule boîte du projet n'est plus
|
|
58
|
+
# tenue par personne, et elle a servi).
|
|
59
|
+
# ask : rien n'est repris tout seul ; `whoami` présente les boîtes en attente, l'agent en
|
|
60
|
+
# rattache une avec `attach_to_mailbox`.
|
|
61
|
+
# off : chaque session repart d'une boîte neuve.
|
|
62
|
+
# Une boîte tenue par une session vivante n'est jamais reprise, quel que soit le réglage.
|
|
63
|
+
# MESSENGER_RESUME=auto
|
|
64
|
+
|
|
65
|
+
# Identifiant de la session hôte, celui qui relie une session reprise à celle qu'elle
|
|
66
|
+
# continue. Inutile à renseigner : il est lu dans l'environnement hérité de l'hôte
|
|
67
|
+
# (CLAUDE_CODE_SESSION_ID et équivalents). À poser soi-même pour des sessions lancées par
|
|
68
|
+
# script. Il n'est jamais écrit sur le disque en clair, seule son empreinte l'est.
|
|
69
|
+
# MESSENGER_SESSION_KEY=
|
package/README.md
CHANGED
|
@@ -72,12 +72,15 @@ Trois invariants, non négociables et vérifiés par les tests :
|
|
|
72
72
|
|
|
73
73
|
Une clé se crée dans la console avec un nom, une portée et une durée :
|
|
74
74
|
|
|
75
|
-
- **Un seul agent** : la clé porte
|
|
76
|
-
boîtes ouvertes avec elle prennent ce nom
|
|
77
|
-
|
|
75
|
+
- **Un seul agent** : la clé porte le nom de l'agent (par exemple `Documentor Dev`) et,
|
|
76
|
+
si on le souhaite, sa description. Les boîtes ouvertes avec elle prennent ce nom et
|
|
77
|
+
cette description, et elle n'ouvre que les boîtes de cet agent. `MESSENGER_AGENT_NAME`
|
|
78
|
+
et `MESSENGER_AGENT_DESCRIPTION` restent vides côté client : une autre valeur y fait
|
|
79
|
+
refuser l'enregistrement de la boîte. Modifier la description dans la console la
|
|
80
|
+
reporte sur les boîtes déjà ouvertes avec la clé.
|
|
78
81
|
- **Tous les agents** : la clé ouvre toutes les boîtes ; chaque client donne alors son nom
|
|
79
|
-
dans `MESSENGER_AGENT_NAME`. C'est
|
|
80
|
-
d'une même personne.
|
|
82
|
+
et sa description dans `MESSENGER_AGENT_NAME` et `MESSENGER_AGENT_DESCRIPTION`. C'est
|
|
83
|
+
un mode « cercle de confiance », pour les sessions d'une même personne.
|
|
81
84
|
- **Durée** : 30 jours, 90 jours, 1 an ou sans échéance. La liste signale une clé qui expire
|
|
82
85
|
dans les 7 jours ; on la prolonge depuis son détail.
|
|
83
86
|
|
|
@@ -138,6 +141,10 @@ Au démarrage, le MCP s'enregistre dans l'annuaire du serveur ; si le serveur es
|
|
|
138
141
|
momentanément injoignable, la session MCP démarre quand même et l'enregistrement se fera au
|
|
139
142
|
premier appel réussi.
|
|
140
143
|
|
|
144
|
+
Un refus du serveur, lui, arrête le démarrage avec son motif : clé inconnue, révoquée ou
|
|
145
|
+
expirée, nom ou description d'agent qui contredit une clé liée à un agent. Il ne passerait
|
|
146
|
+
pas en réessayant, c'est la configuration qui est à corriger.
|
|
147
|
+
|
|
141
148
|
### 3. Écrire à un autre agent
|
|
142
149
|
|
|
143
150
|
L'agent A appelle `send_message` avec `to: ["coder"]`, l'agent B voit le message via
|
|
@@ -323,7 +330,7 @@ pas au même rythme :
|
|
|
323
330
|
| | D'où vient-il ? | De quoi s'occupe-t-il ? |
|
|
324
331
|
|---|---|---|
|
|
325
332
|
| Champ | `agent_name`, `agent_description` | `display_name`, `description` |
|
|
326
|
-
| Posé par | la configuration (`MESSENGER_AGENT_NAME`, `MESSENGER_AGENT_DESCRIPTION`) | l'agent lui-même, via `rename_mailbox` |
|
|
333
|
+
| Posé par | la clé API liée à un agent, sinon la configuration du client (`MESSENGER_AGENT_NAME`, `MESSENGER_AGENT_DESCRIPTION`) | l'agent lui-même, via `rename_mailbox` |
|
|
327
334
|
| Change | jamais depuis une session | librement, au fil du travail |
|
|
328
335
|
| Exemple | `Ai Corrector Dev` | `Correction du parseur de copies` |
|
|
329
336
|
|
|
@@ -650,7 +657,7 @@ dans la base. `MESSENGER_TOKEN`, `MESSENGER_AGENT_TOKENS` et `MESSENGER_ADMIN_TO
|
|
|
650
657
|
| `MESSENGER_SERVER_URL` | ✅ | `http://` ou `https://`, local ou distant |
|
|
651
658
|
| `MESSENGER_API_KEY` | ✅ | Clé API créée dans la console, présentée sur chaque appel |
|
|
652
659
|
| `MESSENGER_AGENT_NAME` | — | Nom de l'agent, pour une clé valable pour tous les agents. Une clé liée à un agent donne le sien, et en refuse un autre |
|
|
653
|
-
| `MESSENGER_AGENT_DESCRIPTION` | — | À quoi sert cet agent, pour
|
|
660
|
+
| `MESSENGER_AGENT_DESCRIPTION` | — | À quoi sert cet agent, pour une clé valable pour tous les agents. Une clé liée à un agent porte la sienne, et en refuse une autre |
|
|
654
661
|
| `MESSENGER_TIMEOUT_MS` | `15000` | Délai maximal d'un appel HTTP |
|
|
655
662
|
| `MESSENGER_STATE_DIR` | — | Répertoire des relais vers le veilleur (défaut : `~/.mcp-messenger`) |
|
|
656
663
|
|
|
@@ -699,8 +706,8 @@ cet agent (403 sinon). Les routes `/admin/*` n'acceptent que la session de la co
|
|
|
699
706
|
| `POST` | `/admin/links` | Lie deux boîtes (`mailboxes: [a, b]`, `note?`) — 404 si l'une est inconnue, 409 si elles sont déjà liées |
|
|
700
707
|
| `DELETE` | `/admin/links/:id` | Supprime une liaison |
|
|
701
708
|
| `GET` | `/admin/keys` | Clés API, sans leur secret : début et fin, agent, échéance, dernier usage, état (`active`, `expired`, `revoked`) |
|
|
702
|
-
| `POST` | `/admin/keys` | Crée une clé (`name`, `agent_name` ou `null` pour tous les agents, `lifetime_days` ou `null`) — rend le secret, une seule fois |
|
|
703
|
-
| `GET` `PATCH` | `/admin/keys/:id` | Détail d'une clé et boîtes ouvertes avec elle · renommer (`name`) |
|
|
709
|
+
| `POST` | `/admin/keys` | Crée une clé (`name`, `agent_name` ou `null` pour tous les agents, `agent_description?` pour une clé liée, `lifetime_days` ou `null`) — rend le secret, une seule fois |
|
|
710
|
+
| `GET` `PATCH` | `/admin/keys/:id` | Détail d'une clé et boîtes ouvertes avec elle · renommer (`name`), changer la description de l'agent (`agent_description`, reportée sur ses boîtes) |
|
|
704
711
|
| `POST` | `/admin/keys/:id/extend` | Repousse l'échéance de `days` jours, ou la retire (`days: null`) |
|
|
705
712
|
| `POST` | `/admin/keys/:id/rotate` | Nouvelle clé aux mêmes réglages ; l'ancienne reste acceptée 24 h |
|
|
706
713
|
| `POST` | `/admin/keys/:id/revoke` | Révoque la clé, refusée dès l'appel suivant |
|
|
@@ -796,7 +803,7 @@ test/ # store, config, API HTTP, outils MCP, binaire réel
|
|
|
796
803
|
|
|
797
804
|
### Tests
|
|
798
805
|
|
|
799
|
-
|
|
806
|
+
382 tests couvrant : le compte de la console (création gardée par le code d'installation,
|
|
800
807
|
connexion et limitation des essais, changement de mot de passe qui ferme les autres
|
|
801
808
|
sessions, fermeture d'une session), les clés API (montrées une fois, liées à un agent ou
|
|
802
809
|
non, prolongées, remplacées, révoquées, expirées) et leurs écrans dans la console, les suppressions de boîtes commandées depuis la console — une boîte
|
|
@@ -17,6 +17,8 @@ export interface ApiKey {
|
|
|
17
17
|
suffix: string;
|
|
18
18
|
/** Agent dont la clé ouvre les boîtes ; null = tous les agents. */
|
|
19
19
|
agent_name: string | null;
|
|
20
|
+
/** Description de cet agent, portée par la clé ; seule une clé liée à un agent en a une. */
|
|
21
|
+
agent_description: string | null;
|
|
20
22
|
created_at: string;
|
|
21
23
|
created_by: string | null;
|
|
22
24
|
expires_at: string | null;
|
|
@@ -93,6 +95,7 @@ export declare class AccessStore {
|
|
|
93
95
|
createApiKey(input: {
|
|
94
96
|
name: string;
|
|
95
97
|
agentName: string | null;
|
|
98
|
+
agentDescription?: string | null;
|
|
96
99
|
lifetimeDays: number | null;
|
|
97
100
|
createdBy: string | null;
|
|
98
101
|
secret: string;
|
|
@@ -101,6 +104,11 @@ export declare class AccessStore {
|
|
|
101
104
|
getApiKey(id: string): ApiKey | null;
|
|
102
105
|
private requireKey;
|
|
103
106
|
renameApiKey(id: string, name: string): ApiKey;
|
|
107
|
+
/**
|
|
108
|
+
* Change la description de l'agent portée par une clé liée à un agent, et la reporte sur
|
|
109
|
+
* les boîtes déjà ouvertes avec elle. null l'efface de la clé, pas des boîtes.
|
|
110
|
+
*/
|
|
111
|
+
setApiKeyAgentDescription(id: string, description: string | null): ApiKey;
|
|
104
112
|
/**
|
|
105
113
|
* Repousse l'expiration de `days` jours, comptés depuis l'échéance encore à venir ou,
|
|
106
114
|
* pour une clé déjà expirée, depuis maintenant. `null` retire toute échéance.
|
|
@@ -108,7 +116,7 @@ export declare class AccessStore {
|
|
|
108
116
|
extendApiKey(id: string, days: number | null): ApiKey;
|
|
109
117
|
revokeApiKey(id: string): ApiKey;
|
|
110
118
|
/**
|
|
111
|
-
* Remplace une clé : la nouvelle reprend le nom, l'agent et la durée de validité de
|
|
119
|
+
* Remplace une clé : la nouvelle reprend le nom, l'agent, sa description et la durée de validité de
|
|
112
120
|
* l'ancienne ; l'ancienne reste acceptée `graceMs` encore, au plus jusqu'à son échéance.
|
|
113
121
|
*/
|
|
114
122
|
rotateApiKey(id: string, secret: string, createdBy: string | null, graceMs: number): {
|
|
@@ -4,7 +4,7 @@ import { MessengerError } from "./errors.js";
|
|
|
4
4
|
/** Une date de dernier usage n'est réécrite qu'au-delà de ce délai : pas une écriture par requête. */
|
|
5
5
|
const TOUCH_INTERVAL_MS = 60_000;
|
|
6
6
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
7
|
-
const KEY_COLUMNS = `k.id, k.name, k.prefix, k.suffix, k.agent_name, k.created_at, k.created_by, k.expires_at,
|
|
7
|
+
const KEY_COLUMNS = `k.id, k.name, k.prefix, k.suffix, k.agent_name, k.agent_description, k.created_at, k.created_by, k.expires_at,
|
|
8
8
|
k.lifetime_days, k.revoked_at, k.last_used_at, k.last_used_ip,
|
|
9
9
|
(SELECT COUNT(*) FROM mailbox_keys mk JOIN agents a ON a.id = mk.mailbox WHERE mk.api_key_id = k.id) AS mailboxes`;
|
|
10
10
|
export class AccessStore {
|
|
@@ -41,6 +41,7 @@ export class AccessStore {
|
|
|
41
41
|
prefix TEXT NOT NULL,
|
|
42
42
|
suffix TEXT NOT NULL,
|
|
43
43
|
agent_name TEXT,
|
|
44
|
+
agent_description TEXT,
|
|
44
45
|
created_at TEXT NOT NULL,
|
|
45
46
|
created_by TEXT,
|
|
46
47
|
expires_at TEXT,
|
|
@@ -183,9 +184,9 @@ export class AccessStore {
|
|
|
183
184
|
const id = newId("key");
|
|
184
185
|
const visible = input.secret.startsWith(API_KEY_PREFIX) ? API_KEY_PREFIX.length : 0;
|
|
185
186
|
this.db
|
|
186
|
-
.prepare(`INSERT INTO api_keys (id, name, secret_hash, prefix, suffix, agent_name, created_at, created_by, expires_at, lifetime_days)
|
|
187
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
188
|
-
.run(id, input.name, digestSecret(input.secret), input.secret.slice(0, visible + 4), input.secret.slice(-4), input.agentName, this.iso(), input.createdBy, input.lifetimeDays === null ? null : this.iso(input.lifetimeDays * DAY_MS), input.lifetimeDays);
|
|
187
|
+
.prepare(`INSERT INTO api_keys (id, name, secret_hash, prefix, suffix, agent_name, agent_description, created_at, created_by, expires_at, lifetime_days)
|
|
188
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
189
|
+
.run(id, input.name, digestSecret(input.secret), input.secret.slice(0, visible + 4), input.secret.slice(-4), input.agentName, input.agentDescription ?? null, this.iso(), input.createdBy, input.lifetimeDays === null ? null : this.iso(input.lifetimeDays * DAY_MS), input.lifetimeDays);
|
|
189
190
|
return this.getApiKey(id);
|
|
190
191
|
}
|
|
191
192
|
listApiKeys() {
|
|
@@ -206,6 +207,25 @@ export class AccessStore {
|
|
|
206
207
|
this.db.prepare("UPDATE api_keys SET name = ? WHERE id = ?").run(name, id);
|
|
207
208
|
return this.getApiKey(id);
|
|
208
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Change la description de l'agent portée par une clé liée à un agent, et la reporte sur
|
|
212
|
+
* les boîtes déjà ouvertes avec elle. null l'efface de la clé, pas des boîtes.
|
|
213
|
+
*/
|
|
214
|
+
setApiKeyAgentDescription(id, description) {
|
|
215
|
+
const key = this.requireKey(id);
|
|
216
|
+
if (!key.agent_name) {
|
|
217
|
+
throw new MessengerError("only a key bound to an agent carries an agent description", 400);
|
|
218
|
+
}
|
|
219
|
+
this.db.transaction(() => {
|
|
220
|
+
this.db.prepare("UPDATE api_keys SET agent_description = ? WHERE id = ?").run(description, id);
|
|
221
|
+
if (description !== null) {
|
|
222
|
+
this.db
|
|
223
|
+
.prepare("UPDATE agents SET agent_description = ? WHERE id IN (SELECT mailbox FROM mailbox_keys WHERE api_key_id = ?)")
|
|
224
|
+
.run(description, id);
|
|
225
|
+
}
|
|
226
|
+
})();
|
|
227
|
+
return this.getApiKey(id);
|
|
228
|
+
}
|
|
209
229
|
/**
|
|
210
230
|
* Repousse l'expiration de `days` jours, comptés depuis l'échéance encore à venir ou,
|
|
211
231
|
* pour une clé déjà expirée, depuis maintenant. `null` retire toute échéance.
|
|
@@ -227,7 +247,7 @@ export class AccessStore {
|
|
|
227
247
|
return this.getApiKey(id);
|
|
228
248
|
}
|
|
229
249
|
/**
|
|
230
|
-
* Remplace une clé : la nouvelle reprend le nom, l'agent et la durée de validité de
|
|
250
|
+
* Remplace une clé : la nouvelle reprend le nom, l'agent, sa description et la durée de validité de
|
|
231
251
|
* l'ancienne ; l'ancienne reste acceptée `graceMs` encore, au plus jusqu'à son échéance.
|
|
232
252
|
*/
|
|
233
253
|
rotateApiKey(id, secret, createdBy, graceMs) {
|
|
@@ -238,6 +258,7 @@ export class AccessStore {
|
|
|
238
258
|
const key = this.createApiKey({
|
|
239
259
|
name: old.name,
|
|
240
260
|
agentName: old.agent_name,
|
|
261
|
+
agentDescription: old.agent_description,
|
|
241
262
|
lifetimeDays: old.lifetime_days,
|
|
242
263
|
createdBy,
|
|
243
264
|
secret,
|
package/dist/src/mcp/start.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
-
import { loadAgentConfig } from "../config.js";
|
|
4
|
+
import { ConfigError, loadAgentConfig } from "../config.js";
|
|
5
5
|
import { MessengerClient, MessengerClientError } from "../client/messenger-client.js";
|
|
6
6
|
import { removeOwnHandoffs } from "../cli/watch-handoff.js";
|
|
7
7
|
import { newId } from "../core/ids.js";
|
|
@@ -190,13 +190,23 @@ export async function runMcpMode() {
|
|
|
190
190
|
const config = loadAgentConfig();
|
|
191
191
|
const client = new MessengerClient(config);
|
|
192
192
|
// Un serveur momentanément injoignable ne doit pas empêcher la session MCP de démarrer :
|
|
193
|
-
// l'agent verra l'erreur au premier outil appelé, et pourra réessayer.
|
|
193
|
+
// l'agent verra l'erreur au premier outil appelé, et pourra réessayer. Un refus du
|
|
194
|
+
// serveur, lui, ne passera pas en réessayant : la clé ou la configuration est à corriger,
|
|
195
|
+
// et le démarrage s'arrête en le disant.
|
|
194
196
|
try {
|
|
195
197
|
const identity = await resolveIdentity(client, config);
|
|
196
198
|
logger.info(`mailbox \`${identity.mailbox}\` ready on ${config.serverUrl}${describeIdentity(identity, config)}`);
|
|
197
199
|
}
|
|
198
200
|
catch (error) {
|
|
199
|
-
|
|
201
|
+
if (error instanceof MessengerClientError && (error.status === 401 || error.status === 403)) {
|
|
202
|
+
throw new ConfigError(`the messenger server at ${config.serverUrl} refused this configuration: ${error.message}`);
|
|
203
|
+
}
|
|
204
|
+
if (error instanceof MessengerClientError && error.status !== undefined) {
|
|
205
|
+
logger.warn(`mailbox registration failed on ${config.serverUrl} (HTTP ${error.status}): ${error.message}`);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
logger.warn(`could not reach ${config.serverUrl}: ${error instanceof Error ? error.message : String(error)}`);
|
|
209
|
+
}
|
|
200
210
|
}
|
|
201
211
|
// Tant que ce battement continue, la boîte de cette session n'est reprise par personne.
|
|
202
212
|
// `unref` : c'est une trace, pas une raison de garder le processus en vie.
|
package/dist/src/server/app.js
CHANGED
|
@@ -119,13 +119,32 @@ const passwordChangeSchema = z.object({
|
|
|
119
119
|
});
|
|
120
120
|
/** Durée de validité d'une clé, en jours ; null = sans échéance. */
|
|
121
121
|
const lifetimeSchema = z.number().int().min(1).max(3650).nullable();
|
|
122
|
-
|
|
122
|
+
/** Description de l'agent portée par une clé : une chaîne vide l'efface. */
|
|
123
|
+
const agentDescriptionSchema = z
|
|
124
|
+
.string()
|
|
125
|
+
.trim()
|
|
126
|
+
.max(2000)
|
|
127
|
+
.nullable()
|
|
128
|
+
.transform((value) => value || null);
|
|
129
|
+
const keyCreateSchema = z
|
|
130
|
+
.object({
|
|
123
131
|
name: z.string().trim().min(1).max(200),
|
|
124
132
|
/** Agent dont la clé ouvre les boîtes ; null = tous les agents. */
|
|
125
133
|
agent_name: z.string().trim().min(1).max(200).nullable(),
|
|
134
|
+
agent_description: agentDescriptionSchema.optional(),
|
|
126
135
|
lifetime_days: lifetimeSchema,
|
|
136
|
+
})
|
|
137
|
+
.refine((input) => input.agent_name !== null || !input.agent_description, {
|
|
138
|
+
message: "only a key bound to an agent carries an agent description",
|
|
139
|
+
});
|
|
140
|
+
const keyPatchSchema = z
|
|
141
|
+
.object({
|
|
142
|
+
name: z.string().trim().min(1).max(200).optional(),
|
|
143
|
+
agent_description: agentDescriptionSchema.optional(),
|
|
144
|
+
})
|
|
145
|
+
.refine((input) => input.name !== undefined || input.agent_description !== undefined, {
|
|
146
|
+
message: "provide `name` and/or `agent_description`",
|
|
127
147
|
});
|
|
128
|
-
const keyRenameSchema = z.object({ name: z.string().trim().min(1).max(200) });
|
|
129
148
|
const keyExtendSchema = z.object({ days: lifetimeSchema });
|
|
130
149
|
const adminThreadsSchema = z.object({
|
|
131
150
|
search: z.string().trim().min(1).optional(),
|
|
@@ -489,6 +508,7 @@ export function createApp({ store, config, setup = new SetupGate(store.access) }
|
|
|
489
508
|
const key = access.createApiKey({
|
|
490
509
|
name: input.name,
|
|
491
510
|
agentName: input.agent_name,
|
|
511
|
+
agentDescription: input.agent_description ?? null,
|
|
492
512
|
lifetimeDays: input.lifetime_days,
|
|
493
513
|
createdBy: req.consoleUser.username,
|
|
494
514
|
secret,
|
|
@@ -504,8 +524,12 @@ export function createApp({ store, config, setup = new SetupGate(store.access) }
|
|
|
504
524
|
res.json({ key, mailboxes: access.mailboxesForKey(id) });
|
|
505
525
|
});
|
|
506
526
|
admin.patch("/keys/:id", (req, res) => {
|
|
507
|
-
const input =
|
|
508
|
-
|
|
527
|
+
const input = keyPatchSchema.parse(req.body ?? {});
|
|
528
|
+
const id = String(req.params.id);
|
|
529
|
+
let key = input.name !== undefined ? access.renameApiKey(id, input.name) : null;
|
|
530
|
+
if (input.agent_description !== undefined)
|
|
531
|
+
key = access.setApiKeyAgentDescription(id, input.agent_description);
|
|
532
|
+
res.json({ key });
|
|
509
533
|
});
|
|
510
534
|
admin.post("/keys/:id/extend", (req, res) => {
|
|
511
535
|
const input = keyExtendSchema.parse(req.body ?? {});
|
|
@@ -550,17 +574,24 @@ export function createApp({ store, config, setup = new SetupGate(store.access) }
|
|
|
550
574
|
if (input.fresh && store.getAgent(req.agentId)) {
|
|
551
575
|
throw new MessengerError(`mailbox already exists: ${req.agentId}`, 409);
|
|
552
576
|
}
|
|
553
|
-
// Une clé liée à un agent donne son nom à la boîte.
|
|
554
|
-
// configuration du client est une erreur de réglage, signalée plutôt que
|
|
577
|
+
// Une clé liée à un agent donne son nom et sa description à la boîte. Une autre valeur
|
|
578
|
+
// venue de la configuration du client est une erreur de réglage, signalée plutôt que
|
|
579
|
+
// corrigée.
|
|
555
580
|
const key = req.apiKey;
|
|
556
|
-
if (key.agent_name
|
|
557
|
-
|
|
558
|
-
`
|
|
581
|
+
if (key.agent_name) {
|
|
582
|
+
if (input.agent_name && input.agent_name !== key.agent_name) {
|
|
583
|
+
throw new MessengerError(`this API key is bound to agent "${key.agent_name}": remove MESSENGER_AGENT_NAME from the client ` +
|
|
584
|
+
`configuration, or set it to "${key.agent_name}"`, 403);
|
|
585
|
+
}
|
|
586
|
+
if (input.agent_description && input.agent_description !== key.agent_description) {
|
|
587
|
+
throw new MessengerError(`this API key carries the description of agent "${key.agent_name}": remove MESSENGER_AGENT_DESCRIPTION ` +
|
|
588
|
+
"from the client configuration, and edit the description in the web console", 403);
|
|
589
|
+
}
|
|
559
590
|
}
|
|
560
591
|
const agent = store.ensureAgent(req.agentId, {
|
|
561
592
|
registered: true,
|
|
562
593
|
agent_name: key.agent_name ?? input.agent_name ?? null,
|
|
563
|
-
agent_description: input.agent_description ?? null,
|
|
594
|
+
agent_description: key.agent_name ? key.agent_description : (input.agent_description ?? null),
|
|
564
595
|
// Un client antérieur à la séparation agent / boîte n'envoie que ces deux-là.
|
|
565
596
|
display_name: input.display_name ?? null,
|
|
566
597
|
description: input.description ?? null,
|
|
@@ -2695,6 +2695,15 @@ function formField(label, control, hint) {
|
|
|
2695
2695
|
return wrapper;
|
|
2696
2696
|
}
|
|
2697
2697
|
|
|
2698
|
+
function textArea(value, placeholder) {
|
|
2699
|
+
const area = document.createElement("textarea");
|
|
2700
|
+
area.value = value || "";
|
|
2701
|
+
area.rows = 2;
|
|
2702
|
+
area.maxLength = 2000;
|
|
2703
|
+
if (placeholder) area.placeholder = placeholder;
|
|
2704
|
+
return area;
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2698
2707
|
function textInput(value, options = {}) {
|
|
2699
2708
|
const input = document.createElement("input");
|
|
2700
2709
|
input.type = options.type || "text";
|
|
@@ -2978,11 +2987,15 @@ function choiceChips(options, selected, onPick) {
|
|
|
2978
2987
|
|
|
2979
2988
|
function openKeyCreate() {
|
|
2980
2989
|
keysView.detailId = null;
|
|
2981
|
-
const form = { name: "", scope: "one", agent: "", lifetime: KEY_LIFETIMES[1] };
|
|
2990
|
+
const form = { name: "", scope: "one", agent: "", description: "", lifetime: KEY_LIFETIMES[1] };
|
|
2982
2991
|
const names = knownAgentNames();
|
|
2983
2992
|
|
|
2984
2993
|
const nameInput = textInput("", { placeholder: "Poste, machine ou usage", autocomplete: "off" });
|
|
2985
2994
|
const agentInput = textInput("", { placeholder: "Nom de l'agent", autocomplete: "off", list: "key-agent-names" });
|
|
2995
|
+
const descriptionInput = textArea("", "À quoi sert cet agent, pour ses correspondants");
|
|
2996
|
+
descriptionInput.addEventListener("input", () => {
|
|
2997
|
+
form.description = descriptionInput.value;
|
|
2998
|
+
});
|
|
2986
2999
|
const datalist = node("datalist");
|
|
2987
3000
|
datalist.id = "key-agent-names";
|
|
2988
3001
|
for (const name of names) {
|
|
@@ -3027,11 +3040,14 @@ function openKeyCreate() {
|
|
|
3027
3040
|
box.append(agentMark, agentInput, newTag);
|
|
3028
3041
|
updateAgentMark();
|
|
3029
3042
|
agentField.appendChild(box);
|
|
3043
|
+
const description = node("span", "field-input field-input--area");
|
|
3044
|
+
description.appendChild(descriptionInput);
|
|
3045
|
+
agentField.appendChild(description);
|
|
3030
3046
|
agentField.appendChild(
|
|
3031
3047
|
node(
|
|
3032
3048
|
"span",
|
|
3033
3049
|
"field-hint",
|
|
3034
|
-
"
|
|
3050
|
+
"Le nom et la description s'appliquent à toutes les sessions ouvertes avec cette clé : rien à poser côté client."
|
|
3035
3051
|
)
|
|
3036
3052
|
);
|
|
3037
3053
|
} else {
|
|
@@ -3071,7 +3087,7 @@ function openKeyCreate() {
|
|
|
3071
3087
|
datalist
|
|
3072
3088
|
);
|
|
3073
3089
|
// Le volet se reconstruit à chaque choix : la saisie en cours garde le curseur.
|
|
3074
|
-
if (focused === agentInput || focused === nameInput) focused.focus();
|
|
3090
|
+
if (focused === agentInput || focused === nameInput || focused === descriptionInput) focused.focus();
|
|
3075
3091
|
create.disabled = !form.name.trim() || (form.scope === "one" && !form.agent.trim());
|
|
3076
3092
|
}
|
|
3077
3093
|
|
|
@@ -3097,6 +3113,7 @@ function openKeyCreate() {
|
|
|
3097
3113
|
const created = await post("/admin/keys", {
|
|
3098
3114
|
name: form.name.trim(),
|
|
3099
3115
|
agent_name: form.scope === "one" ? form.agent.trim() : null,
|
|
3116
|
+
...(form.scope === "one" && form.description.trim() ? { agent_description: form.description.trim() } : {}),
|
|
3100
3117
|
lifetime_days: form.lifetime.days,
|
|
3101
3118
|
});
|
|
3102
3119
|
renderKeyReveal(created.key, created.secret, null);
|
|
@@ -3247,6 +3264,7 @@ async function openKeyDetail(id) {
|
|
|
3247
3264
|
|
|
3248
3265
|
function renderKeyDetail({ key, mailboxes }) {
|
|
3249
3266
|
let renaming = false;
|
|
3267
|
+
let describing = false;
|
|
3250
3268
|
let extendOpen = false;
|
|
3251
3269
|
const error = node("p", "form-banner");
|
|
3252
3270
|
error.hidden = true;
|
|
@@ -3368,6 +3386,7 @@ function renderKeyDetail({ key, mailboxes }) {
|
|
|
3368
3386
|
list.appendChild(cell);
|
|
3369
3387
|
};
|
|
3370
3388
|
add("Agent autorisé", keyAgent(key));
|
|
3389
|
+
if (key.agent_name) add("Description", descriptionCell());
|
|
3371
3390
|
add("Créée", `${formatDay(key.created_at)}${key.created_by ? ` par ${key.created_by}` : ""}`);
|
|
3372
3391
|
add(
|
|
3373
3392
|
"Dernière utilisation",
|
|
@@ -3379,6 +3398,60 @@ function renderKeyDetail({ key, mailboxes }) {
|
|
|
3379
3398
|
return list;
|
|
3380
3399
|
}
|
|
3381
3400
|
|
|
3401
|
+
/** Description de l'agent, modifiable sur place : Entrée enregistre, Échap annule. */
|
|
3402
|
+
function descriptionCell() {
|
|
3403
|
+
if (!describing) {
|
|
3404
|
+
const value = node("span", key.agent_description ? "props-value" : "props-value key-muted", key.agent_description || "aucune");
|
|
3405
|
+
const edit = node("button", "btn btn--icon btn--ghost props-edit");
|
|
3406
|
+
edit.type = "button";
|
|
3407
|
+
edit.title = "Modifier la description";
|
|
3408
|
+
edit.setAttribute("aria-label", "Modifier la description de l'agent");
|
|
3409
|
+
edit.appendChild(icon("pencil"));
|
|
3410
|
+
edit.addEventListener("click", () => {
|
|
3411
|
+
describing = true;
|
|
3412
|
+
render();
|
|
3413
|
+
});
|
|
3414
|
+
return [value, edit];
|
|
3415
|
+
}
|
|
3416
|
+
const area = textArea(key.agent_description, "À quoi sert cet agent, pour ses correspondants");
|
|
3417
|
+
area.className = "inline-area";
|
|
3418
|
+
let saved = false;
|
|
3419
|
+
const save = async () => {
|
|
3420
|
+
if (saved) return;
|
|
3421
|
+
saved = true;
|
|
3422
|
+
const description = area.value.trim();
|
|
3423
|
+
if (description === (key.agent_description || "")) {
|
|
3424
|
+
describing = false;
|
|
3425
|
+
render();
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
3428
|
+
try {
|
|
3429
|
+
await patch(`/admin/keys/${encodeURIComponent(key.id)}`, { agent_description: description || null });
|
|
3430
|
+
await reload();
|
|
3431
|
+
} catch (failure) {
|
|
3432
|
+
saved = false;
|
|
3433
|
+
fail(failure);
|
|
3434
|
+
}
|
|
3435
|
+
};
|
|
3436
|
+
area.addEventListener("keydown", (event) => {
|
|
3437
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
3438
|
+
event.preventDefault();
|
|
3439
|
+
save();
|
|
3440
|
+
}
|
|
3441
|
+
if (event.key === "Escape") {
|
|
3442
|
+
// Échap referme la saisie, pas le volet.
|
|
3443
|
+
event.preventDefault();
|
|
3444
|
+
event.stopPropagation();
|
|
3445
|
+
saved = true;
|
|
3446
|
+
describing = false;
|
|
3447
|
+
render();
|
|
3448
|
+
}
|
|
3449
|
+
});
|
|
3450
|
+
area.addEventListener("blur", save);
|
|
3451
|
+
setTimeout(() => area.focus(), 0);
|
|
3452
|
+
return [area];
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3382
3455
|
function boxes() {
|
|
3383
3456
|
const section = node("section", "drawer-section");
|
|
3384
3457
|
const head = node("div", "drawer-section-head");
|
|
@@ -2780,6 +2780,43 @@ body {
|
|
|
2780
2780
|
gap: 10px;
|
|
2781
2781
|
}
|
|
2782
2782
|
|
|
2783
|
+
.field-input--area {
|
|
2784
|
+
height: auto;
|
|
2785
|
+
margin-top: 12px;
|
|
2786
|
+
padding: 8px 12px;
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
.field-input textarea,
|
|
2790
|
+
.inline-area {
|
|
2791
|
+
flex: 1;
|
|
2792
|
+
min-width: 0;
|
|
2793
|
+
border: 0;
|
|
2794
|
+
background: none;
|
|
2795
|
+
font: inherit;
|
|
2796
|
+
font-size: 13px;
|
|
2797
|
+
line-height: 1.4;
|
|
2798
|
+
color: var(--text);
|
|
2799
|
+
resize: vertical;
|
|
2800
|
+
outline: none;
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
.inline-area {
|
|
2804
|
+
width: 100%;
|
|
2805
|
+
padding: 6px 10px;
|
|
2806
|
+
border: 1px solid var(--accent);
|
|
2807
|
+
border-radius: var(--radius);
|
|
2808
|
+
background: var(--panel);
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
.props-value {
|
|
2812
|
+
line-height: 1.4;
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
.props-edit {
|
|
2816
|
+
flex: none;
|
|
2817
|
+
padding: 4px;
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2783
2820
|
.field-tag {
|
|
2784
2821
|
font-size: 12px;
|
|
2785
2822
|
color: var(--accent);
|
package/package.json
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@imenam/mcp-messenger",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"main": "dist/src/index.js",
|
|
5
|
-
"scripts": {
|
|
6
|
-
"test": "npm run build && vitest run",
|
|
7
|
-
"build": "tsc && node scripts/copy-assets.js",
|
|
8
|
-
"start": "node dist/src/index.js",
|
|
9
|
-
"serve": "node dist/src/index.js --server",
|
|
10
|
-
"prepublishOnly": "npm run build",
|
|
11
|
-
"release": "npm version patch && npm publish && git push --follow-tags",
|
|
12
|
-
"typecheck": "tsc --noEmit",
|
|
13
|
-
"test:watch": "vitest",
|
|
14
|
-
"test:coverage": "vitest run --coverage"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"mcp",
|
|
18
|
-
"model-context-protocol",
|
|
19
|
-
"agent",
|
|
20
|
-
"messaging",
|
|
21
|
-
"mailbox",
|
|
22
|
-
"agent-to-agent"
|
|
23
|
-
],
|
|
24
|
-
"author": "Matthieu Pesnot-Pin <matthieupin@gmail.com>",
|
|
25
|
-
"license": "ISC",
|
|
26
|
-
"description": "Cross-platform agent-to-agent messaging: an MCP mailbox client plus its HTTP mailbox server.",
|
|
27
|
-
"type": "module",
|
|
28
|
-
"bin": {
|
|
29
|
-
"mcp-messenger": "dist/src/index.js"
|
|
30
|
-
},
|
|
31
|
-
"engines": {
|
|
32
|
-
"node": ">=20"
|
|
33
|
-
},
|
|
34
|
-
"publishConfig": {
|
|
35
|
-
"access": "public"
|
|
36
|
-
},
|
|
37
|
-
"dependencies": {
|
|
38
|
-
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
39
|
-
"better-sqlite3": "^12.11.1",
|
|
40
|
-
"dotenv": "^17.4.2",
|
|
41
|
-
"express": "^5.2.1",
|
|
42
|
-
"express-rate-limit": "^8.7.0",
|
|
43
|
-
"helmet": "^8.3.0",
|
|
44
|
-
"zod": "^4.5.4"
|
|
45
|
-
},
|
|
46
|
-
"devDependencies": {
|
|
47
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
48
|
-
"@types/express": "^5.0.6",
|
|
49
|
-
"@types/node": "^22.20.1",
|
|
50
|
-
"@vitest/coverage-v8": "^
|
|
51
|
-
"jsdom": "^29.1.1",
|
|
52
|
-
"typescript": "^5.9.3",
|
|
53
|
-
"vitest": "^
|
|
54
|
-
},
|
|
55
|
-
"files": [
|
|
56
|
-
"dist/src",
|
|
57
|
-
"README.md",
|
|
58
|
-
".env.example",
|
|
59
|
-
"LICENSE"
|
|
60
|
-
]
|
|
61
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@imenam/mcp-messenger",
|
|
3
|
+
"version": "1.0.35",
|
|
4
|
+
"main": "dist/src/index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "npm run build && vitest run",
|
|
7
|
+
"build": "tsc && node scripts/copy-assets.js",
|
|
8
|
+
"start": "node dist/src/index.js",
|
|
9
|
+
"serve": "node dist/src/index.js --server",
|
|
10
|
+
"prepublishOnly": "npm run build",
|
|
11
|
+
"release": "npm version patch && npm publish && git push --follow-tags",
|
|
12
|
+
"typecheck": "tsc --noEmit",
|
|
13
|
+
"test:watch": "vitest",
|
|
14
|
+
"test:coverage": "vitest run --coverage"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"agent",
|
|
20
|
+
"messaging",
|
|
21
|
+
"mailbox",
|
|
22
|
+
"agent-to-agent"
|
|
23
|
+
],
|
|
24
|
+
"author": "Matthieu Pesnot-Pin <matthieupin@gmail.com>",
|
|
25
|
+
"license": "ISC",
|
|
26
|
+
"description": "Cross-platform agent-to-agent messaging: an MCP mailbox client plus its HTTP mailbox server.",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"bin": {
|
|
29
|
+
"mcp-messenger": "dist/src/index.js"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
39
|
+
"better-sqlite3": "^12.11.1",
|
|
40
|
+
"dotenv": "^17.4.2",
|
|
41
|
+
"express": "^5.2.1",
|
|
42
|
+
"express-rate-limit": "^8.7.0",
|
|
43
|
+
"helmet": "^8.3.0",
|
|
44
|
+
"zod": "^4.5.4"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
48
|
+
"@types/express": "^5.0.6",
|
|
49
|
+
"@types/node": "^22.20.1",
|
|
50
|
+
"@vitest/coverage-v8": "^5.0.5",
|
|
51
|
+
"jsdom": "^29.1.1",
|
|
52
|
+
"typescript": "^5.9.3",
|
|
53
|
+
"vitest": "^5.0.5"
|
|
54
|
+
},
|
|
55
|
+
"files": [
|
|
56
|
+
"dist/src",
|
|
57
|
+
"README.md",
|
|
58
|
+
".env.example",
|
|
59
|
+
"LICENSE"
|
|
60
|
+
]
|
|
61
|
+
}
|