@cyanmycelium/mcp-broker 0.3.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/.mcp-broker.example/CONFIGURATION-EN.md +861 -0
  2. package/.mcp-broker.example/CONFIGURATION-FR.md +871 -0
  3. package/.mcp-broker.example/README.md +5 -0
  4. package/.mcp-broker.example/config.json +86 -0
  5. package/README.md +116 -1
  6. package/dist/auth/auth.config.d.ts +40 -0
  7. package/dist/auth/auth.config.js +62 -0
  8. package/dist/auth/auth.config.js.map +1 -0
  9. package/dist/auth/auth.types.d.ts +119 -0
  10. package/dist/auth/auth.types.js +38 -0
  11. package/dist/auth/auth.types.js.map +1 -0
  12. package/dist/auth/http.auth.d.ts +51 -0
  13. package/dist/auth/http.auth.js +121 -0
  14. package/dist/auth/http.auth.js.map +1 -0
  15. package/dist/auth/index.d.ts +16 -0
  16. package/dist/auth/index.js +12 -0
  17. package/dist/auth/index.js.map +1 -0
  18. package/dist/auth/jwt.validator.d.ts +35 -0
  19. package/dist/auth/jwt.validator.js +46 -0
  20. package/dist/auth/jwt.validator.js.map +1 -0
  21. package/dist/auth/provider.auth.d.ts +51 -0
  22. package/dist/auth/provider.auth.js +80 -0
  23. package/dist/auth/provider.auth.js.map +1 -0
  24. package/dist/auth/resource.metadata.d.ts +32 -0
  25. package/dist/auth/resource.metadata.js +26 -0
  26. package/dist/auth/resource.metadata.js.map +1 -0
  27. package/dist/authorization/audit.d.ts +14 -0
  28. package/dist/authorization/audit.js +20 -0
  29. package/dist/authorization/audit.js.map +1 -0
  30. package/dist/authorization/capability.classifier.d.ts +30 -0
  31. package/dist/authorization/capability.classifier.js +85 -0
  32. package/dist/authorization/capability.classifier.js.map +1 -0
  33. package/dist/authorization/index.d.ts +13 -0
  34. package/dist/authorization/index.js +8 -0
  35. package/dist/authorization/index.js.map +1 -0
  36. package/dist/authorization/policy.engine.d.ts +11 -0
  37. package/dist/authorization/policy.engine.js +192 -0
  38. package/dist/authorization/policy.engine.js.map +1 -0
  39. package/dist/authorization/policy.types.d.ts +92 -0
  40. package/dist/authorization/policy.types.js +2 -0
  41. package/dist/authorization/policy.types.js.map +1 -0
  42. package/dist/authorization/resource.path.d.ts +27 -0
  43. package/dist/authorization/resource.path.js +122 -0
  44. package/dist/authorization/resource.path.js.map +1 -0
  45. package/dist/authorization/runtime.d.ts +22 -0
  46. package/dist/authorization/runtime.js +34 -0
  47. package/dist/authorization/runtime.js.map +1 -0
  48. package/dist/authorization/slot.resource.d.ts +12 -0
  49. package/dist/authorization/slot.resource.js +41 -0
  50. package/dist/authorization/slot.resource.js.map +1 -0
  51. package/dist/authorization/subject.mapper.d.ts +15 -0
  52. package/dist/authorization/subject.mapper.js +67 -0
  53. package/dist/authorization/subject.mapper.js.map +1 -0
  54. package/dist/bin.js +49 -0
  55. package/dist/bin.js.map +1 -1
  56. package/dist/broker/adapters/broker.adapter.info.d.ts +3 -3
  57. package/dist/broker/adapters/broker.adapter.info.js +1 -1
  58. package/dist/broker/adapters/broker.adapter.info.js.map +1 -1
  59. package/dist/broker/adapters/broker.adapter.providers.d.ts +2 -2
  60. package/dist/broker/adapters/broker.adapter.providers.js.map +1 -1
  61. package/dist/broker/aggregate/aggregate.catalog.d.ts +23 -11
  62. package/dist/broker/aggregate/aggregate.catalog.js +14 -0
  63. package/dist/broker/aggregate/aggregate.catalog.js.map +1 -1
  64. package/dist/broker/aggregate/aggregate.server.d.ts +22 -3
  65. package/dist/broker/aggregate/aggregate.server.js +115 -9
  66. package/dist/broker/aggregate/aggregate.server.js.map +1 -1
  67. package/dist/broker/aggregate/provider.client.session.d.ts +9 -9
  68. package/dist/broker/aggregate/provider.client.session.js +1 -1
  69. package/dist/broker/aggregate/provider.client.session.js.map +1 -1
  70. package/dist/broker/behaviors/broker.behavior.info.d.ts +2 -2
  71. package/dist/broker/behaviors/broker.behavior.info.js.map +1 -1
  72. package/dist/broker/behaviors/broker.behavior.providers.d.ts +2 -2
  73. package/dist/broker/behaviors/broker.behavior.providers.js.map +1 -1
  74. package/dist/broker/broker.context.d.ts +8 -4
  75. package/dist/broker/broker.grammars.d.ts +52 -86
  76. package/dist/broker/broker.grammars.js +55 -84
  77. package/dist/broker/broker.grammars.js.map +1 -1
  78. package/dist/broker/broker.server.d.ts +28 -24
  79. package/dist/broker/broker.server.js +21 -70
  80. package/dist/broker/broker.server.js.map +1 -1
  81. package/dist/broker/index.d.ts +4 -4
  82. package/dist/broker/index.js +1 -1
  83. package/dist/broker/index.js.map +1 -1
  84. package/dist/config.d.ts +51 -5
  85. package/dist/config.js +1 -1
  86. package/dist/config.js.map +1 -1
  87. package/dist/index.d.ts +13 -9
  88. package/dist/index.js +5 -1
  89. package/dist/index.js.map +1 -1
  90. package/dist/mcpb.loader.d.ts +6 -4
  91. package/dist/mcpb.loader.js +3 -3
  92. package/dist/mcpb.loader.js.map +1 -1
  93. package/dist/remote.transports.d.ts +4 -2
  94. package/dist/remote.transports.js.map +1 -1
  95. package/dist/remote.upstream.d.ts +6 -4
  96. package/dist/remote.upstream.js.map +1 -1
  97. package/dist/stdio.upstream.d.ts +6 -4
  98. package/dist/stdio.upstream.js.map +1 -1
  99. package/dist/upstream.d.ts +3 -1
  100. package/dist/ws.tunnel.builder.d.ts +41 -4
  101. package/dist/ws.tunnel.builder.js +66 -0
  102. package/dist/ws.tunnel.builder.js.map +1 -1
  103. package/dist/ws.tunnel.d.ts +116 -36
  104. package/dist/ws.tunnel.js +384 -51
  105. package/dist/ws.tunnel.js.map +1 -1
  106. package/package.json +7 -3
  107. package/src/auth/auth.config.ts +98 -0
  108. package/src/auth/auth.types.ts +145 -0
  109. package/src/auth/http.auth.ts +132 -0
  110. package/src/auth/index.ts +34 -0
  111. package/src/auth/jwt.validator.ts +63 -0
  112. package/src/auth/provider.auth.ts +114 -0
  113. package/src/auth/resource.metadata.ts +41 -0
  114. package/src/authorization/audit.ts +35 -0
  115. package/src/authorization/capability.classifier.ts +114 -0
  116. package/src/authorization/index.ts +37 -0
  117. package/src/authorization/policy.engine.ts +212 -0
  118. package/src/authorization/policy.types.ts +105 -0
  119. package/src/authorization/resource.path.ts +126 -0
  120. package/src/authorization/runtime.ts +56 -0
  121. package/src/authorization/slot.resource.ts +50 -0
  122. package/src/authorization/subject.mapper.ts +81 -0
  123. package/src/bin.ts +53 -0
  124. package/src/broker/adapters/broker.adapter.info.ts +3 -3
  125. package/src/broker/adapters/broker.adapter.providers.ts +3 -3
  126. package/src/broker/aggregate/aggregate.catalog.ts +48 -20
  127. package/src/broker/aggregate/aggregate.server.ts +146 -16
  128. package/src/broker/aggregate/provider.client.session.ts +22 -22
  129. package/src/broker/behaviors/broker.behavior.info.ts +2 -2
  130. package/src/broker/behaviors/broker.behavior.providers.ts +2 -2
  131. package/src/broker/broker.context.ts +10 -4
  132. package/src/broker/broker.grammars.ts +77 -122
  133. package/src/broker/broker.server.ts +51 -101
  134. package/src/broker/index.ts +5 -7
  135. package/src/config.ts +57 -6
  136. package/src/index.ts +109 -16
  137. package/src/mcpb.loader.ts +14 -11
  138. package/src/remote.transports.ts +8 -5
  139. package/src/remote.upstream.ts +10 -7
  140. package/src/stdio.upstream.ts +8 -5
  141. package/src/upstream.ts +4 -1
  142. package/src/ws.tunnel.builder.ts +89 -9
  143. package/src/ws.tunnel.ts +521 -102
  144. package/web/README.md +94 -0
  145. package/web/assets/logo.png +0 -0
  146. package/web/broker-self-mcp.html +338 -0
  147. package/web/css/styles.css +580 -0
  148. package/web/demos/DemoPlaceholder.html +258 -0
  149. package/web/demos/broker-explorer/css/app.css +393 -0
  150. package/web/demos/broker-explorer/index.html +94 -0
  151. package/web/demos/broker-explorer/js/app.js +271 -0
  152. package/web/demos/broker-explorer/js/mcp-ws-client.js +132 -0
  153. package/web/demos/oauth-lab/README.md +68 -0
  154. package/web/demos/oauth-lab/config.json +116 -0
  155. package/web/demos/oauth-lab/css/app.css +1097 -0
  156. package/web/demos/oauth-lab/index.html +323 -0
  157. package/web/demos/oauth-lab/js/app.js +630 -0
  158. package/web/demos/oauth-lab/server/auth-server.mjs +426 -0
  159. package/web/demos/oauth-lab/server/factory-provider.mjs +269 -0
  160. package/web/demos/oauth-lab/server/smoke-test.mjs +261 -0
  161. package/web/demos/oauth-lab/server/start.mjs +106 -0
  162. package/web/demos/provider-tunnel/css/app.css +384 -0
  163. package/web/demos/provider-tunnel/index.html +99 -0
  164. package/web/demos/provider-tunnel/js/app.js +226 -0
  165. package/web/demos/provider-tunnel/js/toolbox-server.js +186 -0
  166. package/web/index.html +558 -0
  167. package/web/js/lib/broker-tunnel.js +173 -0
@@ -0,0 +1,871 @@
1
+ # Guide pédagogique du fichier `config.json`
2
+
3
+ Ce document explique le fichier [`config.json`](config.json) propriété par
4
+ propriété. Il est destiné aux développeurs qui connaissent peu OAuth, JWT ou
5
+ les modèles de permissions.
6
+
7
+ ## Avant de commencer
8
+
9
+ Le vrai fichier est du JSON strict. Le JSON ne permet pas les commentaires.
10
+ N'ajoutez donc pas de lignes commençant par `//` dans `config.json`.
11
+
12
+ Dans les exemples de ce guide, les commentaires servent uniquement à
13
+ l'explication. Ils ne doivent pas être copiés tels quels dans le fichier JSON.
14
+
15
+ Quelques règles de lecture :
16
+
17
+ - `{` ouvre un objet, c'est-à-dire un ensemble de propriétés.
18
+ - `}` ferme un objet.
19
+ - `[` ouvre une liste.
20
+ - `]` ferme une liste.
21
+ - `,` sépare deux propriétés ou deux éléments d'une liste.
22
+ - Les espaces utilisés pour aligner les valeurs ne changent pas le
23
+ comportement.
24
+ - Les chemins de fichiers relatifs sont résolus depuis le dossier
25
+ `.mcp-broker/`.
26
+
27
+ ## La carte mentale
28
+
29
+ Le fichier répond à cinq questions :
30
+
31
+ 1. Où le broker écoute-t-il ?
32
+ 2. Comment chiffre-t-il les connexions ?
33
+ 3. Comment reconnaît-il les clients et les fournisseurs ?
34
+ 4. Que peut faire chaque client, et sur quelles ressources ?
35
+ 5. Quels serveurs MCP locaux ou empaquetés doit-il charger ?
36
+
37
+ Le bloc `auth` est le plus important pour la sécurité. Il se lit ainsi :
38
+
39
+ ```text
40
+ Sujets JWT Rôles et capacités Chemins de ressources
41
+ qui ? quoi ? où ?
42
+ \ | /
43
+ \ | /
44
+ décision allow ou deny
45
+ ```
46
+
47
+ ## Vocabulaire OAuth et autorisation
48
+
49
+ | Terme | Explication simple |
50
+ |---|---|
51
+ | OAuth 2.1 | Protocole qui permet à un client de présenter un jeton au broker. Le broker ne crée pas ce jeton |
52
+ | Authorization Server | Serveur externe qui authentifie l'utilisateur et émet le jeton |
53
+ | JWT | Format courant du jeton. Il contient des informations appelées claims |
54
+ | Claim | Propriété contenue dans le JWT, par exemple `sub`, `groups` ou `client_id` |
55
+ | JWKS | Adresse publique contenant les clés utilisées pour vérifier la signature des JWT |
56
+ | Scope OAuth | Permission grossière portée par le JWT, vérifiée avant la politique détaillée |
57
+ | Sujet | Identité déduite du JWT, par exemple `user:alice` ou `group:energy-team` |
58
+ | Capacité | Action fonctionnelle stable, par exemple `mcp.tools.diagnose` |
59
+ | Rôle | Groupe réutilisable de capacités |
60
+ | Ressource | Emplacement stable dans la hiérarchie, par exemple `/enterprise/site/area/asset` |
61
+ | Assignment | Affectation d'un rôle à un sujet sur une ressource |
62
+ | Deny | Interdiction explicite, toujours prioritaire sur une autorisation |
63
+ | Slot | Nom technique utilisé pour joindre un fournisseur MCP |
64
+ | Provider | Serveur MCP qui publie ses outils, ressources ou prompts dans un slot |
65
+
66
+ ## Ordre d'une décision d'autorisation
67
+
68
+ Pour chaque requête protégée, le broker suit cet ordre :
69
+
70
+ 1. Il lit le bearer token dans l'en-tête HTTP `Authorization`.
71
+ 2. Il vérifie la signature, l'émetteur, l'audience et l'expiration du JWT.
72
+ 3. Il vérifie `requiredScopes` ou la règle `perSlotScopes` du slot.
73
+ 4. Il transforme les claims JWT en sujets.
74
+ 5. Il transforme l'opération MCP en capacité.
75
+ 6. Il transforme le nom du slot en chemin de ressource.
76
+ 7. Il cherche les rôles affectés aux sujets sur ce chemin.
77
+ 8. Il applique les éventuels `denies`.
78
+ 9. Un deny correspondant refuse toujours la requête.
79
+ 10. Sans deny, au moins un rôle correspondant doit accorder la capacité.
80
+ 11. En l'absence d'autorisation explicite, la requête est refusée.
81
+
82
+ Cette séparation est essentielle :
83
+
84
+ - les scopes OAuth sont une première barrière grossière ;
85
+ - les rôles décrivent ce qui est permis ;
86
+ - les ressources décrivent où cela est permis ;
87
+ - les sujets décrivent à qui cela est permis.
88
+
89
+ ## Lignes 1 à 5 : paramètres généraux
90
+
91
+ ```json
92
+ {
93
+ "port": 3001,
94
+ "host": "0.0.0.0",
95
+ "locale": "fr",
96
+ "brokerName": "broker-eu-west"
97
+ }
98
+ ```
99
+
100
+ ### `port`
101
+
102
+ Port TCP sur lequel le broker écoute.
103
+
104
+ - `3001` signifie que les clients utilisent par exemple
105
+ `https://nom-du-serveur:3001`.
106
+ - La variable d'environnement `MCP_BROKER_PORT` peut remplacer cette valeur.
107
+
108
+ ### `host`
109
+
110
+ Interface réseau sur laquelle le broker accepte les connexions.
111
+
112
+ - `0.0.0.0` signifie toutes les interfaces réseau de la machine.
113
+ - Pour un développement strictement local, utilisez plutôt `127.0.0.1`.
114
+ - N'exposez jamais `0.0.0.0` sur un réseau non fiable sans TLS et
115
+ authentification.
116
+
117
+ ### `locale`
118
+
119
+ Langue utilisée pour les descriptions du provider interne `_broker`.
120
+
121
+ - `fr` sélectionne le français.
122
+ - Cette valeur ne change pas les noms des capacités ni les chemins.
123
+
124
+ ### `brokerName`
125
+
126
+ Nom logique affiché par les outils d'introspection du broker.
127
+
128
+ - Il aide à distinguer plusieurs brokers.
129
+ - Il n'a aucun effet sur l'autorisation.
130
+
131
+ ## Lignes 7 à 11 : chemins HTTP et WebSocket
132
+
133
+ ```json
134
+ "paths": {
135
+ "provider": "/provider",
136
+ "client": "/",
137
+ "mcp": "/mcp"
138
+ }
139
+ ```
140
+
141
+ ### `paths.provider`
142
+
143
+ Préfixe WebSocket utilisé par un fournisseur qui se connecte au broker.
144
+
145
+ Exemple :
146
+
147
+ ```text
148
+ wss://mcp.factory.local/provider/spoony-00452
149
+ ```
150
+
151
+ Le fournisseur demande ici le slot `spoony-00452`.
152
+
153
+ ### `paths.client`
154
+
155
+ Préfixe utilisé par les clients WebSocket MCP. La valeur `/` conserve les URL
156
+ historiques :
157
+
158
+ ```text
159
+ wss://mcp.factory.local/spoony-00452
160
+ ```
161
+
162
+ ### `paths.mcp`
163
+
164
+ Suffixe du transport MCP Streamable HTTP.
165
+
166
+ Avec le slot `spoony-00452`, l'URL devient :
167
+
168
+ ```text
169
+ https://mcp.factory.local/spoony-00452/mcp
170
+ ```
171
+
172
+ Les chemins `providers`, `sse` et `messages` ne sont pas redéfinis dans cet
173
+ exemple. Le broker utilise donc leurs valeurs par défaut.
174
+
175
+ ## Lignes 13 à 16 : TLS
176
+
177
+ ```json
178
+ "tls": {
179
+ "cert": "certs/cert.pem",
180
+ "key": "certs/key.pem"
181
+ }
182
+ ```
183
+
184
+ TLS chiffre les échanges réseau et active HTTPS/WSS.
185
+
186
+ ### `tls.cert`
187
+
188
+ Chemin du certificat public au format PEM.
189
+
190
+ Dans cet exemple, le broker cherche :
191
+
192
+ ```text
193
+ .mcp-broker/certs/cert.pem
194
+ ```
195
+
196
+ ### `tls.key`
197
+
198
+ Chemin de la clé privée associée au certificat.
199
+
200
+ Cette clé est secrète. Elle ne doit jamais être ajoutée au dépôt Git.
201
+
202
+ Le broker doit pouvoir lire les deux fichiers. Une paire certificat et clé
203
+ incorrecte empêche le démarrage en HTTPS.
204
+
205
+ ## Lignes 18 à 23 : fichiers web statiques
206
+
207
+ ```json
208
+ "www": {
209
+ "open": false,
210
+ "mounts": [
211
+ { "urlPrefix": "/", "dir": "www" }
212
+ ]
213
+ }
214
+ ```
215
+
216
+ ### `www.open`
217
+
218
+ Indique si le broker doit ouvrir automatiquement le navigateur.
219
+
220
+ - `false` convient aux serveurs, conteneurs et environnements headless.
221
+ - `true` est pratique en développement local.
222
+
223
+ ### `www.mounts`
224
+
225
+ Liste des dossiers statiques servis par le broker.
226
+
227
+ ### `urlPrefix`
228
+
229
+ Préfixe URL associé au dossier. Ici `/` correspond à la racine du site.
230
+
231
+ ### `dir`
232
+
233
+ Dossier local contenant les fichiers web. Ici `www` correspond à :
234
+
235
+ ```text
236
+ .mcp-broker/www/
237
+ ```
238
+
239
+ Ce bloc ne protège pas automatiquement une interface web. Les routes MCP sont
240
+ protégées par `auth`, mais une application web statique doit aussi être conçue
241
+ pour ne pas exposer de secret.
242
+
243
+ ## Lignes 25 à 35 : activation OAuth
244
+
245
+ ```json
246
+ "auth": {
247
+ "enabled": true,
248
+ "publicBaseUrl": "https://mcp.factory.local",
249
+ "authorizationServers": [
250
+ "https://identity.factory.local"
251
+ ],
252
+ "jwks": "https://identity.factory.local/.well-known/jwks.json",
253
+ "requiredScopes": ["mcp:call"],
254
+ "perSlotScopes": {
255
+ "_broker": ["broker:admin"]
256
+ }
257
+ }
258
+ ```
259
+
260
+ ### `auth.enabled`
261
+
262
+ Active l'authentification OAuth des clients.
263
+
264
+ - `true` exige un bearer token valide.
265
+ - `false` conserve le mode historique sans authentification.
266
+ - Une politique détaillée n'est utile que si les clients possèdent une
267
+ identité authentifiée.
268
+
269
+ ### `auth.publicBaseUrl`
270
+
271
+ Adresse publique utilisée par les clients pour joindre le broker.
272
+
273
+ Cette valeur doit correspondre à l'adresse réellement visible par les clients,
274
+ pas forcément à l'adresse interne du processus.
275
+
276
+ Elle sert aussi à calculer l'audience attendue du JWT. Pour le slot
277
+ `spoony-00452`, l'audience attendue est :
278
+
279
+ ```text
280
+ https://mcp.factory.local/spoony-00452/mcp
281
+ ```
282
+
283
+ Une erreur fréquente consiste à mettre `http://localhost:3001` alors que les
284
+ clients utilisent un reverse proxy public en HTTPS.
285
+
286
+ ### `auth.authorizationServers`
287
+
288
+ Liste des serveurs d'autorisation externes annoncés aux clients.
289
+
290
+ Dans cet exemple, `https://identity.factory.local` :
291
+
292
+ - authentifie les utilisateurs ou applications ;
293
+ - émet les access tokens ;
294
+ - reste extérieur au broker.
295
+
296
+ Le broker ne devient pas un fournisseur d'identité.
297
+
298
+ ### `auth.jwks`
299
+
300
+ URL du document JWKS du serveur d'autorisation.
301
+
302
+ Le broker télécharge les clés publiques de ce document pour vérifier la
303
+ signature des JWT. Une clé publique permet de vérifier un jeton, mais pas d'en
304
+ créer un.
305
+
306
+ N'utilisez pas ici une clé privée ou un secret client.
307
+
308
+ ### `auth.requiredScopes`
309
+
310
+ Scopes OAuth exigés par défaut pour atteindre un slot.
311
+
312
+ ```json
313
+ ["mcp:call"]
314
+ ```
315
+
316
+ signifie que le JWT doit contenir le scope `mcp:call`.
317
+
318
+ Ce scope ne suffit pas à lui seul lorsque la politique hiérarchique est active.
319
+ Il ouvre seulement la première barrière. Les rôles, ressources et denies sont
320
+ ensuite évalués.
321
+
322
+ ### `auth.perSlotScopes`
323
+
324
+ Remplace `requiredScopes` pour certains slots.
325
+
326
+ ```json
327
+ "_broker": ["broker:admin"]
328
+ ```
329
+
330
+ signifie que le slot interne `_broker` exige `broker:admin` à la place de
331
+ `mcp:call`.
332
+
333
+ Cette règle protège l'accès réseau à `_broker`. La politique hiérarchique
334
+ applique ensuite la capacité `broker.providers.read` sur la ressource réservée
335
+ `/_system/broker`.
336
+
337
+ Le fichier d'exemple ne contient volontairement aucune affectation sur
338
+ `/_system/broker`. Par défaut, personne ne peut donc utiliser les outils de
339
+ `_broker`, même avec le scope `broker:admin`.
340
+
341
+ Pour accorder cet accès, ajoutez par exemple :
342
+
343
+ ```json
344
+ {
345
+ "id": "broker-administrators",
346
+ "subject": "group:broker-administrators",
347
+ "role": "administrator",
348
+ "resource": "/_system/broker"
349
+ }
350
+ ```
351
+
352
+ Le JWT devra alors posséder à la fois le scope `broker:admin` et le groupe
353
+ `broker-administrators`.
354
+
355
+ ## Lignes 36 à 40 : claims JWT transformés en sujets
356
+
357
+ ```json
358
+ "subjectMapping": {
359
+ "userClaim": "sub",
360
+ "groupClaims": ["groups"],
361
+ "clientClaim": "client_id"
362
+ }
363
+ ```
364
+
365
+ Le broker ne fait confiance qu'aux claims d'un JWT déjà validé.
366
+
367
+ ### `userClaim`
368
+
369
+ Nom du claim contenant l'identifiant utilisateur.
370
+
371
+ Avec :
372
+
373
+ ```json
374
+ { "sub": "alice" }
375
+ ```
376
+
377
+ le broker produit le sujet :
378
+
379
+ ```text
380
+ user:alice
381
+ ```
382
+
383
+ ### `groupClaims`
384
+
385
+ Claims contenant les groupes de l'utilisateur.
386
+
387
+ Avec :
388
+
389
+ ```json
390
+ { "groups": ["maintenance-area-a", "employees"] }
391
+ ```
392
+
393
+ le broker produit :
394
+
395
+ ```text
396
+ group:maintenance-area-a
397
+ group:employees
398
+ ```
399
+
400
+ Le claim peut être une chaîne unique ou une liste de chaînes. Un type incorrect
401
+ fait échouer l'autorisation de manière sûre.
402
+
403
+ ### `clientClaim`
404
+
405
+ Claim contenant l'identifiant de l'application cliente.
406
+
407
+ Avec :
408
+
409
+ ```json
410
+ { "client_id": "local-ai-assistant" }
411
+ ```
412
+
413
+ le broker produit :
414
+
415
+ ```text
416
+ client:local-ai-assistant
417
+ ```
418
+
419
+ Un même appel peut donc posséder plusieurs identités en même temps, par exemple
420
+ un utilisateur, deux groupes et une application cliente.
421
+
422
+ ## Lignes 41 à 64 : rôles et capacités
423
+
424
+ Un rôle répond uniquement à la question « que peut-on faire ? ». Il ne contient
425
+ jamais de chemin de ressource.
426
+
427
+ ### Rôle `viewer`
428
+
429
+ ```json
430
+ "viewer": {
431
+ "capabilities": [
432
+ "mcp.resources.read",
433
+ "mcp.tools.list",
434
+ "mcp.prompts.read"
435
+ ]
436
+ }
437
+ ```
438
+
439
+ Ce rôle permet :
440
+
441
+ - `mcp.resources.read` : lister et lire les ressources MCP ;
442
+ - `mcp.tools.list` : voir le catalogue des outils ;
443
+ - `mcp.prompts.read` : lister et lire les prompts.
444
+
445
+ Il ne permet pas d'appeler un outil.
446
+
447
+ ### Rôle `maintenance`
448
+
449
+ ```json
450
+ "maintenance": {
451
+ "inherits": ["viewer"],
452
+ "capabilities": [
453
+ "mcp.tools.call",
454
+ "mcp.tools.diagnose",
455
+ "mcp.tools.configure-analysis"
456
+ ]
457
+ }
458
+ ```
459
+
460
+ `inherits: ["viewer"]` signifie que `maintenance` récupère aussi toutes les
461
+ capacités de `viewer`.
462
+
463
+ Ses capacités supplémentaires sont :
464
+
465
+ - `mcp.tools.call` : appeler un outil sans mapping plus précis ;
466
+ - `mcp.tools.diagnose` : exécuter un diagnostic ;
467
+ - `mcp.tools.configure-analysis` : modifier une configuration d'analyse.
468
+
469
+ ### Rôle `operator`
470
+
471
+ ```json
472
+ "operator": {
473
+ "inherits": ["viewer"],
474
+ "capabilities": ["mcp.tools.operate"]
475
+ }
476
+ ```
477
+
478
+ Ce rôle voit les ressources, outils et prompts grâce à `viewer`, puis peut
479
+ effectuer des opérations classées `mcp.tools.operate`.
480
+
481
+ ### Rôle `administrator`
482
+
483
+ ```json
484
+ "administrator": {
485
+ "capabilities": ["*"]
486
+ }
487
+ ```
488
+
489
+ `*` signifie toutes les capacités, mais uniquement sur les ressources couvertes
490
+ par une affectation.
491
+
492
+ Déclarer un rôle ne l'accorde à personne. Dans le fichier d'exemple, aucune
493
+ affectation n'utilise `administrator`. Personne n'est donc administrateur par
494
+ ce seul bloc.
495
+
496
+ ## Lignes 65 à 78 : affectations
497
+
498
+ Une affectation répond à la phrase :
499
+
500
+ ```text
501
+ Ce sujet reçoit ce rôle sur cette ressource.
502
+ ```
503
+
504
+ ### Affectation `maintenance-area-a`
505
+
506
+ ```json
507
+ {
508
+ "id": "maintenance-area-a",
509
+ "subject": "group:maintenance-area-a",
510
+ "role": "maintenance",
511
+ "resource": "/enterprise-a/site-paris/area-a/**"
512
+ }
513
+ ```
514
+
515
+ #### `id`
516
+
517
+ Identifiant unique utilisé dans les validations et journaux d'audit.
518
+
519
+ #### `subject`
520
+
521
+ Sujet auquel le rôle est accordé. Ici, tous les JWT contenant le groupe
522
+ `maintenance-area-a`.
523
+
524
+ #### `role`
525
+
526
+ Nom exact d'un rôle déclaré dans le bloc `roles`.
527
+
528
+ #### `resource`
529
+
530
+ Sous-arbre industriel sur lequel le rôle est valable.
531
+
532
+ Le suffixe `/**` signifie :
533
+
534
+ - la ressource `/enterprise-a/site-paris/area-a` elle-même ;
535
+ - tous ses descendants, quel que soit leur nombre de niveaux.
536
+
537
+ Un nouveau fournisseur ajouté plus tard sous cette zone est automatiquement
538
+ couvert par l'affectation.
539
+
540
+ ### Affectation `energy-team`
541
+
542
+ ```json
543
+ {
544
+ "id": "energy-team",
545
+ "subject": "group:energy-team",
546
+ "role": "viewer",
547
+ "resource": "/enterprise-a/site-paris/**"
548
+ }
549
+ ```
550
+
551
+ Le groupe `energy-team` peut voir les ressources, outils et prompts de tout le
552
+ site Paris, sans pouvoir appeler les outils.
553
+
554
+ ### Signification des wildcards
555
+
556
+ | Forme | Signification |
557
+ |---|---|
558
+ | `/enterprise/site/asset` | Ce chemin exact uniquement |
559
+ | `/enterprise/site/*` | Un seul niveau directement sous le site |
560
+ | `/enterprise/site/**` | Le site et tous ses descendants |
561
+
562
+ Les expressions régulières ne sont pas acceptées.
563
+
564
+ ## Lignes 79 à 89 : interdiction explicite
565
+
566
+ ```json
567
+ "denies": [
568
+ {
569
+ "id": "protect-critical-furnace",
570
+ "subject": "group:maintenance-area-a",
571
+ "capabilities": [
572
+ "mcp.tools.configure-analysis",
573
+ "mcp.tools.operate"
574
+ ],
575
+ "resource": "/enterprise-a/site-paris/area-a/line-2/cell-4/critical-furnace"
576
+ }
577
+ ]
578
+ ```
579
+
580
+ Cette règle interdit au groupe de maintenance :
581
+
582
+ - de modifier la configuration d'analyse ;
583
+ - d'exécuter une opération ;
584
+ - uniquement sur le four critique indiqué.
585
+
586
+ Le groupe conserve ses autres permissions sur le reste de `area-a`.
587
+
588
+ Un deny correspondant est toujours prioritaire sur une affectation allow,
589
+ quelle que soit la position des règles dans le fichier.
590
+
591
+ Utilisez `"capabilities": ["*"]` pour interdire toute capacité sur une
592
+ ressource précise.
593
+
594
+ ## Lignes 90 à 93 : noms techniques et ressources stables
595
+
596
+ ```json
597
+ "slotResources": {
598
+ "spoony-00452": "/enterprise-a/site-paris/area-a/line-3/cell-2/motor-7",
599
+ "site-energy": "/enterprise-a/site-paris"
600
+ }
601
+ ```
602
+
603
+ La clé de gauche est le nom technique du slot. La valeur de droite est son
604
+ identité stable dans la hiérarchie.
605
+
606
+ ### `spoony-00452`
607
+
608
+ Un client utilise le slot technique :
609
+
610
+ ```text
611
+ /spoony-00452/mcp
612
+ ```
613
+
614
+ mais le moteur de politique l'évalue comme :
615
+
616
+ ```text
617
+ /enterprise-a/site-paris/area-a/line-3/cell-2/motor-7
618
+ ```
619
+
620
+ Le fournisseur peut se reconnecter ou changer d'adresse IP sans changer cette
621
+ identité.
622
+
623
+ ### `site-energy`
624
+
625
+ Ce slot représente le site Paris lui-même. Une ressource n'est pas obligée
626
+ d'être une feuille comme un moteur.
627
+
628
+ Un slot non déclaré est normalement converti en `/<nom-du-slot>`. Pour un
629
+ environnement industriel, il est préférable de déclarer explicitement les
630
+ mappings afin de conserver des identités stables.
631
+
632
+ ## Lignes 94 à 99 : classification globale des outils
633
+
634
+ ```json
635
+ "toolCapabilities": {
636
+ "get_electrical_state": "mcp.resources.read",
637
+ "diagnose_motor": "mcp.tools.diagnose",
638
+ "reset_baseline": "mcp.tools.configure-analysis",
639
+ "start_motor": "mcp.tools.operate"
640
+ }
641
+ ```
642
+
643
+ Le broker ne devine jamais une permission à partir du nom d'un outil. Ce bloc
644
+ associe explicitement chaque outil à une capacité.
645
+
646
+ | Outil | Capacité exigée |
647
+ |---|---|
648
+ | `get_electrical_state` | Lecture de ressource |
649
+ | `diagnose_motor` | Diagnostic |
650
+ | `reset_baseline` | Modification de la configuration d'analyse |
651
+ | `start_motor` | Opération sur l'équipement |
652
+
653
+ Si un outil n'est présent dans aucun mapping, le broker utilise la capacité
654
+ générique `mcp.tools.call`.
655
+
656
+ Cette valeur par défaut explique pourquoi le rôle `maintenance` contient aussi
657
+ `mcp.tools.call`.
658
+
659
+ ## Lignes 100 à 104 : classification spécifique à une zone
660
+
661
+ ```json
662
+ "providerToolCapabilities": {
663
+ "/enterprise-a/site-paris/area-a/**": {
664
+ "start_motor": "mcp.tools.operate"
665
+ }
666
+ }
667
+ ```
668
+
669
+ Ce bloc permet de changer la classification d'un outil pour une ressource ou un
670
+ sous-arbre précis.
671
+
672
+ Ordre de résolution :
673
+
674
+ 1. mapping spécifique à la ressource dans `providerToolCapabilities` ;
675
+ 2. mapping global dans `toolCapabilities` ;
676
+ 3. capacité générique `mcp.tools.call`.
677
+
678
+ Dans cet exemple, la valeur spécifique de `start_motor` est identique à la
679
+ valeur globale. Cette redondance est volontairement pédagogique. Dans un vrai
680
+ déploiement, ce bloc est surtout utile si le même nom d'outil n'a pas le même
681
+ niveau de risque selon le fournisseur ou la zone.
682
+
683
+ ## Lignes 105 à 107 : audit
684
+
685
+ ```json
686
+ "audit": {
687
+ "logAllowed": false
688
+ }
689
+ ```
690
+
691
+ Les refus sont toujours journalisés.
692
+
693
+ `logAllowed: false` signifie que les décisions autorisées ne sont pas
694
+ journalisées. C'est la valeur recommandée pour éviter un volume de logs trop
695
+ important.
696
+
697
+ Passez temporairement à `true` pour comprendre une politique ou diagnostiquer
698
+ un problème. Les journaux contiennent la décision et les identifiants de
699
+ politique, jamais le bearer token ni le secret fournisseur.
700
+
701
+ ## Ligne 108 : secret partagé des fournisseurs
702
+
703
+ ```json
704
+ "providerSecret": "change-me"
705
+ ```
706
+
707
+ Ce secret authentifie les serveurs MCP qui se connectent à `/provider/<slot>`
708
+ ou `/providers`.
709
+
710
+ Il est indépendant des bearer tokens des clients.
711
+
712
+ La valeur `change-me` est uniquement un placeholder. En production :
713
+
714
+ - générez une valeur longue et aléatoire ;
715
+ - fournissez-la de préférence avec
716
+ `MCP_BROKER_PROVIDER_SECRET` ;
717
+ - ne la placez pas dans Git ;
718
+ - ne la partagez pas avec les clients MCP.
719
+
720
+ Le secret partagé conserve la compatibilité historique et permet tous les
721
+ chemins de ressources. Pour limiter chaque appareil à son propre sous-arbre,
722
+ utilisez un `IProviderAuthenticator` personnalisé qui renvoie un
723
+ `IProviderPrincipal.allowedResources`.
724
+
725
+ ## Lignes 111 à 117 : serveur MCP local lancé par le broker
726
+
727
+ ```json
728
+ "stdioUpstreams": [
729
+ {
730
+ "name": "fs",
731
+ "command": "npx",
732
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"]
733
+ }
734
+ ]
735
+ ```
736
+
737
+ ### `name`
738
+
739
+ Nom du slot exposé par le broker. Le client utilise :
740
+
741
+ ```text
742
+ /fs/mcp
743
+ ```
744
+
745
+ ### `command`
746
+
747
+ Programme lancé par le broker. Ici, `npx`.
748
+
749
+ ### `args`
750
+
751
+ Arguments transmis au programme :
752
+
753
+ - `-y` accepte automatiquement l'installation demandée par `npx` ;
754
+ - `@modelcontextprotocol/server-filesystem` est le paquet lancé ;
755
+ - `/data` est le dossier accessible au serveur.
756
+
757
+ Accorder un accès au filesystem est sensible. Limitez `/data` au strict
758
+ nécessaire.
759
+
760
+ Ajoutez `"aggregate": true` si ce provider doit aussi apparaître dans `_all`.
761
+ Sans cette propriété, cet upstream stdio reste accessible par son slot direct.
762
+
763
+ ## Lignes 119 à 128 : bundle MCP local signé
764
+
765
+ ```json
766
+ "mcpbBundles": [
767
+ {
768
+ "name": "weather",
769
+ "path": "bundles/weather.mcpb",
770
+ "publicKey": "bundles/mcpb-signing.pub.pem",
771
+ "signature": "bundles/weather.mcpb.sig",
772
+ "userConfig": { "apiKey": "your-key-here" },
773
+ "aggregate": true
774
+ }
775
+ ]
776
+ ```
777
+
778
+ ### `name`
779
+
780
+ Nom du slot exposé, ici `weather`.
781
+
782
+ ### `path`
783
+
784
+ Chemin du bundle `.mcpb`.
785
+
786
+ ### `publicKey`
787
+
788
+ Clé publique utilisée pour vérifier que le bundle a été signé par une source
789
+ de confiance.
790
+
791
+ ### `signature`
792
+
793
+ Fichier de signature détachée correspondant au bundle.
794
+
795
+ Le broker refuse de lancer le bundle si la signature est absente ou invalide.
796
+
797
+ ### `userConfig`
798
+
799
+ Valeurs injectées dans la configuration déclarée par le bundle.
800
+
801
+ `apiKey` est un secret d'exemple. Ne conservez pas une vraie clé API dans une
802
+ version publique ou partagée de ce fichier.
803
+
804
+ ### `aggregate`
805
+
806
+ `true` ajoute le provider `weather` au slot agrégé `_all`.
807
+
808
+ Même dans `_all`, la visibilité et les appels restent filtrés par la politique
809
+ d'autorisation.
810
+
811
+ ## Exemple de décision complète
812
+
813
+ Supposons un JWT validé contenant :
814
+
815
+ ```json
816
+ {
817
+ "sub": "alice",
818
+ "groups": ["maintenance-area-a"],
819
+ "client_id": "local-ai-assistant",
820
+ "scope": "mcp:call"
821
+ }
822
+ ```
823
+
824
+ Alice appelle :
825
+
826
+ ```text
827
+ outil : diagnose_motor
828
+ slot : spoony-00452
829
+ ```
830
+
831
+ Le broker calcule :
832
+
833
+ 1. Le scope `mcp:call` satisfait la barrière OAuth.
834
+ 2. Le claim `groups` produit `group:maintenance-area-a`.
835
+ 3. `diagnose_motor` produit la capacité `mcp.tools.diagnose`.
836
+ 4. `spoony-00452` produit la ressource
837
+ `/enterprise-a/site-paris/area-a/line-3/cell-2/motor-7`.
838
+ 5. L'affectation `maintenance-area-a` correspond au sujet et à la ressource.
839
+ 6. Le rôle `maintenance` contient `mcp.tools.diagnose`.
840
+ 7. Aucun deny ne correspond à ce moteur.
841
+ 8. La décision finale est allow.
842
+
843
+ Si Alice tente `start_motor` sur le four critique :
844
+
845
+ 1. `start_motor` produit `mcp.tools.operate`.
846
+ 2. Le deny `protect-critical-furnace` correspond à la ressource.
847
+ 3. Le deny est prioritaire.
848
+ 4. La décision finale est deny.
849
+
850
+ ## Checklist avant un déploiement
851
+
852
+ - Remplacer tous les domaines `.local` par les adresses réelles.
853
+ - Vérifier que `publicBaseUrl` est exactement l'adresse publique du broker.
854
+ - Vérifier que les JWT utilisent cette ressource dans leur audience.
855
+ - Vérifier l'URL JWKS et l'émetteur attendu.
856
+ - Ne jamais conserver `change-me`.
857
+ - Ne jamais publier la clé TLS privée.
858
+ - Ne jamais publier les clés API de `userConfig`.
859
+ - Utiliser `127.0.0.1` au lieu de `0.0.0.0` si aucun accès réseau n'est requis.
860
+ - Tester chaque rôle avec un compte représentatif.
861
+ - Tester les denies sur les actifs critiques.
862
+ - Vérifier que `_all` ne révèle pas les providers non autorisés.
863
+ - Laisser `audit.logAllowed` à `false` après le diagnostic.
864
+ - Redémarrer le broker après toute modification, car les politiques sont
865
+ chargées une seule fois au démarrage.
866
+
867
+ ## Pour aller plus loin
868
+
869
+ - [Référence complète de configuration](../docs/config.md)
870
+ - [Guide OAuth du broker](../../docs/authorization.md)
871
+ - [Autorisation hiérarchique](../../docs/hierarchical-authorization.md)