@imenam/mcp-http-gateway 2.0.14 → 2.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +663 -512
- package/gateway-exec.js +162 -0
- package/gateway-watch.js +348 -0
- package/http-relay.js +93 -0
- package/mcp-mode.js +722 -647
- package/package.json +57 -54
- package/server.js +33 -6
package/README.md
CHANGED
|
@@ -1,512 +1,663 @@
|
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
###
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
-
|
|
368
|
-
|
|
369
|
-
- l'
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
L'
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
`
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
- **
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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`. Ne s'applique pas aux routes de [relais HTTP](#relais-http-mcp_http_relay_port). |
|
|
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
|
+
#### Relais HTTP (`MCP_HTTP_RELAY_PORT`)
|
|
82
|
+
|
|
83
|
+
Un MCP peut avoir besoin de recevoir du trafic HTTP brut en plus de ses appels d'outils : le protocole HTTP de git, un flux binaire, etc. Il déclare alors `MCP_HTTP_RELAY_PORT` dans son bloc `env` :
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
"env": {
|
|
87
|
+
"MCP_SERVER_ROUTE": "/github",
|
|
88
|
+
"MCP_HTTP_RELAY_PORT": "4610"
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- Le MCP écoute lui-même en HTTP sur `127.0.0.1:<port>`. Il reçoit la valeur par son environnement, comme toute variable du bloc `env`.
|
|
93
|
+
- Le gateway relaie tel quel `/{route}/http/*` vers `http://127.0.0.1:<port>/*` : méthode, chemin, requête, en-têtes et corps à l'aller, statut, en-têtes et corps au retour.
|
|
94
|
+
- Le gateway ne connaît rien du protocole relayé. Il authentifie la requête et applique le rate limiting comme sur toutes ses routes, puis retire l'en-tête `Authorization` avant de transmettre : son jeton ne parvient jamais au MCP.
|
|
95
|
+
- Le corps circule en flux, sans être lu ni décodé. `GATEWAY_BODY_LIMIT` ne s'y applique pas.
|
|
96
|
+
- Le port reste sur `127.0.0.1` : le seul point d'entrée exposé est le gateway.
|
|
97
|
+
|
|
98
|
+
Une valeur hors de `1`–`65535` fait ignorer le MCP au démarrage, avec une erreur dans les logs. Si le MCP n'écoute pas sur le port déclaré, le relais répond `502`.
|
|
99
|
+
|
|
100
|
+
## Démarrage
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm start
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
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.
|
|
107
|
+
|
|
108
|
+
## Routes
|
|
109
|
+
|
|
110
|
+
### `GET /mcps`
|
|
111
|
+
|
|
112
|
+
Retourne la liste des MCPs connectés avec leur route, description et instructions d'utilisation (format Markdown).
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
curl http://localhost:3500/mcps
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
### `GET /{route}/tools`
|
|
121
|
+
|
|
122
|
+
Liste les outils disponibles d'un MCP avec leur nom, description et schéma d'arguments.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
curl http://localhost:3500/documentor/tools
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### `POST /{route}/tools/:toolName`
|
|
131
|
+
|
|
132
|
+
Appelle un outil du MCP. Les arguments sont passés dans le body JSON.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
curl -X POST http://localhost:3500/documentor/tools/list_projects -H "Content-Type: application/json" -d '{}'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
curl -X POST http://localhost:3500/documentor/tools/get_doc -H "Content-Type: application/json" -d '{"id":"mon-doc-id"}'
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### Réponse
|
|
143
|
+
|
|
144
|
+
La réponse est le format brut du protocole MCP — un tableau `content` de blocs typés :
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"content": [
|
|
149
|
+
{ "type": "text", "text": "Résultat de l'outil..." }
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Pour extraire le texte directement avec `jq` :
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
curl -s -X POST http://localhost:3500/documentor/tools/list_projects -H "Content-Type: application/json" -d '{}' | jq -r '.content[0].text'
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### `/{route}/http/*`
|
|
163
|
+
|
|
164
|
+
Présente uniquement pour un MCP qui déclare `MCP_HTTP_RELAY_PORT` (voir [Relais HTTP](#relais-http-mcp_http_relay_port)). Toute méthode est acceptée ; la requête est transmise telle quelle au serveur HTTP du MCP, sans l'en-tête `Authorization`.
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
git clone -c http.extraHeader="Authorization: Bearer <jeton>" http://localhost:3500/github/http/repo.git
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Mode MCP
|
|
171
|
+
|
|
172
|
+
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.
|
|
173
|
+
|
|
174
|
+
### Cas d'usage : agent dans un conteneur Docker
|
|
175
|
+
|
|
176
|
+
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.
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
[Agent dans conteneur]
|
|
180
|
+
│ stdio (MCP)
|
|
181
|
+
▼
|
|
182
|
+
[mcp-http-gateway --mcp] ←── GATEWAY_URL
|
|
183
|
+
│ HTTP
|
|
184
|
+
▼
|
|
185
|
+
[mcp-http-gateway hors conteneur]
|
|
186
|
+
│ stdio
|
|
187
|
+
▼
|
|
188
|
+
[MCPs réels (clés sensibles)]
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Démarrage
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
GATEWAY_URL=http://host.docker.internal:3500 npx @imenam/mcp-http-gateway --mcp
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Configuration dans le conteneur
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"mcpServers": {
|
|
202
|
+
"gateway": {
|
|
203
|
+
"command": "npx",
|
|
204
|
+
"args": ["@imenam/mcp-http-gateway", "--mcp"],
|
|
205
|
+
"env": {
|
|
206
|
+
"GATEWAY_URL": "http://host.docker.internal:3500"
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
> `host.docker.internal` est l'adresse de l'hôte depuis un conteneur Docker.
|
|
214
|
+
|
|
215
|
+
### Variables d'environnement du mode `--mcp`
|
|
216
|
+
|
|
217
|
+
| Variable | Requis | Description |
|
|
218
|
+
|---|---|---|
|
|
219
|
+
| `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`. |
|
|
220
|
+
| `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. |
|
|
221
|
+
| `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. |
|
|
222
|
+
| `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. |
|
|
223
|
+
| `GATEWAY_INLINE_MAX_BYTES` | non | Taille maximale d'un fichier transmis, en octets, dans les deux sens. Défaut : `8388608` (8 Mio). |
|
|
224
|
+
| `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. |
|
|
225
|
+
| `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. |
|
|
226
|
+
| `GATEWAY_EXEC_ROUTES` | non | Liste blanche des routes, dans le gateway, des MCP autorisés à faire exécuter des commandes sur la machine de l'agent, séparées par des virgules (ex. `/github`) — cf. [Exécution sur la machine de l'agent](#exécution-sur-la-machine-de-lagent). **Vide, la fonction est désactivée.** Une entrée qui ne commence pas par `/` fait refuser le démarrage. |
|
|
227
|
+
| `GATEWAY_WATCH_DIR` | non | Dossier des fichiers de veille déposés pour les [notifications](#notifications--un-veilleur-sur-la-machine-de-lagent). Défaut : `~/.mcp-http-gateway/watch`. |
|
|
228
|
+
|
|
229
|
+
> Ces deux variables sont lues dans l'environnement du process. Le relais fait
|
|
230
|
+
> `import 'dotenv/config'` : un fichier `.env` présent dans le **répertoire courant de
|
|
231
|
+
> l'agent** peut donc les fournir. dotenv n'écrase jamais une variable déjà définie —
|
|
232
|
+
> le bloc `env` de la configuration MCP l'emporte toujours.
|
|
233
|
+
|
|
234
|
+
### Derrière un frontal qui authentifie
|
|
235
|
+
|
|
236
|
+
Quand le gateway n'est pas joint en direct mais à travers un frontal qui exige un token
|
|
237
|
+
porteur (hub [`mcp-gateway-manager`](https://github.com/Matthieu-Pesnot-Pin), reverse-proxy
|
|
238
|
+
maison…), `GATEWAY_TOKEN` est ce que le relais présente à chaque requête :
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
{
|
|
242
|
+
"mcpServers": {
|
|
243
|
+
"gateway": {
|
|
244
|
+
"command": "npx",
|
|
245
|
+
"args": ["@imenam/mcp-http-gateway", "--mcp"],
|
|
246
|
+
"env": {
|
|
247
|
+
"GATEWAY_URL": "https://my-gateway.fr/jskdlf-jsfklhj",
|
|
248
|
+
"GATEWAY_TOKEN": "…"
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Un refus (`401`/`403`) est rapporté à l'agent en clair — token manquant, ou token invalide —
|
|
256
|
+
plutôt qu'en `HTTP 401` nu. Le token lui-même n'est **jamais** journalisé, pas même tronqué.
|
|
257
|
+
|
|
258
|
+
### Outils exposés
|
|
259
|
+
|
|
260
|
+
Le mode MCP expose 3 outils qui reflètent directement les routes du gateway :
|
|
261
|
+
|
|
262
|
+
| Outil | Description |
|
|
263
|
+
|---|---|
|
|
264
|
+
| `list_mcps` | Liste les MCPs disponibles avec leur route et description |
|
|
265
|
+
| `list_tools` | Liste les outils d'un MCP donné (`route` requis) |
|
|
266
|
+
| `call_tool` | Appelle un outil (`route`, `tool_name`, `arguments`) |
|
|
267
|
+
|
|
268
|
+
L'agent découvre d'abord les MCPs disponibles, explore les outils d'un MCP si besoin, puis appelle l'outil voulu.
|
|
269
|
+
|
|
270
|
+
### Fichiers locaux : l'agent nomme, le relais lit
|
|
271
|
+
|
|
272
|
+
Le relais tourne sur la machine de l'**agent** ; les MCP, eux, tournent sur la machine du
|
|
273
|
+
**gateway**. Un chemin de fichier transmis tel quel ne veut donc rien dire à l'arrivée : le
|
|
274
|
+
MCP le cherche chez lui et répond « fichier introuvable ».
|
|
275
|
+
|
|
276
|
+
Le relais sait résoudre ça. Dès qu'un outil distant déclare une paire de paramètres
|
|
277
|
+
`<x>_path` / `<x>_content`, le relais **lit le fichier localement, l'encode en base64 et
|
|
278
|
+
envoie le contenu** dans `<x>_content` (plus le nom dans `<x>_name`, si le schéma le
|
|
279
|
+
prévoit). Le paramètre `file_path` reste la seule chose que l'agent fournit : les
|
|
280
|
+
paramètres que le relais remplit sont **retirés du schéma** publié à l'agent, par
|
|
281
|
+
`tools/list` comme par `list_tools`. L'agent n'a donc aucun arbitrage à faire, et aucune
|
|
282
|
+
occasion d'envoyer un chemin qui ne serait pas lisible à l'arrivée.
|
|
283
|
+
|
|
284
|
+
Un outil qui ne déclare pas cette paire n'est jamais concerné : un `output_path` seul reste
|
|
285
|
+
un chemin, interprété côté gateway comme avant.
|
|
286
|
+
|
|
287
|
+
`<x>_path` est obligatoire pour l'agent si, et seulement si, le MCP le déclare obligatoire.
|
|
288
|
+
Un outil qui le laisse optionnel accepte sa donnée autrement — un texte passé en ligne, par
|
|
289
|
+
exemple. Sans `<x>_path`, le relais ne lit rien et transmet l'appel tel quel.
|
|
290
|
+
|
|
291
|
+
**Le chemin vient de l'agent : il n'est pas digne de confiance.** La fonction est donc
|
|
292
|
+
fermée par défaut et s'ouvre dossier par dossier :
|
|
293
|
+
|
|
294
|
+
```json
|
|
295
|
+
{
|
|
296
|
+
"mcpServers": {
|
|
297
|
+
"gateway": {
|
|
298
|
+
"command": "npx",
|
|
299
|
+
"args": ["@imenam/mcp-http-gateway", "--mcp"],
|
|
300
|
+
"env": {
|
|
301
|
+
"GATEWAY_URL": "https://my-gateway.fr/jskdlf-jsfklhj",
|
|
302
|
+
"GATEWAY_TOKEN": "…",
|
|
303
|
+
"GATEWAY_INLINE_ROOTS": "C:\\Users\\moi\\Documents\\tableurs;C:\\travail\\exports"
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
- **`GATEWAY_INLINE_ROOTS` est l'interrupteur.** Non renseignée, le relais ne lit aucun
|
|
311
|
+
fichier et **l'appel échoue** en nommant la variable à définir. Un outil qui déclare la
|
|
312
|
+
paire attend le contenu : laisser filer le chemin ferait chercher le fichier au MCP sur
|
|
313
|
+
la machine du gateway, où il ne le trouverait pas — ou pire, où il traiterait un homonyme
|
|
314
|
+
sans rien signaler. Pas de repli, pas de dégradation silencieuse.
|
|
315
|
+
|
|
316
|
+
> **Changement de comportement.** Les versions antérieures laissaient passer le chemin tel
|
|
317
|
+
> quel dans ce cas. Si un déploiement existant s'appuyait là-dessus, renseignez
|
|
318
|
+
> `GATEWAY_INLINE_ROOTS` — le message d'erreur le dit explicitement.
|
|
319
|
+
- Les liens symboliques sont résolus **avant** le contrôle : un lien posé dans un dossier
|
|
320
|
+
autorisé ne fait pas sortir un fichier situé ailleurs. Une remontée par `..` est refusée
|
|
321
|
+
pour la même raison.
|
|
322
|
+
- Seules les extensions de `GATEWAY_INLINE_EXTENSIONS` passent (défaut : toutes).
|
|
323
|
+
- Au-delà de `GATEWAY_INLINE_MAX_BYTES` (8 Mio par défaut), refus explicite. Ce défaut
|
|
324
|
+
tient sous le plafond de 16 Mio que le hub `mcp-gateway-manager` applique quand il
|
|
325
|
+
journalise les corps de requête.
|
|
326
|
+
- Tout refus est rapporté à l'agent en clair, et **chaque fichier transmis laisse une ligne
|
|
327
|
+
sur `stderr`** : ce qui sort de la machine est lisible dans le journal de l'agent.
|
|
328
|
+
|
|
329
|
+
> Côté MCP, cette fonction suppose que l'outil accepte le contenu en base64. C'est le cas
|
|
330
|
+
> de [`@imenam/xls-reader`](https://www.npmjs.com/package/@imenam/xls-reader) ≥ 2.2. Un MCP
|
|
331
|
+
> qui n'expose que `file_path` ne déclare pas la paire, et rien ne change pour lui.
|
|
332
|
+
|
|
333
|
+
> **Le mode MCP distant (`/mcp`, Claude.ai) n'a pas de relais local** : rien ne tourne alors
|
|
334
|
+
> sur la machine de l'appelant, donc personne ne peut lire son disque. Par ce chemin, un
|
|
335
|
+
> agent qui veut faire lire un fichier doit en envoyer lui-même le contenu.
|
|
336
|
+
|
|
337
|
+
### Fichiers reçus : le MCP produit, le relais écrit
|
|
338
|
+
|
|
339
|
+
Le sens retour, symétrique du précédent. Un MCP qui *fabrique* un fichier — un export, une
|
|
340
|
+
pièce jointe, un téléchargement — ne peut pas le déposer sur la machine de l'agent : le
|
|
341
|
+
protocole n'offre au serveur aucune primitive pour y écrire. Un chemin de destination
|
|
342
|
+
transmis tel quel ferait écrire le MCP sur **son** disque, sans que personne s'en aperçoive.
|
|
343
|
+
|
|
344
|
+
Dès qu'un outil distant déclare la paire `<x>_path` / `<x>_relay`, le relais :
|
|
345
|
+
|
|
346
|
+
1. retire `<x>_relay` du schéma publié à l'agent, qui ne voit qu'un dossier à nommer ;
|
|
347
|
+
2. retient `<x>_path`, ne le transmet pas, et pose `<x>_relay` à `true` ;
|
|
348
|
+
3. reçoit le contenu du fichier dans la réponse du MCP, l'écrit **ici**, et le retire du
|
|
349
|
+
résultat avant de le rendre à l'agent.
|
|
350
|
+
|
|
351
|
+
```json
|
|
352
|
+
{
|
|
353
|
+
"mcpServers": {
|
|
354
|
+
"gateway": {
|
|
355
|
+
"command": "npx",
|
|
356
|
+
"args": ["@imenam/mcp-http-gateway", "--mcp"],
|
|
357
|
+
"env": {
|
|
358
|
+
"GATEWAY_URL": "https://my-gateway.fr/jskdlf-jsfklhj",
|
|
359
|
+
"GATEWAY_TOKEN": "…",
|
|
360
|
+
"GATEWAY_INLINE_WRITE_ROOTS": "C:\\Users\\moi\\Téléchargements"
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
- **`GATEWAY_INLINE_WRITE_ROOTS` est l'interrupteur**, distinct de celui de la lecture :
|
|
368
|
+
écrire est plus dangereux que lire, et n'hérite donc pas des dossiers autorisés en lecture.
|
|
369
|
+
- **Non renseignée, l'appel échoue** en nommant la variable à définir. Pas de repli, pas de
|
|
370
|
+
dégradation silencieuse : mieux vaut un fichier perdu qu'un fichier écrit au mauvais endroit.
|
|
371
|
+
- `<x>_path` est toujours un **dossier**, créé si besoin ; le fichier garde le nom que lui
|
|
372
|
+
donne le MCP. Le relais ne renomme jamais.
|
|
373
|
+
- Comme au sens aller, `<x>_path` n'est obligatoire que si le MCP le déclare tel.
|
|
374
|
+
Un outil qui le déclare optionnel dit par là qu'il sait quoi faire sans destination —
|
|
375
|
+
renvoyer une capture inline, par exemple. Sans `<x>_path`, le relais ne retient rien,
|
|
376
|
+
ne pose pas `<x>_relay`, et le résultat repart intact.
|
|
377
|
+
- Si l'outil déclare aussi `<x>_overwrite`, le relais l'honore ; sinon il refuse d'écraser.
|
|
378
|
+
- Les liens symboliques sont résolus avant contrôle, y compris sur le plus proche ancêtre
|
|
379
|
+
existant quand la destination n'existe pas encore.
|
|
380
|
+
- **Le contenu est retiré du résultat dans tous les cas**, y compris en cas d'échec
|
|
381
|
+
d'écriture. Un fichier de 8 Mio encodé en base64 n'a rien à faire dans le contexte d'un
|
|
382
|
+
agent : ça coûte cher, et un agent qui le recopierait à la main le corromprait.
|
|
383
|
+
- Chaque fichier écrit laisse une ligne sur `stderr`.
|
|
384
|
+
|
|
385
|
+
> Côté MCP, cette fonction suppose que l'outil sache renvoyer son fichier en bloc
|
|
386
|
+
> `resource` (avec `blob` et `name`) quand `<x>_relay` est posé. C'est le cas de
|
|
387
|
+
> [`file-share-mcp`](https://www.npmjs.com/package/file-share-mcp). Un MCP qui ne déclare
|
|
388
|
+
> pas la paire n'est pas concerné, et son résultat repart intact.
|
|
389
|
+
|
|
390
|
+
### Exécution sur la machine de l'agent
|
|
391
|
+
|
|
392
|
+
Un MCP du gateway tourne sur la machine du gateway et n'a aucun moyen de lancer quoi que
|
|
393
|
+
ce soit sur celle de l'agent. Si sa route figure dans `GATEWAY_EXEC_ROUTES`, le relais lui
|
|
394
|
+
en donne un, à travers les appels d'outils ordinaires :
|
|
395
|
+
|
|
396
|
+
1. Le relais ajoute à chaque appel d'outil vers cette route l'argument réservé `_gateway_exec` :
|
|
397
|
+
`{ "version": 1, "cwd": "<dossier de lancement du relais>", "gatewayToken": true|false }`.
|
|
398
|
+
2. Le MCP peut répondre par une **demande d'exécution** au lieu d'un résultat final, dans
|
|
399
|
+
`_meta["gateway/exec"]` :
|
|
400
|
+
```json
|
|
401
|
+
{ "id": "…", "steps": [ { "command": "git", "args": ["status"], "cwd": "repo", "env": { "…": "…" } } ] }
|
|
402
|
+
```
|
|
403
|
+
3. Le relais lance les étapes dans l'ordre, **sans shell**, et s'arrête à la première dont
|
|
404
|
+
le code de sortie n'est pas `0`.
|
|
405
|
+
- `cwd` est résolu par rapport au dossier de lancement du relais, c'est-à-dire le dossier où l'agent l'a démarré.
|
|
406
|
+
- Dans `args` et dans les valeurs de `env`, le relais remplace `{{GATEWAY_URL}}` et `{{GATEWAY_TOKEN}}` par ses propres valeurs. Un marqueur dont la valeur manque fait échouer l'appel.
|
|
407
|
+
4. Il rappelle le même outil avec `_gateway_exec.continuation = { "id", "results" }`, un
|
|
408
|
+
résultat `{ code, stdout, stderr, error? }` par étape exécutée. Il recommence tant que le MCP renvoie une demande, dans la limite de 50 allers-retours.
|
|
409
|
+
|
|
410
|
+
L'agent ne voit que le résultat final.
|
|
411
|
+
|
|
412
|
+
```json
|
|
413
|
+
{
|
|
414
|
+
"mcpServers": {
|
|
415
|
+
"gateway": {
|
|
416
|
+
"command": "npx",
|
|
417
|
+
"args": ["-y", "@imenam/mcp-http-gateway", "--mcp"],
|
|
418
|
+
"env": {
|
|
419
|
+
"GATEWAY_URL": "https://gateway.example.com",
|
|
420
|
+
"GATEWAY_TOKEN": "…",
|
|
421
|
+
"GATEWAY_EXEC_ROUTES": "/github"
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
- **`GATEWAY_EXEC_ROUTES` est la liste blanche, côté agent** : les routes, dans le gateway,
|
|
429
|
+
des MCP autorisés à exécuter, séparées par des virgules. Vide, aucun MCP n'exécute.
|
|
430
|
+
- Seul un appel vers une route listée porte `_gateway_exec`. Un MCP qui a besoin
|
|
431
|
+
d'exécuter refuse les autres appels, en le disant.
|
|
432
|
+
- Une demande d'exécution renvoyée par une route non listée fait échouer l'appel, sans
|
|
433
|
+
rien lancer.
|
|
434
|
+
- `/github` et `/github/` désignent la même route. Une entrée qui ne commence pas par `/`
|
|
435
|
+
fait refuser le démarrage du relais.
|
|
436
|
+
- **Pour un MCP listé, l'exécution est libre** : il peut faire lancer n'importe quelle
|
|
437
|
+
commande, avec les droits de l'utilisateur qui fait tourner le relais. Ne listez que des
|
|
438
|
+
MCP que vous maîtrisez.
|
|
439
|
+
- **Les demandes ne viennent que du gateway de `GATEWAY_URL`** : le relais n'en lit que dans
|
|
440
|
+
les réponses à ses propres appels. `GATEWAY_TOKEN` prouve au gateway l'identité du relais,
|
|
441
|
+
mais pas l'inverse. C'est HTTPS qui garantit que la réponse vient bien de ce gateway. Sur
|
|
442
|
+
un `GATEWAY_URL` en `http://` hors de la machine, quiconque se trouve sur le chemin réseau
|
|
443
|
+
peut injecter des demandes d'exécution.
|
|
444
|
+
- Chaque commande laisse une ligne sur `stderr`, avec ses marqueurs non remplacés : le
|
|
445
|
+
jeton n'y figure jamais.
|
|
446
|
+
- La sortie standard et la sortie d'erreur de chaque étape sont tronquées au-delà de 1 Mio.
|
|
447
|
+
- Un MCP dont le schéma refuse les arguments inconnus rejettera `_gateway_exec` : avec
|
|
448
|
+
l'exécution activée, ses appels échouent.
|
|
449
|
+
|
|
450
|
+
> Côté MCP, c'est le cas de [`@imenam/mcp-github`](https://www.npmjs.com/package/@imenam/mcp-github)
|
|
451
|
+
> en rôle `remote` : git s'exécute sur la copie de travail de l'agent, à travers le
|
|
452
|
+
> [relais HTTP](#relais-http-mcp_http_relay_port) du gateway.
|
|
453
|
+
|
|
454
|
+
### Notifications : un veilleur sur la machine de l'agent
|
|
455
|
+
|
|
456
|
+
Le gateway ne sait que répondre : il ne pousse rien vers l'agent. Pour qu'un MCP du gateway
|
|
457
|
+
réveille l'agent quand il a du nouveau (un post publié, un message reçu…), le relais fait
|
|
458
|
+
armer par l'agent un **veilleur** : un processus lancé sur la machine de l'agent par l'outil
|
|
459
|
+
`Monitor` de son hôte, qui appelle en boucle un outil d'attente du MCP à travers l'API REST du
|
|
460
|
+
gateway. Chaque appel reste ouvert jusqu'à ce que le MCP ait un événement, ou 50 s au plus,
|
|
461
|
+
sous les 60 s que le gateway accorde à un appel d'outil. Chaque événement devient une ligne
|
|
462
|
+
sur la sortie du veilleur, et `Monitor` réveille l'agent à chaque ligne.
|
|
463
|
+
|
|
464
|
+
La convention, côté MCP :
|
|
465
|
+
|
|
466
|
+
1. Un **outil d'activation** déclare dans son schéma la propriété réservée `_gateway_watch`.
|
|
467
|
+
Le relais la retire du schéma montré à l'agent, et la remplit à chaque appel de cet outil :
|
|
468
|
+
`{ "version": 1 }`. Sans elle, le MCP sait qu'aucun relais ne veillera pour lui.
|
|
469
|
+
2. L'outil d'activation répond par une **spécification de veille**, dans `_meta["gateway/watch"]` :
|
|
470
|
+
```json
|
|
471
|
+
{ "tool": "wait_for_posts", "arguments": {}, "cursor": "57", "description": "New posts in the Telegram channel" }
|
|
472
|
+
```
|
|
473
|
+
L'outil d'attente, ses arguments fixes, le point de reprise de départ et le libellé du `Monitor`.
|
|
474
|
+
3. Le relais dépose sur la machine de l'agent un **fichier de veille** en `0600` : adresse et
|
|
475
|
+
jeton du gateway, route, outil, arguments, point de reprise. Il rend à l'agent, à la place
|
|
476
|
+
de la réponse du MCP, l'appel `Monitor` à faire :
|
|
477
|
+
```text
|
|
478
|
+
command: "<node>" "<…>/mcp-http-gateway/server.js" --watch "<…>/watch/<id>.json"
|
|
479
|
+
timeout_ms: 1800000
|
|
480
|
+
```
|
|
481
|
+
Le jeton ne figure jamais dans la commande, donc jamais dans la conversation.
|
|
482
|
+
4. Le veilleur (`server.js --watch <fichier>`) appelle l'outil d'attente avec ses arguments,
|
|
483
|
+
plus `cursor` et `timeout_seconds: 50`. L'outil répond, dans `_meta["gateway/events"]` :
|
|
484
|
+
```json
|
|
485
|
+
{ "cursor": "58", "lines": ["[telegram] new post 42: … — read it with get_post"] }
|
|
486
|
+
```
|
|
487
|
+
Le veilleur écrit chaque ligne telle quelle, enregistre le point de reprise dans le fichier
|
|
488
|
+
de veille, et rappelle aussitôt l'outil.
|
|
489
|
+
|
|
490
|
+
Comportement du veilleur :
|
|
491
|
+
|
|
492
|
+
- **Expiration du `Monitor`** (30 minutes au plus) : l'agent relance la même commande, et le
|
|
493
|
+
veilleur repart du point de reprise enregistré, sans rien perdre entre-temps.
|
|
494
|
+
- **Réactivation pendant la veille** : si un veilleur suit déjà ce fichier, le relais le dit
|
|
495
|
+
au lieu de demander un second veilleur, et ne touche pas au point de reprise.
|
|
496
|
+
- **Pannes** : le veilleur réessaie avec une temporisation croissante (de 1 s à 30 s) et ne
|
|
497
|
+
s'arrête jamais de lui-même. Un refus du jeton, un outil qui ne suit pas la convention, ou
|
|
498
|
+
trois échecs de suite sont annoncés une fois sur sa sortie, donc à l'agent ; le retour à la
|
|
499
|
+
normale aussi.
|
|
500
|
+
- **Ménage** : le fichier de veille, qui contient le jeton, est propre au relais qui l'écrit.
|
|
501
|
+
Il est retiré à l'arrêt de ce relais (fermeture de son entrée standard) ou, si le relais a
|
|
502
|
+
été tué, par le prochain relais qui démarre.
|
|
503
|
+
- Une spécification renvoyée par un outil qui n'a pas déclaré `_gateway_watch` est ignorée.
|
|
504
|
+
- Un MCP n'accède ainsi qu'à la veille de ses propres outils : le veilleur n'appelle que
|
|
505
|
+
l'outil nommé dans la spécification, sur la route qui l'a renvoyée.
|
|
506
|
+
|
|
507
|
+
> Côté MCP, c'est le cas de [`@imenam/mcp-telegram`](https://www.npmjs.com/package/@imenam/mcp-telegram) :
|
|
508
|
+
> `enable_notifications` active la veille, `wait_for_posts` est l'outil d'attente.
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
## Mode MCP distant (HTTPS / Claude.ai)
|
|
513
|
+
|
|
514
|
+
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.
|
|
515
|
+
|
|
516
|
+
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 :
|
|
517
|
+
|
|
518
|
+
- un connecteur laissé inactif ne peut pas « expirer » côté serveur — il n'y a rien à expirer ;
|
|
519
|
+
- un redémarrage ou un redéploiement du gateway est transparent pour les clients connectés ;
|
|
520
|
+
- l'endpoint peut être scalé horizontalement derrière un load balancer sans affinité de session.
|
|
521
|
+
|
|
522
|
+
`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).
|
|
523
|
+
|
|
524
|
+
### Flow
|
|
525
|
+
|
|
526
|
+
```
|
|
527
|
+
Claude.ai
|
|
528
|
+
│ HTTPS + Bearer JWT
|
|
529
|
+
▼
|
|
530
|
+
[mcp-http-gateway /mcp] ──── 401 + WWW-Authenticate si non authentifié
|
|
531
|
+
│
|
|
532
|
+
│ (découverte OAuth 2.1)
|
|
533
|
+
▼
|
|
534
|
+
[Authorization Server externe] (Auth0, Keycloak, Authentik, Zitadel, Logto…)
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
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.
|
|
538
|
+
|
|
539
|
+
### Variables d'environnement
|
|
540
|
+
|
|
541
|
+
| Variable | Description |
|
|
542
|
+
|---|---|
|
|
543
|
+
| `MCP_OAUTH_ENABLED` | `true` pour activer la protection OAuth (recommandé en production) |
|
|
544
|
+
| `MCP_PUBLIC_URL` | URL publique canonique du endpoint — ex. `https://gateway.example.com/mcp`. Utilisée comme `resource` (RFC 9728) et `aud` JWT. |
|
|
545
|
+
| `OAUTH_JWKS_URI` | URI du JWKS de l'Authorization Server — ex. `https://your-auth-server/.well-known/jwks.json` |
|
|
546
|
+
| `OAUTH_ISSUER` | Issuer attendu dans les JWT — ex. `https://your-auth-server` |
|
|
547
|
+
|
|
548
|
+
> 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`.
|
|
549
|
+
|
|
550
|
+
### Configuration Claude.ai
|
|
551
|
+
|
|
552
|
+
Dans Claude.ai → **Paramètres → Connecteurs** → Ajouter un connecteur MCP :
|
|
553
|
+
|
|
554
|
+
```
|
|
555
|
+
URL : https://gateway.example.com/mcp
|
|
556
|
+
Authentification : OAuth (auto-détectée via /.well-known/oauth-protected-resource)
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Prérequis côté Authorization Server
|
|
560
|
+
|
|
561
|
+
L'AS doit supporter :
|
|
562
|
+
- **DCR** (RFC 7591) — `registration_endpoint` exposé
|
|
563
|
+
- **PKCE S256** — `code_challenge_methods_supported: ["S256"]`
|
|
564
|
+
- `redirect_uri` autorisé : `https://claude.ai/api/mcp/auth_callback`
|
|
565
|
+
- JWT signés (RS256 ou ES256) avec claims `iss`, `aud=MCP_PUBLIC_URL`, `exp`
|
|
566
|
+
|
|
567
|
+
### Exposition HTTPS publique
|
|
568
|
+
|
|
569
|
+
Le endpoint `/mcp` doit être accessible depuis l'IP range `160.79.104.0/21` (Anthropic) en HTTPS. Options :
|
|
570
|
+
|
|
571
|
+
| Option | Commande / Notes |
|
|
572
|
+
|---|---|
|
|
573
|
+
| **Cloudflare Tunnel** | `cloudflared tunnel --url http://localhost:3500` |
|
|
574
|
+
| **ngrok** | `ngrok http 3500` — idéal pour les tests |
|
|
575
|
+
| **Reverse-proxy** | Caddy / Traefik en frontal avec cert Let's Encrypt |
|
|
576
|
+
|
|
577
|
+
### Outils exposés
|
|
578
|
+
|
|
579
|
+
Les mêmes 3 outils que le mode stdio — avec accès direct aux clients MCP internes (sans repasser par HTTP) :
|
|
580
|
+
|
|
581
|
+
| Outil | Description |
|
|
582
|
+
|---|---|
|
|
583
|
+
| `list_mcps` | Liste les MCPs disponibles avec leur route et description |
|
|
584
|
+
| `list_tools` | Liste les outils d'un MCP donné (`route` requis) |
|
|
585
|
+
| `call_tool` | Appelle un outil (`route`, `tool_name`, `arguments`) |
|
|
586
|
+
|
|
587
|
+
### Pré-exposition de MCP au démarrage (`EXPOSE_AT_AGENT_STARTUP`)
|
|
588
|
+
|
|
589
|
+
Par défaut, l'agent ne reçoit que les 3 outils de découverte et doit appeler `list_mcps` /
|
|
590
|
+
`list_tools` pour découvrir les MCP. Pour les MCP fréquemment utilisés, la clé
|
|
591
|
+
`EXPOSE_AT_AGENT_STARTUP` publie leurs outils **directement dans la réponse `tools/list`**,
|
|
592
|
+
nommés `<serveur>__<outil>`, avec description et `inputSchema` complets. L'agent les appelle
|
|
593
|
+
directement, sans passer par `call_tool` et sans aucun aller-retour de découverte.
|
|
594
|
+
|
|
595
|
+
L'option se déclare **par MCP**, dans son bloc `env` de `config/mcp-servers.json` — au même
|
|
596
|
+
endroit que `MCP_SERVER_ROUTE`, qui est également une directive lue par le gateway :
|
|
597
|
+
|
|
598
|
+
```json
|
|
599
|
+
{
|
|
600
|
+
"mcpServers": {
|
|
601
|
+
"documentor": {
|
|
602
|
+
"command": "node",
|
|
603
|
+
"args": ["/chemin/vers/documentor/dist/index.js"],
|
|
604
|
+
"description": "Documentation manager MCP",
|
|
605
|
+
"env": {
|
|
606
|
+
"MCP_SERVER_ROUTE": "/documentor",
|
|
607
|
+
"EXPOSE_AT_AGENT_STARTUP": "true"
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
"scraper": {
|
|
611
|
+
"command": "node",
|
|
612
|
+
"args": ["/chemin/vers/scraper/dist/index.js"],
|
|
613
|
+
"env": {
|
|
614
|
+
"MCP_SERVER_ROUTE": "/scraper"
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
Ici `documentor` est pré-exposé ; `scraper`, sans la clé, ne l'est pas et reste accessible
|
|
622
|
+
via `list_mcps` / `list_tools` / `call_tool`. Le `.env` du gateway ne contient **aucune**
|
|
623
|
+
indication relative aux MCP.
|
|
624
|
+
|
|
625
|
+
La valeur est un booléen : `"true"` (également `"1"`, `"yes"`, `"on"`) ou `"false"`. Toute
|
|
626
|
+
autre valeur est refusée avec un avertissement au démarrage et le MCP n'est pas pré-exposé.
|
|
627
|
+
|
|
628
|
+
**Pourquoi ce canal ?** Les définitions d'outils passent par `tools/list`, seul canal sans
|
|
629
|
+
limite de taille. Le champ `instructions` du protocole, lui, est plafonné par les clients
|
|
630
|
+
autour de **2 048 caractères** et tronqué silencieusement au-delà — un MCP de 30 outils
|
|
631
|
+
avec ses schémas pèse ~33 000 caractères, il ne peut donc pas y transiter.
|
|
632
|
+
|
|
633
|
+
**Quand l'activer ?** Le compromis, pour un MCP réellement utilisé :
|
|
634
|
+
|
|
635
|
+
| | Découverte (défaut) | Pré-exposé |
|
|
636
|
+
|---|---|---|
|
|
637
|
+
| Allers-retours | 3 (`list_mcps`, `list_tools`, `call_tool`) | 1 (appel direct) |
|
|
638
|
+
| 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 |
|
|
639
|
+
|
|
640
|
+
À activer donc pour les MCP utilisés dans la majorité des sessions. À laisser en découverte
|
|
641
|
+
pour les MCP occasionnels : un MCP pré-exposé mais inutilisé coûte ses noms d'outils à
|
|
642
|
+
chaque requête, et multiplier les outils similaires dégrade la sélection par l'agent.
|
|
643
|
+
|
|
644
|
+
Dans tous les cas, le MCP enfant reste hébergé côté gateway : seules ses **définitions**
|
|
645
|
+
sont relayées, les appels sont proxifiés. L'isolation est préservée.
|
|
646
|
+
|
|
647
|
+
Cette config vit **uniquement côté serveur** et s'applique de façon transparente aux deux
|
|
648
|
+
points d'entrée par lesquels un agent atteint le gateway :
|
|
649
|
+
|
|
650
|
+
- **endpoint `/mcp` distant** (cas Claude.ai) — les outils proxy sont publiés directement ;
|
|
651
|
+
- **bridge stdio** (`server.js --mcp`, cas local) — au démarrage, le bridge interroge le
|
|
652
|
+
serveur (`GET /startup-exposition`), publie les outils reçus et route leurs appels vers
|
|
653
|
+
la bonne route. Le bridge ne connaît pas la config : il ne fait que transmettre ce que le
|
|
654
|
+
serveur décide.
|
|
655
|
+
|
|
656
|
+
---
|
|
657
|
+
|
|
658
|
+
## Ajouter un MCP
|
|
659
|
+
|
|
660
|
+
1. Ajouter l'entrée dans `config/mcp-servers.json` avec `MCP_SERVER_ROUTE` dans `env`
|
|
661
|
+
2. Redémarrer le gateway
|
|
662
|
+
|
|
663
|
+
Aucune modification du MCP n'est nécessaire.
|