@cyanmycelium/mcp-broker 1.2.1 → 1.3.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 (49) hide show
  1. package/.mcp-broker.example/CONFIGURATION-EN.md +300 -44
  2. package/.mcp-broker.example/CONFIGURATION-FR.md +313 -44
  3. package/.mcp-broker.example/README.md +73 -2
  4. package/.mcp-broker.example/config.json +18 -9
  5. package/.mcp-broker.example/config.stdio-bridge.json +16 -0
  6. package/README.md +407 -27
  7. package/dist/bin.js +170 -23
  8. package/dist/bin.js.map +1 -1
  9. package/dist/chunk-UOJRDF5R.js +5980 -0
  10. package/dist/chunk-UOJRDF5R.js.map +1 -0
  11. package/dist/grammars/claude/en.json +12 -0
  12. package/dist/grammars/claude/fr.json +12 -0
  13. package/dist/grammars/default/en.json +40 -0
  14. package/dist/grammars/default/fr.json +40 -0
  15. package/dist/grammars/default/zh.json +40 -0
  16. package/dist/index.d.ts +1002 -25
  17. package/dist/index.js +1 -1
  18. package/package.json +3 -3
  19. package/src/auth/index.ts +3 -1
  20. package/src/auth/provider.auth.ts +126 -8
  21. package/src/authorization/policy.engine.ts +11 -2
  22. package/src/authorization/policy.types.ts +25 -1
  23. package/src/bin.ts +280 -31
  24. package/src/broker/adapters/broker.adapter.diagnose.ts +45 -0
  25. package/src/broker/adapters/broker.adapter.guide.ts +108 -0
  26. package/src/broker/aggregate/aggregate.server.ts +92 -15
  27. package/src/broker/aggregate/provider.client.session.ts +85 -11
  28. package/src/broker/behaviors/broker.behavior.diagnose.ts +47 -0
  29. package/src/broker/behaviors/broker.behavior.guide.ts +79 -0
  30. package/src/broker/broker.context.ts +65 -0
  31. package/src/broker/broker.diagnostics.ts +495 -0
  32. package/src/broker/broker.guides.ts +1029 -0
  33. package/src/broker/broker.server.ts +23 -7
  34. package/src/broker/broker.slots.ts +36 -0
  35. package/src/broker/grammars/claude/en.json +12 -0
  36. package/src/broker/grammars/claude/fr.json +12 -0
  37. package/src/broker/grammars/default/en.json +40 -0
  38. package/src/broker/grammars/default/fr.json +40 -0
  39. package/src/broker/grammars/default/zh.json +40 -0
  40. package/src/config.ts +191 -4
  41. package/src/index.ts +38 -3
  42. package/src/remote.transports.ts +127 -10
  43. package/src/remote.upstream.ts +4 -1
  44. package/src/ws/ws.interfaces.ts +148 -3
  45. package/src/ws/ws.tunnel.builder.ts +63 -1
  46. package/src/ws/ws.tunnel.ts +1167 -173
  47. package/web/README.md +31 -4
  48. package/dist/chunk-FTDKH2C4.js +0 -3670
  49. package/dist/chunk-FTDKH2C4.js.map +0 -1
@@ -22,15 +22,25 @@ Basic reading rules:
22
22
  - Spaces used to align values do not change behavior.
23
23
  - Relative file paths are resolved from the `.mcp-broker/` directory.
24
24
 
25
+ Each section below is named after the key it explains, not after a line number,
26
+ so the guide stays correct when the example file grows.
27
+
28
+ One thing to know before anything else: **the template ships with
29
+ `auth.enabled: false`**. A fresh copy starts and answers every client, which is
30
+ what you want while you are finding your way. The whole `auth` block is present
31
+ as the reference for the day you turn it on. Read
32
+ [`docs/authorization.md`](../../../../docs/authorization.md) before you do.
33
+
25
34
  ## The mental model
26
35
 
27
- The file answers five questions:
36
+ The file answers six questions:
28
37
 
29
38
  1. Where does the broker listen?
30
39
  2. How are network connections encrypted?
31
- 3. How does the broker identify clients and providers?
32
- 4. What may each client do, and on which resources?
33
- 5. Which local or packaged MCP servers should be loaded?
40
+ 3. Which browser pages, if any, may call it?
41
+ 4. How does the broker identify clients and providers?
42
+ 5. What may each client do, and on which resources?
43
+ 6. Which local or packaged MCP servers should be loaded?
34
44
 
35
45
  The `auth` block is the most security-sensitive part. Read it this way:
36
46
 
@@ -84,7 +94,7 @@ This separation is important:
84
94
  - Resources describe where it is allowed.
85
95
  - Subjects describe who receives the permission.
86
96
 
87
- ## Lines 1 to 5: general settings
97
+ ## General settings
88
98
 
89
99
  ```json
90
100
  {
@@ -125,17 +135,69 @@ Logical name displayed by the broker introspection tools.
125
135
 
126
136
  - It helps distinguish multiple broker instances.
127
137
  - It has no effect on authorization.
138
+ - **Library-only today.** The tunnel honors it, but the command-line broker has
139
+ no way to forward it yet, so setting it here changes nothing. Set it through
140
+ the programmatic API (`IWsTunnelOptions.brokerName`) if you need it now.
141
+
142
+ ## Browser origins
143
+
144
+ ```json
145
+ "allowedOrigins": ["https://app.factory.local", "https://mcp.factory.local"]
146
+ ```
147
+
148
+ The list of web-page origins allowed to call this broker over HTTP. It is
149
+ checked on `/<slot>/mcp`, `/<slot>/sse` and `/<slot>/messages`.
150
+
151
+ Three rules worth memorizing, because each one surprises somebody:
152
+
153
+ 1. **Absent means closed.** With no `allowedOrigins`, every request carrying an
154
+ `Origin` header is refused with `403`. That is deliberate: without the check,
155
+ any page the user happens to have open could drive your broker.
156
+ 2. **A request with no `Origin` header always passes.** Claude Desktop, the MCP
157
+ Inspector and every server-side SDK send none, which is why the broker
158
+ appears to work perfectly until the first browser tries.
159
+ 3. **Being served by this broker exempts nothing.** A page loaded from the `www`
160
+ mount is still a browser origin and must be listed.
161
+
162
+ Origins are compared verbatim, so the scheme and the port are part of the value:
163
+ `https://app.factory.local` does not match `http://app.factory.local` or
164
+ `https://app.factory.local:8443`, and a trailing slash never matches. This
165
+ template sets `tls.cert`/`tls.key`, so the broker speaks HTTPS and the entries
166
+ use `https://`. Drop the TLS block and they must become `http://...:3001`.
128
167
 
129
- ## Lines 7 to 11: HTTP and WebSocket paths
168
+ A regular expression is accepted instead of a list when the origins are not
169
+ known in advance:
170
+
171
+ ```json
172
+ "allowedOrigins": { "pattern": "^https://[a-z0-9-]+\\.factory\\.local$" }
173
+ ```
174
+
175
+ `MCP_BROKER_ALLOWED_ORIGINS` overrides the file with a comma-separated list. It
176
+ cannot carry the pattern form, since a regular expression does not survive
177
+ comma-splitting.
178
+
179
+ ## HTTP and WebSocket paths
130
180
 
131
181
  ```json
132
182
  "paths": {
133
- "provider": "/provider",
134
- "client": "/",
135
- "mcp": "/mcp"
183
+ "provider": "/provider",
184
+ "providers": "/providers",
185
+ "client": "/",
186
+ "mcp": "/mcp",
187
+ "sse": "/sse",
188
+ "messages": "/messages"
136
189
  }
137
190
  ```
138
191
 
192
+ All six keys are honored, and each is also settable through an environment
193
+ variable, which wins: `MCP_BROKER_PROVIDER_PATH`, `MCP_BROKER_PROVIDERS_PATH`,
194
+ `MCP_BROKER_CLIENT_PATH`, `MCP_BROKER_MCP_PATH`, `MCP_BROKER_SSE_PATH`,
195
+ `MCP_BROKER_MESSAGES_PATH`.
196
+
197
+ Change one and you move the endpoint for everybody: the provider SDK, the
198
+ clients, and the URLs printed in the startup banner. Leave them alone unless you
199
+ have a reason.
200
+
139
201
  ### `paths.provider`
140
202
 
141
203
  WebSocket prefix used by a provider connecting to the broker.
@@ -146,7 +208,34 @@ Example:
146
208
  wss://mcp.factory.local/provider/spoony-00452
147
209
  ```
148
210
 
149
- The provider requests the `spoony-00452` slot.
211
+ The provider requests the `spoony-00452` slot. The socket carries plain
212
+ JSON-RPC frames, one provider per socket. In
213
+ `@cyanmycelium/mcp-broker-provider` this is `DirectTransport`.
214
+
215
+ ### `paths.providers`
216
+
217
+ Exact WebSocket path (no slot appended) used by a provider that carries several
218
+ slots over a single socket, wrapping each frame in an envelope that names the
219
+ slot. In `@cyanmycelium/mcp-broker-provider` this is `MultiplexTransport`.
220
+
221
+ ```text
222
+ wss://mcp.factory.local/providers
223
+ ```
224
+
225
+ `paths.provider` and `paths.providers` are one letter apart and are **not
226
+ interchangeable**: they differ by framing, not just by URL. Pointing a
227
+ `MultiplexTransport` at `/provider/<name>`, or a `DirectTransport` at
228
+ `/providers`, is the single most common integration failure. The broker now
229
+ names the mismatch and refuses the socket rather than hanging, but the pairing
230
+ is worth getting right the first time:
231
+
232
+ | Endpoint | Framing | Transport |
233
+ |---------------------|--------------------|----------------------|
234
+ | `/provider/<name>` | plain JSON-RPC | `DirectTransport` |
235
+ | `/providers` | envelopes | `MultiplexTransport` |
236
+
237
+ `/providers/<name>` is neither, and is taken as a *client* connection on a slot
238
+ literally named `providers/<name>`.
150
239
 
151
240
  ### `paths.client`
152
241
 
@@ -167,10 +256,84 @@ For the `spoony-00452` slot, the URL becomes:
167
256
  https://mcp.factory.local/spoony-00452/mcp
168
257
  ```
169
258
 
170
- The `providers`, `sse`, and `messages` paths are not overridden in this
171
- example, so the broker uses their default values.
259
+ This is the transport to reach for. The two below it are the legacy pair.
260
+
261
+ ### `paths.sse`
262
+
263
+ Suffix of the legacy SSE stream, opened with `GET`. The broker answers it with
264
+ an `endpoint` event carrying the URL to post to.
265
+
266
+ ```text
267
+ https://mcp.factory.local/spoony-00452/sse
268
+ ```
172
269
 
173
- ## Lines 13 to 16: TLS
270
+ ### `paths.messages`
271
+
272
+ Suffix the legacy SSE client `POST`s its JSON-RPC requests to, paired with the
273
+ stream above.
274
+
275
+ ```text
276
+ https://mcp.factory.local/spoony-00452/messages
277
+ ```
278
+
279
+ Both legacy endpoints are subject to the same `allowedOrigins` check as
280
+ `/<slot>/mcp`.
281
+
282
+ ## Provider liveness
283
+
284
+ ```json
285
+ "providerHeartbeatIntervalMs": 30000,
286
+ "providerRequestTimeoutMs": 60000,
287
+ "providerTakeover": "liveness"
288
+ ```
289
+
290
+ Three optional keys. The defaults shown are the built-in ones, so you can delete
291
+ the block entirely; they are spelled out here because when a provider misbehaves
292
+ these are what you tune.
293
+
294
+ ### `providerHeartbeatIntervalMs`
295
+
296
+ How often the broker pings each connected provider socket. A provider that
297
+ misses a full interval is disconnected and its slot freed. `0` disables the
298
+ heartbeat. Also `MCP_BROKER_PROVIDER_HEARTBEAT_MS`.
299
+
300
+ Without it, a socket whose peer vanished without closing (a killed browser tab,
301
+ a slept laptop, a dropped VPN) stays open as far as the operating system is
302
+ concerned for around two hours, during which the broker reports the slot as
303
+ connected and refuses every reconnection attempt.
304
+
305
+ Be honest about what it proves: a pong is answered by the peer's network stack,
306
+ not by the page's JavaScript. It detects a dead process, machine or network
307
+ path, not a provider that is connected and simply not answering. For that, see
308
+ the next key.
309
+
310
+ ### `providerRequestTimeoutMs`
311
+
312
+ How long the broker waits for a provider to answer one request before failing it
313
+ with a JSON-RPC error naming the slot. `0` disables the deadline. Also
314
+ `MCP_BROKER_PROVIDER_REQUEST_TIMEOUT_MS`.
315
+
316
+ Raise it if you host genuinely long-running tools. Lower it if you would rather
317
+ see an error than a client that waits forever, which is the alternative: a
318
+ browser tab throttled in the background is a connected provider that answers
319
+ nothing.
320
+
321
+ ### `providerTakeover`
322
+
323
+ What happens when a provider connects to a slot another socket already holds.
324
+
325
+ - `"reject"`: the incumbent always keeps the slot.
326
+ - `"liveness"` (default): the incumbent keeps it only while it answers the
327
+ heartbeat.
328
+ - `"always"`: the newcomer wins, but only when provider authentication is
329
+ configured and it authenticated as the same principal as the incumbent.
330
+ Without provider authentication the broker falls back to `"liveness"` and says
331
+ so, because unconditional takeover would let anyone who can reach the URL
332
+ evict the real provider.
333
+
334
+ Also `MCP_BROKER_PROVIDER_TAKEOVER`.
335
+
336
+ ## TLS
174
337
 
175
338
  ```json
176
339
  "tls": {
@@ -200,7 +363,7 @@ This key is secret. It must never be committed to the Git repository.
200
363
  The broker must be able to read both files. A mismatched certificate and key
201
364
  pair prevents HTTPS startup.
202
365
 
203
- ## Lines 18 to 23: static web files
366
+ ## Static web files
204
367
 
205
368
  ```json
206
369
  "www": {
@@ -213,10 +376,24 @@ pair prevents HTTPS startup.
213
376
 
214
377
  ### `www.open`
215
378
 
216
- Controls whether the broker automatically opens a web browser.
379
+ Controls whether the broker automatically opens a web browser at startup.
380
+
381
+ - `false` (or absent) is suitable for servers, containers, and headless
382
+ environments.
383
+ - `true` opens the broker root, `https://localhost:3001/`.
384
+ - A string opens a specific page: `"/app/index.html"`, or an absolute URL on
385
+ this broker's own origin.
386
+
387
+ A URL on any other origin is refused with a message on stderr, and so is any
388
+ other string: a value that reaches the platform's "open this" command unchecked
389
+ can launch a local file or a registered application, and nothing about starting
390
+ a broker requires visiting another host. Open it yourself instead.
217
391
 
218
- - `false` is suitable for servers, containers, and headless environments.
219
- - `true` is convenient during local development.
392
+ The browser opens only when a `www.mounts` entry actually covers the resolved
393
+ path. If none does, the broker says which prefixes are mounted rather than
394
+ launching a browser onto a `404`.
395
+
396
+ `MCP_BROKER_OPEN` carries the same values (`"1"` for the root).
220
397
 
221
398
  ### `www.mounts`
222
399
 
@@ -238,11 +415,11 @@ This block does not automatically secure a web application. MCP routes are
238
415
  protected by `auth`, but a static web application must also be designed not to
239
416
  expose secrets.
240
417
 
241
- ## Lines 25 to 35: enabling OAuth
418
+ ## Enabling OAuth
242
419
 
243
420
  ```json
244
421
  "auth": {
245
- "enabled": true,
422
+ "enabled": false,
246
423
  "publicBaseUrl": "https://mcp.factory.local",
247
424
  "authorizationServers": [
248
425
  "https://identity.factory.local"
@@ -257,13 +434,23 @@ expose secrets.
257
434
 
258
435
  ### `auth.enabled`
259
436
 
260
- Enables OAuth authentication for MCP clients.
437
+ Enables OAuth authentication for MCP clients. **This template ships it off.**
261
438
 
262
- - `true` requires a valid bearer token.
263
- - `false` preserves the historical unauthenticated mode.
439
+ - `false` (the shipped value) preserves the historical unauthenticated mode:
440
+ every client reaches every slot. Use it on a trusted network, and while you
441
+ are getting the rest working.
442
+ - `true` requires a valid bearer token on every client request. The rest of this
443
+ block then has to describe a real authorization server: with `enabled: true`
444
+ and the placeholder `identity.factory.local` values still in place, the broker
445
+ answers every client with a `401` whose challenge points at a host that does
446
+ not resolve, which is a confusing way to spend an afternoon.
264
447
  - A detailed policy is useful only when clients have an authenticated
265
448
  identity.
266
449
 
450
+ Everything below this point (`roles`, `assignments`, `denies`, `slotResources`,
451
+ `toolCapabilities`) is inert while `enabled` is `false`. It is kept in the
452
+ template as a worked example, not because it is doing anything.
453
+
267
454
  ### `auth.publicBaseUrl`
268
455
 
269
456
  Public address through which clients reach the broker.
@@ -348,7 +535,7 @@ To grant this access, add an assignment such as:
348
535
  The JWT must then contain both the `broker:admin` scope and the
349
536
  `broker-administrators` group.
350
537
 
351
- ## Lines 36 to 40: converting JWT claims into subjects
538
+ ## Converting JWT claims into subjects
352
539
 
353
540
  ```json
354
541
  "subjectMapping": {
@@ -415,7 +602,7 @@ client:local-ai-assistant
415
602
  A single call may therefore have multiple identities at the same time, such as
416
603
  one user, two groups, and one client application.
417
604
 
418
- ## Lines 41 to 64: roles and capabilities
605
+ ## Roles and capabilities
419
606
 
420
607
  A role answers only the question "what may be done?" It never contains a
421
608
  resource path.
@@ -488,7 +675,7 @@ Declaring a role does not grant it to anyone. The example file contains no
488
675
  assignment for `administrator`, so nobody becomes an administrator from this
489
676
  block alone.
490
677
 
491
- ## Lines 65 to 78: assignments
678
+ ## Assignments
492
679
 
493
680
  An assignment expresses this sentence:
494
681
 
@@ -556,7 +743,7 @@ site, but it cannot call tools.
556
743
 
557
744
  Regular expressions are not supported.
558
745
 
559
- ## Lines 79 to 89: explicit deny
746
+ ## Explicit deny
560
747
 
561
748
  ```json
562
749
  "denies": [
@@ -585,7 +772,7 @@ in the file.
585
772
 
586
773
  Use `"capabilities": ["*"]` to deny every capability on a specific resource.
587
774
 
588
- ## Lines 90 to 93: technical names and stable resources
775
+ ## Technical names and stable resources
589
776
 
590
777
  ```json
591
778
  "slotResources": {
@@ -623,7 +810,7 @@ An undeclared slot normally becomes `/<slot-name>`. In an industrial
623
810
  environment, explicit mappings are preferable because they preserve stable
624
811
  identities.
625
812
 
626
- ## Lines 94 to 99: global tool classification
813
+ ## Global tool classification
627
814
 
628
815
  ```json
629
816
  "toolCapabilities": {
@@ -650,7 +837,7 @@ If a tool is absent from every mapping, the broker uses the generic
650
837
  This fallback explains why the `maintenance` role also contains
651
838
  `mcp.tools.call`.
652
839
 
653
- ## Lines 100 to 104: resource-specific tool classification
840
+ ## Resource-specific tool classification
654
841
 
655
842
  ```json
656
843
  "providerToolCapabilities": {
@@ -673,7 +860,7 @@ value. This duplication is intentionally educational. In a real deployment,
673
860
  this block is useful when the same tool name has a different risk level for a
674
861
  particular provider or area.
675
862
 
676
- ## Lines 105 to 107: audit logging
863
+ ## Audit logging
677
864
 
678
865
  ```json
679
866
  "audit": {
@@ -690,16 +877,30 @@ Temporarily set it to `true` when learning the policy or diagnosing a problem.
690
877
  Audit records contain the decision and matching policy identifiers, but never
691
878
  the bearer token or provider secret.
692
879
 
693
- ## Line 108: shared provider secret
880
+ ## Shared provider secret
694
881
 
695
882
  ```json
696
883
  "providerSecret": "change-me"
697
884
  ```
698
885
 
886
+ **Deliberately absent from the shipped template.** Add the key inside `auth` to
887
+ turn provider authentication on.
888
+
699
889
  This secret authenticates MCP servers connecting to `/provider/<slot>` or
700
- `/providers`.
890
+ `/providers`. Every provider must then present it as `X-Provider-Token` or
891
+ `Authorization: Bearer`, and one that does not is refused at the WebSocket
892
+ handshake.
893
+
894
+ It is independent from client bearer tokens, and in particular it is **not**
895
+ governed by `auth.enabled`: set it and provider authentication is on, even with
896
+ OAuth off. That is why the template does not ship it. Left in place with the
897
+ placeholder value, it would refuse every provider on a broker the reader
898
+ believes is running wide open.
701
899
 
702
- It is independent from client bearer tokens.
900
+ One consequence to plan around: the browser `WebSocket` constructor cannot set
901
+ request headers, so a provider hosted in a web page cannot present the secret at
902
+ all. With `providerSecret` set, browser-hosted providers are locked out; they
903
+ need provider authentication off, or an authenticating reverse proxy in front.
703
904
 
704
905
  The `change-me` value is only a placeholder. In production:
705
906
 
@@ -712,14 +913,15 @@ The shared secret preserves historical compatibility and allows every resource
712
913
  path. To restrict each device to its own subtree, use a custom
713
914
  `IProviderAuthenticator` that returns `IProviderPrincipal.allowedResources`.
714
915
 
715
- ## Lines 111 to 117: local MCP server started by the broker
916
+ ## Local MCP server started by the broker
716
917
 
717
918
  ```json
718
919
  "stdioUpstreams": [
719
920
  {
720
- "name": "fs",
721
- "command": "npx",
722
- "args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"]
921
+ "name": "fs",
922
+ "command": "npx",
923
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"],
924
+ "aggregate": true
723
925
  }
724
926
  ]
725
927
  ```
@@ -747,11 +949,55 @@ Arguments passed to the program:
747
949
  Filesystem access is sensitive. Restrict `/data` to the smallest required
748
950
  directory.
749
951
 
750
- Add `"aggregate": true` if this provider should also appear in `_all`.
751
- Without this property, the stdio upstream remains available through its direct
752
- slot only.
952
+ ### `aggregate`
953
+
954
+ `true` makes this provider part of the reserved `_all` slot, in addition to its
955
+ own `/fs/mcp`. Without it the stdio upstream is reachable through its own slot
956
+ only.
957
+
958
+ Note the asymmetry: `stdioUpstreams` entries do **not** join `_all` by default,
959
+ while `mcpServers` and `mcpbBundles` entries do. Set it explicitly either way
960
+ and you never have to remember which is which.
961
+
962
+ ## Bridging an MCP host over stdio
753
963
 
754
- ## Lines 119 to 128: signed local MCP bundle
964
+ Not in `config.json`, but the reason most people set `aggregate` in the first
965
+ place. A second file in this folder, `config.stdio-bridge.json`, adds one key:
966
+
967
+ ```json
968
+ "stdioProvider": "_all"
969
+ ```
970
+
971
+ With it, the broker also behaves as a stdio MCP server: it reads JSON-RPC from
972
+ its standard input and writes answers to its standard output, bridging a host
973
+ such as Claude Desktop to one slot. Point the host's config at that file:
974
+
975
+ ```json
976
+ {
977
+ "command": "npx",
978
+ "args": ["-y", "@cyanmycelium/mcp-broker"],
979
+ "env": { "MCP_BROKER_CONFIG": "/abs/path/to/.mcp-broker/config.stdio-bridge.json" }
980
+ }
981
+ ```
982
+
983
+ Two things to get right.
984
+
985
+ - **Pin it to `_all`, not to a real slot.** `_all` exists from startup and
986
+ answers the handshake itself, so the host connects even though no provider has
987
+ arrived yet, and it announces new tools as providers join. Pinned to a real
988
+ slot, the host starts before the provider does, gets "not connected" for its
989
+ very first message, and gives up. For a provider hosted in a browser page that
990
+ is guaranteed: the page cannot possibly be open before the host launches.
991
+ `_broker` also always answers, but it only ever offers the five introspection
992
+ tools. The broker warns at startup when `stdioProvider` names a slot it does
993
+ not host.
994
+ - **Keep it in its own file.** With `stdioProvider` set, standard output belongs
995
+ to the JSON-RPC stream and every log line moves to standard error, so a broker
996
+ started that way in a terminal looks like it is doing nothing.
997
+
998
+ `MCP_BROKER_STDIO_PROVIDER` sets the same thing from the environment.
999
+
1000
+ ## Signed local MCP bundle
755
1001
 
756
1002
  ```json
757
1003
  "mcpbBundles": [
@@ -839,17 +1085,22 @@ If Alice attempts `start_motor` on the critical furnace:
839
1085
 
840
1086
  ## Pre-deployment checklist
841
1087
 
1088
+ - Set `auth.enabled` to `true`. The template ships it off so that a fresh copy
1089
+ runs; leaving it off in production means every client reaches every slot.
842
1090
  - Replace every `.local` domain with the real address.
843
1091
  - Confirm that `publicBaseUrl` exactly matches the broker's public address.
844
1092
  - Confirm that JWTs use this resource as their audience.
845
1093
  - Verify the JWKS URL and expected issuer.
846
- - Never keep `change-me`.
1094
+ - If you add `providerSecret`, never keep `change-me`.
847
1095
  - Never publish the private TLS key.
848
1096
  - Never publish API keys from `userConfig`.
849
1097
  - Use `127.0.0.1` instead of `0.0.0.0` when network access is unnecessary.
850
1098
  - Test every role with a representative account.
851
1099
  - Test denies against critical assets.
852
1100
  - Confirm that `_all` does not reveal unauthorized providers.
1101
+ - List in `allowedOrigins` exactly the browser origins that need access, with
1102
+ the right scheme and port, and no others. Remember that a page served by this
1103
+ broker still counts as a browser origin.
853
1104
  - Return `audit.logAllowed` to `false` after troubleshooting.
854
1105
  - Restart the broker after each policy change because policies are loaded only
855
1106
  once at startup.
@@ -857,5 +1108,10 @@ If Alice attempts `start_motor` on the critical furnace:
857
1108
  ## Further reading
858
1109
 
859
1110
  - [Complete configuration reference](../docs/config.md)
860
- - [Broker OAuth guide](../../docs/authorization.md)
861
- - [Hierarchical authorization](../../docs/hierarchical-authorization.md)
1111
+ - [Broker OAuth guide](../../../../docs/authorization.md)
1112
+ - [Hierarchical authorization](../../../../docs/hierarchical-authorization.md)
1113
+ - [Endpoints and transports](../../../../docs/endpoints.md)
1114
+
1115
+ Or ask the broker itself: the reserved `_broker` slot exposes a `broker_guide`
1116
+ tool (integration walkthroughs, written from the source) and a `broker_diagnose`
1117
+ tool (live state plus the problems it can prove, each with a fix).