@imenam/mcp-http-gateway 2.0.10 → 2.0.12

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.
Files changed (3) hide show
  1. package/README.md +504 -446
  2. package/mcp-mode.js +648 -408
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,446 +1,504 @@
1
- # MCP HTTP Gateway
2
-
3
- Serveur HTTP qui expose n'importe quel serveur MCP (Model Context Protocol) en tant qu'API REST, sans modifier les MCPs eux-mêmes. Conçu pour permettre à des agents ou applications ne supportant pas nativement le protocole MCP de communiquer avec l'écosystème MCP via de simples requêtes HTTP.
4
-
5
- ## Fonctionnement
6
-
7
- Chaque MCP tourne en tant que processus enfant (transport stdio). Le gateway s'y connecte via le SDK MCP officiel et expose ses outils sur des routes HTTP configurables.
8
-
9
- ```
10
- Agent / App externe
11
- │
12
- │ HTTP
13
- ▼
14
- MCP HTTP Gateway (port 3500)
15
- │
16
- │ stdio (SDK MCP)
17
- ├──▶ mcp-documentor (/documentor)
18
- ├──▶ mcp-raindrop (/raindrop)
19
- └──▶ ...
20
- ```
21
-
22
- ## Installation
23
-
24
- **En tant que CLI globale :**
25
-
26
- ```bash
27
- npm install -g @imenam/mcp-http-gateway
28
- ```
29
-
30
- **Via npx (sans installation) :**
31
-
32
- ```bash
33
- npx @imenam/mcp-http-gateway
34
- ```
35
-
36
- **En dépendance locale d'un projet :**
37
-
38
- ```bash
39
- npm install @imenam/mcp-http-gateway
40
- ```
41
-
42
- ## Configuration
43
-
44
- ### `.env`
45
-
46
- ```env
47
- GATEWAY_PORT=3500
48
- GATEWAY_CONFIG_DIR=./config
49
- ```
50
-
51
- | Variable | Défaut | Description |
52
- |---|---|---|
53
- | `GATEWAY_PORT` | `3500` | Port HTTP du gateway |
54
- | `GATEWAY_CONFIG_DIR` | `./config` (relatif au cwd) | Dossier contenant `mcp-servers.json` |
55
- | `GATEWAY_BODY_LIMIT` | `32mb` | Taille maximale du corps d'une requête. Le défaut d'express (100 ko) ne suffit pas dès qu'un appel d'outil porte le contenu d'un fichier en base64 ; au-delà de cette limite, la réponse est un `413`. |
56
-
57
- ### `mcp-servers.json`
58
-
59
- À créer dans le dossier de configuration (`./config` par défaut, ou le chemin pointé par `GATEWAY_CONFIG_DIR`). Un fichier `config/mcp-servers.example.json` est fourni comme point de départ.
60
-
61
- Même format que la configuration `mcpServers` de Cursor. Ajouter `MCP_SERVER_ROUTE` dans `env` pour définir la route HTTP du MCP, et `description` pour la documentation.
62
-
63
- ```json
64
- {
65
- "mcpServers": {
66
- "mon-mcp": {
67
- "command": "node",
68
- "args": ["/chemin/vers/dist/index.js"],
69
- "description": "Description courte du MCP",
70
- "env": {
71
- "MA_VARIABLE": "valeur",
72
- "MCP_SERVER_ROUTE": "/ma-route"
73
- }
74
- }
75
- }
76
- }
77
- ```
78
-
79
- > Un MCP sans `MCP_SERVER_ROUTE` est ignoré au démarrage.
80
-
81
- ## Démarrage
82
-
83
- ```bash
84
- npm start
85
- ```
86
-
87
- Le gateway connecte chaque MCP configuré et démarre le serveur HTTP. Les MCPs en échec au démarrage sont ignorés sans bloquer les autres.
88
-
89
- ## Routes
90
-
91
- ### `GET /mcps`
92
-
93
- Retourne la liste des MCPs connectés avec leur route, description et instructions d'utilisation (format Markdown).
94
-
95
- ```bash
96
- curl http://localhost:3500/mcps
97
- ```
98
-
99
- ---
100
-
101
- ### `GET /{route}/tools`
102
-
103
- Liste les outils disponibles d'un MCP avec leur nom, description et schéma d'arguments.
104
-
105
- ```bash
106
- curl http://localhost:3500/documentor/tools
107
- ```
108
-
109
- ---
110
-
111
- ### `POST /{route}/tools/:toolName`
112
-
113
- Appelle un outil du MCP. Les arguments sont passés dans le body JSON.
114
-
115
- ```bash
116
- curl -X POST http://localhost:3500/documentor/tools/list_projects -H "Content-Type: application/json" -d '{}'
117
- ```
118
-
119
- ```bash
120
- curl -X POST http://localhost:3500/documentor/tools/get_doc -H "Content-Type: application/json" -d '{"id":"mon-doc-id"}'
121
- ```
122
-
123
- #### Réponse
124
-
125
- La réponse est le format brut du protocole MCP — un tableau `content` de blocs typés :
126
-
127
- ```json
128
- {
129
- "content": [
130
- { "type": "text", "text": "Résultat de l'outil..." }
131
- ]
132
- }
133
- ```
134
-
135
- Pour extraire le texte directement avec `jq` :
136
-
137
- ```bash
138
- curl -s -X POST http://localhost:3500/documentor/tools/list_projects -H "Content-Type: application/json" -d '{}' | jq -r '.content[0].text'
139
- ```
140
-
141
- ## Mode MCP
142
-
143
- Le gateway peut également démarrer en tant que **serveur MCP** (transport stdio). Dans ce mode, il se connecte à une instance du gateway HTTP et expose ses outils comme des outils MCP natifs.
144
-
145
- ### Cas d'usage : agent dans un conteneur Docker
146
-
147
- Le gateway HTTP tourne **hors du conteneur**, avec accès aux MCPs et leurs clés sensibles. L'agent dans le conteneur reçoit uniquement le gateway en mode MCP — aucune clé ne lui est injectée.
148
-
149
- ```
150
- [Agent dans conteneur]
151
- │ stdio (MCP)
152
- ▼
153
- [mcp-http-gateway --mcp] ←── GATEWAY_URL
154
- │ HTTP
155
- ▼
156
- [mcp-http-gateway hors conteneur]
157
- │ stdio
158
- ▼
159
- [MCPs réels (clés sensibles)]
160
- ```
161
-
162
- ### Démarrage
163
-
164
- ```bash
165
- GATEWAY_URL=http://host.docker.internal:3500 npx @imenam/mcp-http-gateway --mcp
166
- ```
167
-
168
- ### Configuration dans le conteneur
169
-
170
- ```json
171
- {
172
- "mcpServers": {
173
- "gateway": {
174
- "command": "npx",
175
- "args": ["@imenam/mcp-http-gateway", "--mcp"],
176
- "env": {
177
- "GATEWAY_URL": "http://host.docker.internal:3500"
178
- }
179
- }
180
- }
181
- }
182
- ```
183
-
184
- > `host.docker.internal` est l'adresse de l'hôte depuis un conteneur Docker.
185
-
186
- ### Variables d'environnement du mode `--mcp`
187
-
188
- | Variable | Requis | Description |
189
- |---|---|---|
190
- | `GATEWAY_URL` | oui | Base HTTP du gateway à relayer, sans barre finale. Le relais compose lui-même ses routes REST (`/startup-exposition`, `/mcp-definitions`, `/{route}/tools`, `/{route}/tools/{outil}`) : **ne pas** y ajouter `/mcp`. |
191
- | `GATEWAY_TOKEN` | non | Token porteur. S'il est défini, **tous** les appels sortants du relais portent `Authorization: Bearer <token>`. Absent, aucun en-tête n'est ajouté — le comportement est exactement celui des versions antérieures. |
192
- | `GATEWAY_INLINE_ROOTS` | non | Dossiers depuis lesquels le relais peut lire un fichier et en transmettre le contenu (cf. [Fichiers locaux](#fichiers-locaux--lagent-nomme-le-relais-lit)). **Non renseignée, la fonction est désactivée** et un chemin part tel quel. Séparateur : `;` sous Windows, `:` ailleurs. |
193
- | `GATEWAY_INLINE_EXTENSIONS` | non | Extensions autorisées, séparées par des virgules. Défaut : `.xlsx,.xls,.ods,.csv`. `*` autorise toutes les extensions. |
194
- | `GATEWAY_INLINE_MAX_BYTES` | non | Taille maximale d'un fichier transmis, en octets. Défaut : `8388608` (8 Mio). |
195
-
196
- > Ces deux variables sont lues dans l'environnement du process. Le relais fait
197
- > `import 'dotenv/config'` : un fichier `.env` présent dans le **répertoire courant de
198
- > l'agent** peut donc les fournir. dotenv n'écrase jamais une variable déjà définie —
199
- > le bloc `env` de la configuration MCP l'emporte toujours.
200
-
201
- ### Derrière un frontal qui authentifie
202
-
203
- Quand le gateway n'est pas joint en direct mais à travers un frontal qui exige un token
204
- porteur (hub [`mcp-gateway-manager`](https://github.com/Matthieu-Pesnot-Pin), reverse-proxy
205
- maison…), `GATEWAY_TOKEN` est ce que le relais présente à chaque requête :
206
-
207
- ```json
208
- {
209
- "mcpServers": {
210
- "gateway": {
211
- "command": "npx",
212
- "args": ["@imenam/mcp-http-gateway", "--mcp"],
213
- "env": {
214
- "GATEWAY_URL": "https://my-gateway.fr/jskdlf-jsfklhj",
215
- "GATEWAY_TOKEN": "…"
216
- }
217
- }
218
- }
219
- }
220
- ```
221
-
222
- Un refus (`401`/`403`) est rapporté à l'agent en clair — token manquant, ou token invalide —
223
- plutôt qu'en `HTTP 401` nu. Le token lui-même n'est **jamais** journalisé, pas même tronqué.
224
-
225
- ### Outils exposés
226
-
227
- Le mode MCP expose 3 outils qui reflètent directement les routes du gateway :
228
-
229
- | Outil | Description |
230
- |---|---|
231
- | `list_mcps` | Liste les MCPs disponibles avec leur route et description |
232
- | `list_tools` | Liste les outils d'un MCP donné (`route` requis) |
233
- | `call_tool` | Appelle un outil (`route`, `tool_name`, `arguments`) |
234
-
235
- L'agent découvre d'abord les MCPs disponibles, explore les outils d'un MCP si besoin, puis appelle l'outil voulu.
236
-
237
- ### Fichiers locaux : l'agent nomme, le relais lit
238
-
239
- Le relais tourne sur la machine de l'**agent** ; les MCP, eux, tournent sur la machine du
240
- **gateway**. Un chemin de fichier transmis tel quel ne veut donc rien dire à l'arrivée : le
241
- MCP le cherche chez lui et répond « fichier introuvable ».
242
-
243
- Le relais sait résoudre ça. Dès qu'un outil distant déclare une paire de paramètres
244
- `<x>_path` / `<x>_content`, le relais **lit le fichier localement, l'encode en base64 et
245
- envoie le contenu** dans `<x>_content` (plus le nom dans `<x>_name`, si le schéma le
246
- prévoit). Le paramètre `file_path` reste la seule chose que l'agent fournit : les
247
- paramètres que le relais remplit sont **retirés du schéma** publié à l'agent, par
248
- `tools/list` comme par `list_tools`. L'agent n'a donc aucun arbitrage à faire, et aucune
249
- occasion d'envoyer un chemin qui ne serait pas lisible à l'arrivée.
250
-
251
- Un outil qui ne déclare pas cette paire n'est jamais concerné : un `output_path` seul reste
252
- un chemin, interprété côté gateway comme avant.
253
-
254
- **Le chemin vient de l'agent : il n'est pas digne de confiance.** La fonction est donc
255
- fermée par défaut et s'ouvre dossier par dossier :
256
-
257
- ```json
258
- {
259
- "mcpServers": {
260
- "gateway": {
261
- "command": "npx",
262
- "args": ["@imenam/mcp-http-gateway", "--mcp"],
263
- "env": {
264
- "GATEWAY_URL": "https://my-gateway.fr/jskdlf-jsfklhj",
265
- "GATEWAY_TOKEN": "…",
266
- "GATEWAY_INLINE_ROOTS": "C:\\Users\\moi\\Documents\\tableurs;C:\\travail\\exports"
267
- }
268
- }
269
- }
270
- }
271
- ```
272
-
273
- - **`GATEWAY_INLINE_ROOTS` est l'interrupteur.** Non renseignée, le relais ne lit aucun
274
- fichier et se comporte exactement comme les versions antérieures.
275
- - Les liens symboliques sont résolus **avant** le contrôle : un lien posé dans un dossier
276
- autorisé ne fait pas sortir un fichier situé ailleurs. Une remontée par `..` est refusée
277
- pour la même raison.
278
- - Seules les extensions de `GATEWAY_INLINE_EXTENSIONS` passent (défaut : tableurs).
279
- - Au-delà de `GATEWAY_INLINE_MAX_BYTES` (8 Mio par défaut), refus explicite. Ce défaut
280
- tient sous le plafond de 16 Mio que le hub `mcp-gateway-manager` applique quand il
281
- journalise les corps de requête.
282
- - Tout refus est rapporté à l'agent en clair, et **chaque fichier transmis laisse une ligne
283
- sur `stderr`** : ce qui sort de la machine est lisible dans le journal de l'agent.
284
-
285
- > Côté MCP, cette fonction suppose que l'outil accepte le contenu en base64. C'est le cas
286
- > de [`@imenam/xls-reader`](https://www.npmjs.com/package/@imenam/xls-reader) ≥ 2.2. Un MCP
287
- > qui n'expose que `file_path` ne déclare pas la paire, et rien ne change pour lui.
288
-
289
- > **Le mode MCP distant (`/mcp`, Claude.ai) n'a pas de relais local** : rien ne tourne alors
290
- > sur la machine de l'appelant, donc personne ne peut lire son disque. Par ce chemin, un
291
- > agent qui veut faire lire un fichier doit en envoyer lui-même le contenu.
292
-
293
- ---
294
-
295
- ## Mode MCP distant (HTTPS / Claude.ai)
296
-
297
- Le gateway expose également un **endpoint MCP natif** sur `/mcp` via le transport **Streamable HTTP**, en mode **stateless**. Ce mode permet à Claude.ai — et à tout client MCP distant compatible — de se connecter directement au gateway, sans intermédiaire.
298
-
299
- Chaque `POST /mcp` est traité de façon autonome : aucun `mcp-session-id` n'est émis, aucun état n'est conservé entre deux requêtes. Les trois outils exposés étant du pur requête/réponse (aucune notification serveur→client), aucune session n'est nécessaire. Concrètement :
300
-
301
- - un connecteur laissé inactif ne peut pas « expirer » côté serveur — il n'y a rien à expirer ;
302
- - un redémarrage ou un redéploiement du gateway est transparent pour les clients connectés ;
303
- - l'endpoint peut être scalé horizontalement derrière un load balancer sans affinité de session.
304
-
305
- `GET /mcp` et `DELETE /mcp` répondent `405 Method Not Allowed` : sans session, il n'y a ni flux SSE autonome ni terminaison de session à gérer (la spec Streamable HTTP l'autorise explicitement).
306
-
307
- ### Flow
308
-
309
- ```
310
- Claude.ai
311
- │ HTTPS + Bearer JWT
312
- ▼
313
- [mcp-http-gateway /mcp] ──── 401 + WWW-Authenticate si non authentifié
314
- │
315
- │ (découverte OAuth 2.1)
316
- ▼
317
- [Authorization Server externe] (Auth0, Keycloak, Authentik, Zitadel, Logto…)
318
- ```
319
-
320
- Le gateway agit uniquement comme **Resource Server** : il valide les JWT via JWKS et expose l'URL de l'AS via le Protected Resource Metadata (RFC 9728). Toute la logique OAuth (DCR, PKCE, `/authorize`, `/token`) est déléguée à l'AS externe.
321
-
322
- ### Variables d'environnement
323
-
324
- | Variable | Description |
325
- |---|---|
326
- | `MCP_OAUTH_ENABLED` | `true` pour activer la protection OAuth (recommandé en production) |
327
- | `MCP_PUBLIC_URL` | URL publique canonique du endpoint — ex. `https://gateway.example.com/mcp`. Utilisée comme `resource` (RFC 9728) et `aud` JWT. |
328
- | `OAUTH_JWKS_URI` | URI du JWKS de l'Authorization Server — ex. `https://your-auth-server/.well-known/jwks.json` |
329
- | `OAUTH_ISSUER` | Issuer attendu dans les JWT — ex. `https://your-auth-server` |
330
-
331
- > Sans `MCP_OAUTH_ENABLED=true`, `/mcp` est ouvert (utile pour les tests locaux). Claude.ai refusera cependant de s'y connecter sans 401 + `WWW-Authenticate`.
332
-
333
- ### Configuration Claude.ai
334
-
335
- Dans Claude.ai → **Paramètres → Connecteurs** → Ajouter un connecteur MCP :
336
-
337
- ```
338
- URL : https://gateway.example.com/mcp
339
- Authentification : OAuth (auto-détectée via /.well-known/oauth-protected-resource)
340
- ```
341
-
342
- ### Prérequis côté Authorization Server
343
-
344
- L'AS doit supporter :
345
- - **DCR** (RFC 7591) — `registration_endpoint` exposé
346
- - **PKCE S256** — `code_challenge_methods_supported: ["S256"]`
347
- - `redirect_uri` autorisé : `https://claude.ai/api/mcp/auth_callback`
348
- - JWT signés (RS256 ou ES256) avec claims `iss`, `aud=MCP_PUBLIC_URL`, `exp`
349
-
350
- ### Exposition HTTPS publique
351
-
352
- Le endpoint `/mcp` doit être accessible depuis l'IP range `160.79.104.0/21` (Anthropic) en HTTPS. Options :
353
-
354
- | Option | Commande / Notes |
355
- |---|---|
356
- | **Cloudflare Tunnel** | `cloudflared tunnel --url http://localhost:3500` |
357
- | **ngrok** | `ngrok http 3500` — idéal pour les tests |
358
- | **Reverse-proxy** | Caddy / Traefik en frontal avec cert Let's Encrypt |
359
-
360
- ### Outils exposés
361
-
362
- Les mêmes 3 outils que le mode stdio — avec accès direct aux clients MCP internes (sans repasser par HTTP) :
363
-
364
- | Outil | Description |
365
- |---|---|
366
- | `list_mcps` | Liste les MCPs disponibles avec leur route et description |
367
- | `list_tools` | Liste les outils d'un MCP donné (`route` requis) |
368
- | `call_tool` | Appelle un outil (`route`, `tool_name`, `arguments`) |
369
-
370
- ### Pré-exposition de MCP au démarrage (`EXPOSE_AT_AGENT_STARTUP`)
371
-
372
- Par défaut, l'agent ne reçoit que les 3 outils de découverte et doit appeler `list_mcps` /
373
- `list_tools` pour découvrir les MCP. Pour les MCP fréquemment utilisés, la clé
374
- `EXPOSE_AT_AGENT_STARTUP` publie leurs outils **directement dans la réponse `tools/list`**,
375
- nommés `<serveur>__<outil>`, avec description et `inputSchema` complets. L'agent les appelle
376
- directement, sans passer par `call_tool` et sans aucun aller-retour de découverte.
377
-
378
- L'option se déclare **par MCP**, dans son bloc `env` de `config/mcp-servers.json` — au même
379
- endroit que `MCP_SERVER_ROUTE`, qui est également une directive lue par le gateway :
380
-
381
- ```json
382
- {
383
- "mcpServers": {
384
- "documentor": {
385
- "command": "node",
386
- "args": ["/chemin/vers/documentor/dist/index.js"],
387
- "description": "Documentation manager MCP",
388
- "env": {
389
- "MCP_SERVER_ROUTE": "/documentor",
390
- "EXPOSE_AT_AGENT_STARTUP": "true"
391
- }
392
- },
393
- "scraper": {
394
- "command": "node",
395
- "args": ["/chemin/vers/scraper/dist/index.js"],
396
- "env": {
397
- "MCP_SERVER_ROUTE": "/scraper"
398
- }
399
- }
400
- }
401
- }
402
- ```
403
-
404
- Ici `documentor` est pré-exposé ; `scraper`, sans la clé, ne l'est pas et reste accessible
405
- via `list_mcps` / `list_tools` / `call_tool`. Le `.env` du gateway ne contient **aucune**
406
- indication relative aux MCP.
407
-
408
- La valeur est un booléen : `"true"` (également `"1"`, `"yes"`, `"on"`) ou `"false"`. Toute
409
- autre valeur est refusée avec un avertissement au démarrage et le MCP n'est pas pré-exposé.
410
-
411
- **Pourquoi ce canal ?** Les définitions d'outils passent par `tools/list`, seul canal sans
412
- limite de taille. Le champ `instructions` du protocole, lui, est plafonné par les clients
413
- autour de **2 048 caractères** et tronqué silencieusement au-delà — un MCP de 30 outils
414
- avec ses schémas pèse ~33 000 caractères, il ne peut donc pas y transiter.
415
-
416
- **Quand l'activer ?** Le compromis, pour un MCP réellement utilisé :
417
-
418
- | | Découverte (défaut) | Pré-exposé |
419
- |---|---|---|
420
- | Allers-retours | 3 (`list_mcps`, `list_tools`, `call_tool`) | 1 (appel direct) |
421
- | Contexte | `list_tools` déverse tous les schémas dans la conversation dès le 1ᵉʳ usage, définitivement | les noms d'outils, puis le schéma des seuls outils utilisés |
422
-
423
- À activer donc pour les MCP utilisés dans la majorité des sessions. À laisser en découverte
424
- pour les MCP occasionnels : un MCP pré-exposé mais inutilisé coûte ses noms d'outils à
425
- chaque requête, et multiplier les outils similaires dégrade la sélection par l'agent.
426
-
427
- Dans tous les cas, le MCP enfant reste hébergé côté gateway : seules ses **définitions**
428
- sont relayées, les appels sont proxifiés. L'isolation est préservée.
429
-
430
- Cette config vit **uniquement côté serveur** et s'applique de façon transparente aux deux
431
- points d'entrée par lesquels un agent atteint le gateway :
432
-
433
- - **endpoint `/mcp` distant** (cas Claude.ai) — les outils proxy sont publiés directement ;
434
- - **bridge stdio** (`server.js --mcp`, cas local) — au démarrage, le bridge interroge le
435
- serveur (`GET /startup-exposition`), publie les outils reçus et route leurs appels vers
436
- la bonne route. Le bridge ne connaît pas la config : il ne fait que transmettre ce que le
437
- serveur décide.
438
-
439
- ---
440
-
441
- ## Ajouter un MCP
442
-
443
- 1. Ajouter l'entrée dans `config/mcp-servers.json` avec `MCP_SERVER_ROUTE` dans `env`
444
- 2. Redémarrer le gateway
445
-
446
- Aucune modification du MCP n'est nécessaire.
1
+ # MCP HTTP Gateway
2
+
3
+ Serveur HTTP qui expose n'importe quel serveur MCP (Model Context Protocol) en tant qu'API REST, sans modifier les MCPs eux-mêmes. Conçu pour permettre à des agents ou applications ne supportant pas nativement le protocole MCP de communiquer avec l'écosystème MCP via de simples requêtes HTTP.
4
+
5
+ ## Fonctionnement
6
+
7
+ Chaque MCP tourne en tant que processus enfant (transport stdio). Le gateway s'y connecte via le SDK MCP officiel et expose ses outils sur des routes HTTP configurables.
8
+
9
+ ```
10
+ Agent / App externe
11
+ │
12
+ │ HTTP
13
+ ▼
14
+ MCP HTTP Gateway (port 3500)
15
+ │
16
+ │ stdio (SDK MCP)
17
+ ├──▶ mcp-documentor (/documentor)
18
+ ├──▶ mcp-raindrop (/raindrop)
19
+ └──▶ ...
20
+ ```
21
+
22
+ ## Installation
23
+
24
+ **En tant que CLI globale :**
25
+
26
+ ```bash
27
+ npm install -g @imenam/mcp-http-gateway
28
+ ```
29
+
30
+ **Via npx (sans installation) :**
31
+
32
+ ```bash
33
+ npx @imenam/mcp-http-gateway
34
+ ```
35
+
36
+ **En dépendance locale d'un projet :**
37
+
38
+ ```bash
39
+ npm install @imenam/mcp-http-gateway
40
+ ```
41
+
42
+ ## Configuration
43
+
44
+ ### `.env`
45
+
46
+ ```env
47
+ GATEWAY_PORT=3500
48
+ GATEWAY_CONFIG_DIR=./config
49
+ ```
50
+
51
+ | Variable | Défaut | Description |
52
+ |---|---|---|
53
+ | `GATEWAY_PORT` | `3500` | Port HTTP du gateway |
54
+ | `GATEWAY_CONFIG_DIR` | `./config` (relatif au cwd) | Dossier contenant `mcp-servers.json` |
55
+ | `GATEWAY_BODY_LIMIT` | `32mb` | Taille maximale du corps d'une requête. Le défaut d'express (100 ko) ne suffit pas dès qu'un appel d'outil porte le contenu d'un fichier en base64 ; au-delà de cette limite, la réponse est un `413`. |
56
+
57
+ ### `mcp-servers.json`
58
+
59
+ À créer dans le dossier de configuration (`./config` par défaut, ou le chemin pointé par `GATEWAY_CONFIG_DIR`). Un fichier `config/mcp-servers.example.json` est fourni comme point de départ.
60
+
61
+ Même format que la configuration `mcpServers` de Cursor. Ajouter `MCP_SERVER_ROUTE` dans `env` pour définir la route HTTP du MCP, et `description` pour la documentation.
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "mon-mcp": {
67
+ "command": "node",
68
+ "args": ["/chemin/vers/dist/index.js"],
69
+ "description": "Description courte du MCP",
70
+ "env": {
71
+ "MA_VARIABLE": "valeur",
72
+ "MCP_SERVER_ROUTE": "/ma-route"
73
+ }
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ > Un MCP sans `MCP_SERVER_ROUTE` est ignoré au démarrage.
80
+
81
+ ## Démarrage
82
+
83
+ ```bash
84
+ npm start
85
+ ```
86
+
87
+ Le gateway connecte chaque MCP configuré et démarre le serveur HTTP. Les MCPs en échec au démarrage sont ignorés sans bloquer les autres.
88
+
89
+ ## Routes
90
+
91
+ ### `GET /mcps`
92
+
93
+ Retourne la liste des MCPs connectés avec leur route, description et instructions d'utilisation (format Markdown).
94
+
95
+ ```bash
96
+ curl http://localhost:3500/mcps
97
+ ```
98
+
99
+ ---
100
+
101
+ ### `GET /{route}/tools`
102
+
103
+ Liste les outils disponibles d'un MCP avec leur nom, description et schéma d'arguments.
104
+
105
+ ```bash
106
+ curl http://localhost:3500/documentor/tools
107
+ ```
108
+
109
+ ---
110
+
111
+ ### `POST /{route}/tools/:toolName`
112
+
113
+ Appelle un outil du MCP. Les arguments sont passés dans le body JSON.
114
+
115
+ ```bash
116
+ curl -X POST http://localhost:3500/documentor/tools/list_projects -H "Content-Type: application/json" -d '{}'
117
+ ```
118
+
119
+ ```bash
120
+ curl -X POST http://localhost:3500/documentor/tools/get_doc -H "Content-Type: application/json" -d '{"id":"mon-doc-id"}'
121
+ ```
122
+
123
+ #### Réponse
124
+
125
+ La réponse est le format brut du protocole MCP — un tableau `content` de blocs typés :
126
+
127
+ ```json
128
+ {
129
+ "content": [
130
+ { "type": "text", "text": "Résultat de l'outil..." }
131
+ ]
132
+ }
133
+ ```
134
+
135
+ Pour extraire le texte directement avec `jq` :
136
+
137
+ ```bash
138
+ curl -s -X POST http://localhost:3500/documentor/tools/list_projects -H "Content-Type: application/json" -d '{}' | jq -r '.content[0].text'
139
+ ```
140
+
141
+ ## Mode MCP
142
+
143
+ Le gateway peut également démarrer en tant que **serveur MCP** (transport stdio). Dans ce mode, il se connecte à une instance du gateway HTTP et expose ses outils comme des outils MCP natifs.
144
+
145
+ ### Cas d'usage : agent dans un conteneur Docker
146
+
147
+ Le gateway HTTP tourne **hors du conteneur**, avec accès aux MCPs et leurs clés sensibles. L'agent dans le conteneur reçoit uniquement le gateway en mode MCP — aucune clé ne lui est injectée.
148
+
149
+ ```
150
+ [Agent dans conteneur]
151
+ │ stdio (MCP)
152
+ ▼
153
+ [mcp-http-gateway --mcp] ←── GATEWAY_URL
154
+ │ HTTP
155
+ ▼
156
+ [mcp-http-gateway hors conteneur]
157
+ │ stdio
158
+ ▼
159
+ [MCPs réels (clés sensibles)]
160
+ ```
161
+
162
+ ### Démarrage
163
+
164
+ ```bash
165
+ GATEWAY_URL=http://host.docker.internal:3500 npx @imenam/mcp-http-gateway --mcp
166
+ ```
167
+
168
+ ### Configuration dans le conteneur
169
+
170
+ ```json
171
+ {
172
+ "mcpServers": {
173
+ "gateway": {
174
+ "command": "npx",
175
+ "args": ["@imenam/mcp-http-gateway", "--mcp"],
176
+ "env": {
177
+ "GATEWAY_URL": "http://host.docker.internal:3500"
178
+ }
179
+ }
180
+ }
181
+ }
182
+ ```
183
+
184
+ > `host.docker.internal` est l'adresse de l'hôte depuis un conteneur Docker.
185
+
186
+ ### Variables d'environnement du mode `--mcp`
187
+
188
+ | Variable | Requis | Description |
189
+ |---|---|---|
190
+ | `GATEWAY_URL` | oui | Base HTTP du gateway à relayer, sans barre finale. Le relais compose lui-même ses routes REST (`/startup-exposition`, `/mcp-definitions`, `/{route}/tools`, `/{route}/tools/{outil}`) : **ne pas** y ajouter `/mcp`. |
191
+ | `GATEWAY_TOKEN` | non | Token porteur. S'il est défini, **tous** les appels sortants du relais portent `Authorization: Bearer <token>`. Absent, aucun en-tête n'est ajouté — le comportement est exactement celui des versions antérieures. |
192
+ | `GATEWAY_INLINE_ROOTS` | non | Dossiers depuis lesquels le relais peut lire un fichier et en transmettre le contenu (cf. [Fichiers locaux](#fichiers-locaux--lagent-nomme-le-relais-lit)). **Non renseignée, tout appel à un outil qui déclare la paire échoue explicitement.** Séparateur : `;` sous Windows, `:` ailleurs. |
193
+ | `GATEWAY_INLINE_EXTENSIONS` | non | Extensions autorisées à la lecture, séparées par des virgules. Défaut : `*` (toutes) — les racines restent le vrai garde-fou. |
194
+ | `GATEWAY_INLINE_MAX_BYTES` | non | Taille maximale d'un fichier transmis, en octets, dans les deux sens. Défaut : `8388608` (8 Mio). |
195
+ | `GATEWAY_INLINE_WRITE_ROOTS` | non | Dossiers dans lesquels le relais peut écrire un fichier renvoyé par un MCP (cf. [Fichiers reçus](#fichiers-reçus--le-mcp-produit-le-relais-écrit)). **Non renseignée, la fonction est désactivée** et tout outil qui la réclame échoue explicitement. Séparateur : `;` sous Windows, `:` ailleurs. |
196
+ | `GATEWAY_INLINE_WRITE_EXTENSIONS` | non | Extensions autorisées à l'écriture, séparées par des virgules. Défaut : `*` (toutes) — les racines restent le vrai garde-fou. |
197
+
198
+ > Ces deux variables sont lues dans l'environnement du process. Le relais fait
199
+ > `import 'dotenv/config'` : un fichier `.env` présent dans le **répertoire courant de
200
+ > l'agent** peut donc les fournir. dotenv n'écrase jamais une variable déjà définie —
201
+ > le bloc `env` de la configuration MCP l'emporte toujours.
202
+
203
+ ### Derrière un frontal qui authentifie
204
+
205
+ Quand le gateway n'est pas joint en direct mais à travers un frontal qui exige un token
206
+ porteur (hub [`mcp-gateway-manager`](https://github.com/Matthieu-Pesnot-Pin), reverse-proxy
207
+ maison…), `GATEWAY_TOKEN` est ce que le relais présente à chaque requête :
208
+
209
+ ```json
210
+ {
211
+ "mcpServers": {
212
+ "gateway": {
213
+ "command": "npx",
214
+ "args": ["@imenam/mcp-http-gateway", "--mcp"],
215
+ "env": {
216
+ "GATEWAY_URL": "https://my-gateway.fr/jskdlf-jsfklhj",
217
+ "GATEWAY_TOKEN": "…"
218
+ }
219
+ }
220
+ }
221
+ }
222
+ ```
223
+
224
+ Un refus (`401`/`403`) est rapporté à l'agent en clair — token manquant, ou token invalide —
225
+ plutôt qu'en `HTTP 401` nu. Le token lui-même n'est **jamais** journalisé, pas même tronqué.
226
+
227
+ ### Outils exposés
228
+
229
+ Le mode MCP expose 3 outils qui reflètent directement les routes du gateway :
230
+
231
+ | Outil | Description |
232
+ |---|---|
233
+ | `list_mcps` | Liste les MCPs disponibles avec leur route et description |
234
+ | `list_tools` | Liste les outils d'un MCP donné (`route` requis) |
235
+ | `call_tool` | Appelle un outil (`route`, `tool_name`, `arguments`) |
236
+
237
+ L'agent découvre d'abord les MCPs disponibles, explore les outils d'un MCP si besoin, puis appelle l'outil voulu.
238
+
239
+ ### Fichiers locaux : l'agent nomme, le relais lit
240
+
241
+ Le relais tourne sur la machine de l'**agent** ; les MCP, eux, tournent sur la machine du
242
+ **gateway**. Un chemin de fichier transmis tel quel ne veut donc rien dire à l'arrivée : le
243
+ MCP le cherche chez lui et répond « fichier introuvable ».
244
+
245
+ Le relais sait résoudre ça. Dès qu'un outil distant déclare une paire de paramètres
246
+ `<x>_path` / `<x>_content`, le relais **lit le fichier localement, l'encode en base64 et
247
+ envoie le contenu** dans `<x>_content` (plus le nom dans `<x>_name`, si le schéma le
248
+ prévoit). Le paramètre `file_path` reste la seule chose que l'agent fournit : les
249
+ paramètres que le relais remplit sont **retirés du schéma** publié à l'agent, par
250
+ `tools/list` comme par `list_tools`. L'agent n'a donc aucun arbitrage à faire, et aucune
251
+ occasion d'envoyer un chemin qui ne serait pas lisible à l'arrivée.
252
+
253
+ Un outil qui ne déclare pas cette paire n'est jamais concerné : un `output_path` seul reste
254
+ un chemin, interprété côté gateway comme avant.
255
+
256
+ **Le chemin vient de l'agent : il n'est pas digne de confiance.** La fonction est donc
257
+ fermée par défaut et s'ouvre dossier par dossier :
258
+
259
+ ```json
260
+ {
261
+ "mcpServers": {
262
+ "gateway": {
263
+ "command": "npx",
264
+ "args": ["@imenam/mcp-http-gateway", "--mcp"],
265
+ "env": {
266
+ "GATEWAY_URL": "https://my-gateway.fr/jskdlf-jsfklhj",
267
+ "GATEWAY_TOKEN": "…",
268
+ "GATEWAY_INLINE_ROOTS": "C:\\Users\\moi\\Documents\\tableurs;C:\\travail\\exports"
269
+ }
270
+ }
271
+ }
272
+ }
273
+ ```
274
+
275
+ - **`GATEWAY_INLINE_ROOTS` est l'interrupteur.** Non renseignée, le relais ne lit aucun
276
+ fichier et **l'appel échoue** en nommant la variable à définir. Un outil qui déclare la
277
+ paire attend le contenu : laisser filer le chemin ferait chercher le fichier au MCP sur
278
+ la machine du gateway, où il ne le trouverait pas — ou pire, où il traiterait un homonyme
279
+ sans rien signaler. Pas de repli, pas de dégradation silencieuse.
280
+
281
+ > **Changement de comportement.** Les versions antérieures laissaient passer le chemin tel
282
+ > quel dans ce cas. Si un déploiement existant s'appuyait là-dessus, renseignez
283
+ > `GATEWAY_INLINE_ROOTS` — le message d'erreur le dit explicitement.
284
+ - Les liens symboliques sont résolus **avant** le contrôle : un lien posé dans un dossier
285
+ autorisé ne fait pas sortir un fichier situé ailleurs. Une remontée par `..` est refusée
286
+ pour la même raison.
287
+ - Seules les extensions de `GATEWAY_INLINE_EXTENSIONS` passent (défaut : toutes).
288
+ - Au-delà de `GATEWAY_INLINE_MAX_BYTES` (8 Mio par défaut), refus explicite. Ce défaut
289
+ tient sous le plafond de 16 Mio que le hub `mcp-gateway-manager` applique quand il
290
+ journalise les corps de requête.
291
+ - Tout refus est rapporté à l'agent en clair, et **chaque fichier transmis laisse une ligne
292
+ sur `stderr`** : ce qui sort de la machine est lisible dans le journal de l'agent.
293
+
294
+ > Côté MCP, cette fonction suppose que l'outil accepte le contenu en base64. C'est le cas
295
+ > de [`@imenam/xls-reader`](https://www.npmjs.com/package/@imenam/xls-reader) ≥ 2.2. Un MCP
296
+ > qui n'expose que `file_path` ne déclare pas la paire, et rien ne change pour lui.
297
+
298
+ > **Le mode MCP distant (`/mcp`, Claude.ai) n'a pas de relais local** : rien ne tourne alors
299
+ > sur la machine de l'appelant, donc personne ne peut lire son disque. Par ce chemin, un
300
+ > agent qui veut faire lire un fichier doit en envoyer lui-même le contenu.
301
+
302
+ ### Fichiers reçus : le MCP produit, le relais écrit
303
+
304
+ Le sens retour, symétrique du précédent. Un MCP qui *fabrique* un fichier — un export, une
305
+ pièce jointe, un téléchargement — ne peut pas le déposer sur la machine de l'agent : le
306
+ protocole n'offre au serveur aucune primitive pour y écrire. Un chemin de destination
307
+ transmis tel quel ferait écrire le MCP sur **son** disque, sans que personne s'en aperçoive.
308
+
309
+ Dès qu'un outil distant déclare la paire `<x>_path` / `<x>_relay`, le relais :
310
+
311
+ 1. retire `<x>_relay` du schéma publié à l'agent, qui ne voit qu'un dossier à nommer ;
312
+ 2. retient `<x>_path`, ne le transmet pas, et pose `<x>_relay` à `true` ;
313
+ 3. reçoit le contenu du fichier dans la réponse du MCP, l'écrit **ici**, et le retire du
314
+ résultat avant de le rendre à l'agent.
315
+
316
+ ```json
317
+ {
318
+ "mcpServers": {
319
+ "gateway": {
320
+ "command": "npx",
321
+ "args": ["@imenam/mcp-http-gateway", "--mcp"],
322
+ "env": {
323
+ "GATEWAY_URL": "https://my-gateway.fr/jskdlf-jsfklhj",
324
+ "GATEWAY_TOKEN": "…",
325
+ "GATEWAY_INLINE_WRITE_ROOTS": "C:\\Users\\moi\\Téléchargements"
326
+ }
327
+ }
328
+ }
329
+ }
330
+ ```
331
+
332
+ - **`GATEWAY_INLINE_WRITE_ROOTS` est l'interrupteur**, distinct de celui de la lecture :
333
+ écrire est plus dangereux que lire, et n'hérite donc pas des dossiers autorisés en lecture.
334
+ - **Non renseignée, l'appel échoue** en nommant la variable à définir. Pas de repli, pas de
335
+ dégradation silencieuse : mieux vaut un fichier perdu qu'un fichier écrit au mauvais endroit.
336
+ - `<x>_path` est toujours un **dossier**, créé si besoin ; le fichier garde le nom que lui
337
+ donne le MCP. Le relais ne renomme jamais.
338
+ - Si l'outil déclare aussi `<x>_overwrite`, le relais l'honore ; sinon il refuse d'écraser.
339
+ - Les liens symboliques sont résolus avant contrôle, y compris sur le plus proche ancêtre
340
+ existant quand la destination n'existe pas encore.
341
+ - **Le contenu est retiré du résultat dans tous les cas**, y compris en cas d'échec
342
+ d'écriture. Un fichier de 8 Mio encodé en base64 n'a rien à faire dans le contexte d'un
343
+ agent : ça coûte cher, et un agent qui le recopierait à la main le corromprait.
344
+ - Chaque fichier écrit laisse une ligne sur `stderr`.
345
+
346
+ > Côté MCP, cette fonction suppose que l'outil sache renvoyer son fichier en bloc
347
+ > `resource` (avec `blob` et `name`) quand `<x>_relay` est posé. C'est le cas de
348
+ > [`file-share-mcp`](https://www.npmjs.com/package/file-share-mcp). Un MCP qui ne déclare
349
+ > pas la paire n'est pas concerné, et son résultat repart intact.
350
+
351
+ ---
352
+
353
+ ## Mode MCP distant (HTTPS / Claude.ai)
354
+
355
+ Le gateway expose également un **endpoint MCP natif** sur `/mcp` via le transport **Streamable HTTP**, en mode **stateless**. Ce mode permet à Claude.ai — et à tout client MCP distant compatible — de se connecter directement au gateway, sans intermédiaire.
356
+
357
+ Chaque `POST /mcp` est traité de façon autonome : aucun `mcp-session-id` n'est émis, aucun état n'est conservé entre deux requêtes. Les trois outils exposés étant du pur requête/réponse (aucune notification serveur→client), aucune session n'est nécessaire. Concrètement :
358
+
359
+ - un connecteur laissé inactif ne peut pas « expirer » côté serveur — il n'y a rien à expirer ;
360
+ - un redémarrage ou un redéploiement du gateway est transparent pour les clients connectés ;
361
+ - l'endpoint peut être scalé horizontalement derrière un load balancer sans affinité de session.
362
+
363
+ `GET /mcp` et `DELETE /mcp` répondent `405 Method Not Allowed` : sans session, il n'y a ni flux SSE autonome ni terminaison de session à gérer (la spec Streamable HTTP l'autorise explicitement).
364
+
365
+ ### Flow
366
+
367
+ ```
368
+ Claude.ai
369
+ │ HTTPS + Bearer JWT
370
+ ▼
371
+ [mcp-http-gateway /mcp] ──── 401 + WWW-Authenticate si non authentifié
372
+ │
373
+ │ (découverte OAuth 2.1)
374
+ ▼
375
+ [Authorization Server externe] (Auth0, Keycloak, Authentik, Zitadel, Logto…)
376
+ ```
377
+
378
+ Le gateway agit uniquement comme **Resource Server** : il valide les JWT via JWKS et expose l'URL de l'AS via le Protected Resource Metadata (RFC 9728). Toute la logique OAuth (DCR, PKCE, `/authorize`, `/token`) est déléguée à l'AS externe.
379
+
380
+ ### Variables d'environnement
381
+
382
+ | Variable | Description |
383
+ |---|---|
384
+ | `MCP_OAUTH_ENABLED` | `true` pour activer la protection OAuth (recommandé en production) |
385
+ | `MCP_PUBLIC_URL` | URL publique canonique du endpoint — ex. `https://gateway.example.com/mcp`. Utilisée comme `resource` (RFC 9728) et `aud` JWT. |
386
+ | `OAUTH_JWKS_URI` | URI du JWKS de l'Authorization Server — ex. `https://your-auth-server/.well-known/jwks.json` |
387
+ | `OAUTH_ISSUER` | Issuer attendu dans les JWT — ex. `https://your-auth-server` |
388
+
389
+ > Sans `MCP_OAUTH_ENABLED=true`, `/mcp` est ouvert (utile pour les tests locaux). Claude.ai refusera cependant de s'y connecter sans 401 + `WWW-Authenticate`.
390
+
391
+ ### Configuration Claude.ai
392
+
393
+ Dans Claude.ai → **Paramètres → Connecteurs** → Ajouter un connecteur MCP :
394
+
395
+ ```
396
+ URL : https://gateway.example.com/mcp
397
+ Authentification : OAuth (auto-détectée via /.well-known/oauth-protected-resource)
398
+ ```
399
+
400
+ ### Prérequis côté Authorization Server
401
+
402
+ L'AS doit supporter :
403
+ - **DCR** (RFC 7591) — `registration_endpoint` exposé
404
+ - **PKCE S256** — `code_challenge_methods_supported: ["S256"]`
405
+ - `redirect_uri` autorisé : `https://claude.ai/api/mcp/auth_callback`
406
+ - JWT signés (RS256 ou ES256) avec claims `iss`, `aud=MCP_PUBLIC_URL`, `exp`
407
+
408
+ ### Exposition HTTPS publique
409
+
410
+ Le endpoint `/mcp` doit être accessible depuis l'IP range `160.79.104.0/21` (Anthropic) en HTTPS. Options :
411
+
412
+ | Option | Commande / Notes |
413
+ |---|---|
414
+ | **Cloudflare Tunnel** | `cloudflared tunnel --url http://localhost:3500` |
415
+ | **ngrok** | `ngrok http 3500` — idéal pour les tests |
416
+ | **Reverse-proxy** | Caddy / Traefik en frontal avec cert Let's Encrypt |
417
+
418
+ ### Outils exposés
419
+
420
+ Les mêmes 3 outils que le mode stdio — avec accès direct aux clients MCP internes (sans repasser par HTTP) :
421
+
422
+ | Outil | Description |
423
+ |---|---|
424
+ | `list_mcps` | Liste les MCPs disponibles avec leur route et description |
425
+ | `list_tools` | Liste les outils d'un MCP donné (`route` requis) |
426
+ | `call_tool` | Appelle un outil (`route`, `tool_name`, `arguments`) |
427
+
428
+ ### Pré-exposition de MCP au démarrage (`EXPOSE_AT_AGENT_STARTUP`)
429
+
430
+ Par défaut, l'agent ne reçoit que les 3 outils de découverte et doit appeler `list_mcps` /
431
+ `list_tools` pour découvrir les MCP. Pour les MCP fréquemment utilisés, la clé
432
+ `EXPOSE_AT_AGENT_STARTUP` publie leurs outils **directement dans la réponse `tools/list`**,
433
+ nommés `<serveur>__<outil>`, avec description et `inputSchema` complets. L'agent les appelle
434
+ directement, sans passer par `call_tool` et sans aucun aller-retour de découverte.
435
+
436
+ L'option se déclare **par MCP**, dans son bloc `env` de `config/mcp-servers.json` — au même
437
+ endroit que `MCP_SERVER_ROUTE`, qui est également une directive lue par le gateway :
438
+
439
+ ```json
440
+ {
441
+ "mcpServers": {
442
+ "documentor": {
443
+ "command": "node",
444
+ "args": ["/chemin/vers/documentor/dist/index.js"],
445
+ "description": "Documentation manager MCP",
446
+ "env": {
447
+ "MCP_SERVER_ROUTE": "/documentor",
448
+ "EXPOSE_AT_AGENT_STARTUP": "true"
449
+ }
450
+ },
451
+ "scraper": {
452
+ "command": "node",
453
+ "args": ["/chemin/vers/scraper/dist/index.js"],
454
+ "env": {
455
+ "MCP_SERVER_ROUTE": "/scraper"
456
+ }
457
+ }
458
+ }
459
+ }
460
+ ```
461
+
462
+ Ici `documentor` est pré-exposé ; `scraper`, sans la clé, ne l'est pas et reste accessible
463
+ via `list_mcps` / `list_tools` / `call_tool`. Le `.env` du gateway ne contient **aucune**
464
+ indication relative aux MCP.
465
+
466
+ La valeur est un booléen : `"true"` (également `"1"`, `"yes"`, `"on"`) ou `"false"`. Toute
467
+ autre valeur est refusée avec un avertissement au démarrage et le MCP n'est pas pré-exposé.
468
+
469
+ **Pourquoi ce canal ?** Les définitions d'outils passent par `tools/list`, seul canal sans
470
+ limite de taille. Le champ `instructions` du protocole, lui, est plafonné par les clients
471
+ autour de **2 048 caractères** et tronqué silencieusement au-delà — un MCP de 30 outils
472
+ avec ses schémas pèse ~33 000 caractères, il ne peut donc pas y transiter.
473
+
474
+ **Quand l'activer ?** Le compromis, pour un MCP réellement utilisé :
475
+
476
+ | | Découverte (défaut) | Pré-exposé |
477
+ |---|---|---|
478
+ | Allers-retours | 3 (`list_mcps`, `list_tools`, `call_tool`) | 1 (appel direct) |
479
+ | Contexte | `list_tools` déverse tous les schémas dans la conversation dès le 1ᵉʳ usage, définitivement | les noms d'outils, puis le schéma des seuls outils utilisés |
480
+
481
+ À activer donc pour les MCP utilisés dans la majorité des sessions. À laisser en découverte
482
+ pour les MCP occasionnels : un MCP pré-exposé mais inutilisé coûte ses noms d'outils à
483
+ chaque requête, et multiplier les outils similaires dégrade la sélection par l'agent.
484
+
485
+ Dans tous les cas, le MCP enfant reste hébergé côté gateway : seules ses **définitions**
486
+ sont relayées, les appels sont proxifiés. L'isolation est préservée.
487
+
488
+ Cette config vit **uniquement côté serveur** et s'applique de façon transparente aux deux
489
+ points d'entrée par lesquels un agent atteint le gateway :
490
+
491
+ - **endpoint `/mcp` distant** (cas Claude.ai) — les outils proxy sont publiés directement ;
492
+ - **bridge stdio** (`server.js --mcp`, cas local) — au démarrage, le bridge interroge le
493
+ serveur (`GET /startup-exposition`), publie les outils reçus et route leurs appels vers
494
+ la bonne route. Le bridge ne connaît pas la config : il ne fait que transmettre ce que le
495
+ serveur décide.
496
+
497
+ ---
498
+
499
+ ## Ajouter un MCP
500
+
501
+ 1. Ajouter l'entrée dans `config/mcp-servers.json` avec `MCP_SERVER_ROUTE` dans `env`
502
+ 2. Redémarrer le gateway
503
+
504
+ Aucune modification du MCP n'est nécessaire.