@cyanmycelium/mcp-broker 1.0.1 → 1.2.0

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 (188) hide show
  1. package/.mcp-broker.example/README.md +3 -3
  2. package/README.md +6 -2
  3. package/dist/bin.d.ts +0 -1
  4. package/dist/bin.js +176 -253
  5. package/dist/bin.js.map +1 -1
  6. package/dist/chunk-FTDKH2C4.js +3670 -0
  7. package/dist/chunk-FTDKH2C4.js.map +1 -0
  8. package/dist/{broker/grammars → grammars}/claude/en.json +1 -1
  9. package/dist/{broker/grammars → grammars}/claude/fr.json +1 -1
  10. package/dist/index.d.ts +1790 -22
  11. package/dist/index.js +2 -18
  12. package/dist/index.js.map +1 -1
  13. package/package.json +9 -7
  14. package/scripts/copy-assets.mjs +16 -10
  15. package/scripts/gen-cert.mjs +5 -5
  16. package/scripts/pack-mcpb.mjs +5 -5
  17. package/scripts/sign-bundle.mjs +6 -6
  18. package/src/auth/auth.config.ts +3 -3
  19. package/src/auth/auth.types.ts +34 -59
  20. package/src/auth/http.auth.ts +18 -22
  21. package/src/auth/index.ts +12 -12
  22. package/src/auth/jwt.validator.ts +3 -3
  23. package/src/auth/provider.auth.ts +1 -1
  24. package/src/auth/resource.metadata.ts +14 -21
  25. package/src/authorization/audit.ts +2 -2
  26. package/src/authorization/capability.classifier.ts +1 -1
  27. package/src/authorization/index.ts +13 -13
  28. package/src/authorization/policy.engine.ts +3 -3
  29. package/src/authorization/policy.types.ts +1 -1
  30. package/src/authorization/runtime.ts +5 -5
  31. package/src/authorization/slot.resource.ts +1 -1
  32. package/src/authorization/subject.mapper.ts +1 -1
  33. package/src/bin.ts +38 -8
  34. package/src/broker/adapters/broker.adapter.info.ts +1 -1
  35. package/src/broker/adapters/broker.adapter.providers.ts +1 -1
  36. package/src/broker/aggregate/aggregate.catalog.ts +1 -1
  37. package/src/broker/aggregate/aggregate.server.ts +7 -7
  38. package/src/broker/aggregate/provider.client.session.ts +2 -2
  39. package/src/broker/behaviors/broker.behavior.info.ts +3 -3
  40. package/src/broker/behaviors/broker.behavior.providers.ts +5 -5
  41. package/src/broker/broker.grammars.ts +9 -9
  42. package/src/broker/broker.server.ts +6 -6
  43. package/src/broker/grammars/claude/en.json +1 -1
  44. package/src/broker/grammars/claude/fr.json +1 -1
  45. package/src/broker/index.ts +9 -9
  46. package/src/config.ts +25 -3
  47. package/src/index.ts +24 -24
  48. package/src/{mcpb.loader.ts → mcpb/mcpb.loader.ts} +11 -11
  49. package/src/{mcpb.unzip.ts → mcpb/mcpb.unzip.ts} +2 -2
  50. package/src/remote.transports.ts +3 -3
  51. package/src/remote.upstream.ts +3 -3
  52. package/src/stdio.upstream.ts +2 -2
  53. package/src/upstream.ts +2 -2
  54. package/src/ws/ws.interfaces.ts +357 -0
  55. package/src/{ws.tunnel.builder.ts → ws/ws.tunnel.builder.ts} +28 -5
  56. package/src/{ws.tunnel.ts → ws/ws.tunnel.ts} +231 -467
  57. package/web/README.md +3 -3
  58. package/web/broker-self-mcp.html +9 -9
  59. package/web/css/styles.css +1 -1
  60. package/web/demos/DemoPlaceholder.html +6 -6
  61. package/web/demos/broker-explorer/css/app.css +1 -1
  62. package/web/demos/broker-explorer/index.html +2 -2
  63. package/web/demos/broker-explorer/js/app.js +4 -4
  64. package/web/demos/broker-explorer/js/mcp-ws-client.js +2 -2
  65. package/web/demos/oauth-lab/README.md +26 -0
  66. package/web/demos/oauth-lab/config.json +1 -0
  67. package/web/demos/oauth-lab/js/app.js +40 -16
  68. package/web/demos/oauth-lab/server/smoke-test.mjs +57 -10
  69. package/web/demos/provider-tunnel/css/app.css +1 -1
  70. package/web/demos/provider-tunnel/index.html +3 -3
  71. package/web/demos/provider-tunnel/js/app.js +10 -10
  72. package/web/demos/provider-tunnel/js/toolbox-server.js +5 -5
  73. package/web/index.html +11 -11
  74. package/web/js/lib/broker-tunnel.js +12 -12
  75. package/dist/auth/auth.config.d.ts +0 -40
  76. package/dist/auth/auth.config.js +0 -62
  77. package/dist/auth/auth.config.js.map +0 -1
  78. package/dist/auth/auth.types.d.ts +0 -119
  79. package/dist/auth/auth.types.js +0 -38
  80. package/dist/auth/auth.types.js.map +0 -1
  81. package/dist/auth/http.auth.d.ts +0 -51
  82. package/dist/auth/http.auth.js +0 -121
  83. package/dist/auth/http.auth.js.map +0 -1
  84. package/dist/auth/index.d.ts +0 -16
  85. package/dist/auth/index.js +0 -12
  86. package/dist/auth/index.js.map +0 -1
  87. package/dist/auth/jwt.validator.d.ts +0 -35
  88. package/dist/auth/jwt.validator.js +0 -46
  89. package/dist/auth/jwt.validator.js.map +0 -1
  90. package/dist/auth/provider.auth.d.ts +0 -51
  91. package/dist/auth/provider.auth.js +0 -80
  92. package/dist/auth/provider.auth.js.map +0 -1
  93. package/dist/auth/resource.metadata.d.ts +0 -32
  94. package/dist/auth/resource.metadata.js +0 -26
  95. package/dist/auth/resource.metadata.js.map +0 -1
  96. package/dist/authorization/audit.d.ts +0 -14
  97. package/dist/authorization/audit.js +0 -20
  98. package/dist/authorization/audit.js.map +0 -1
  99. package/dist/authorization/capability.classifier.d.ts +0 -30
  100. package/dist/authorization/capability.classifier.js +0 -85
  101. package/dist/authorization/capability.classifier.js.map +0 -1
  102. package/dist/authorization/index.d.ts +0 -13
  103. package/dist/authorization/index.js +0 -8
  104. package/dist/authorization/index.js.map +0 -1
  105. package/dist/authorization/policy.engine.d.ts +0 -11
  106. package/dist/authorization/policy.engine.js +0 -192
  107. package/dist/authorization/policy.engine.js.map +0 -1
  108. package/dist/authorization/policy.types.d.ts +0 -92
  109. package/dist/authorization/policy.types.js +0 -2
  110. package/dist/authorization/policy.types.js.map +0 -1
  111. package/dist/authorization/resource.path.d.ts +0 -27
  112. package/dist/authorization/resource.path.js +0 -122
  113. package/dist/authorization/resource.path.js.map +0 -1
  114. package/dist/authorization/runtime.d.ts +0 -22
  115. package/dist/authorization/runtime.js +0 -34
  116. package/dist/authorization/runtime.js.map +0 -1
  117. package/dist/authorization/slot.resource.d.ts +0 -12
  118. package/dist/authorization/slot.resource.js +0 -41
  119. package/dist/authorization/slot.resource.js.map +0 -1
  120. package/dist/authorization/subject.mapper.d.ts +0 -15
  121. package/dist/authorization/subject.mapper.js +0 -67
  122. package/dist/authorization/subject.mapper.js.map +0 -1
  123. package/dist/broker/adapters/broker.adapter.info.d.ts +0 -16
  124. package/dist/broker/adapters/broker.adapter.info.js +0 -43
  125. package/dist/broker/adapters/broker.adapter.info.js.map +0 -1
  126. package/dist/broker/adapters/broker.adapter.providers.d.ts +0 -18
  127. package/dist/broker/adapters/broker.adapter.providers.js +0 -61
  128. package/dist/broker/adapters/broker.adapter.providers.js.map +0 -1
  129. package/dist/broker/aggregate/aggregate.catalog.d.ts +0 -66
  130. package/dist/broker/aggregate/aggregate.catalog.js +0 -119
  131. package/dist/broker/aggregate/aggregate.catalog.js.map +0 -1
  132. package/dist/broker/aggregate/aggregate.server.d.ts +0 -66
  133. package/dist/broker/aggregate/aggregate.server.js +0 -257
  134. package/dist/broker/aggregate/aggregate.server.js.map +0 -1
  135. package/dist/broker/aggregate/provider.client.session.d.ts +0 -52
  136. package/dist/broker/aggregate/provider.client.session.js +0 -140
  137. package/dist/broker/aggregate/provider.client.session.js.map +0 -1
  138. package/dist/broker/behaviors/broker.behavior.info.d.ts +0 -15
  139. package/dist/broker/behaviors/broker.behavior.info.js +0 -41
  140. package/dist/broker/behaviors/broker.behavior.info.js.map +0 -1
  141. package/dist/broker/behaviors/broker.behavior.providers.d.ts +0 -19
  142. package/dist/broker/behaviors/broker.behavior.providers.js +0 -69
  143. package/dist/broker/behaviors/broker.behavior.providers.js.map +0 -1
  144. package/dist/broker/broker.context.d.ts +0 -63
  145. package/dist/broker/broker.context.js +0 -2
  146. package/dist/broker/broker.context.js.map +0 -1
  147. package/dist/broker/broker.grammars.d.ts +0 -132
  148. package/dist/broker/broker.grammars.js +0 -229
  149. package/dist/broker/broker.grammars.js.map +0 -1
  150. package/dist/broker/broker.server.d.ts +0 -68
  151. package/dist/broker/broker.server.js +0 -73
  152. package/dist/broker/broker.server.js.map +0 -1
  153. package/dist/broker/index.d.ts +0 -9
  154. package/dist/broker/index.js +0 -7
  155. package/dist/broker/index.js.map +0 -1
  156. package/dist/config.d.ts +0 -182
  157. package/dist/config.js +0 -61
  158. package/dist/config.js.map +0 -1
  159. package/dist/mcpb.loader.d.ts +0 -26
  160. package/dist/mcpb.loader.js +0 -161
  161. package/dist/mcpb.loader.js.map +0 -1
  162. package/dist/mcpb.unzip.d.ts +0 -6
  163. package/dist/mcpb.unzip.js +0 -95
  164. package/dist/mcpb.unzip.js.map +0 -1
  165. package/dist/remote.transports.d.ts +0 -18
  166. package/dist/remote.transports.js +0 -297
  167. package/dist/remote.transports.js.map +0 -1
  168. package/dist/remote.upstream.d.ts +0 -38
  169. package/dist/remote.upstream.js +0 -52
  170. package/dist/remote.upstream.js.map +0 -1
  171. package/dist/stdio.upstream.d.ts +0 -47
  172. package/dist/stdio.upstream.js +0 -85
  173. package/dist/stdio.upstream.js.map +0 -1
  174. package/dist/upstream.d.ts +0 -35
  175. package/dist/upstream.js +0 -2
  176. package/dist/upstream.js.map +0 -1
  177. package/dist/version.d.ts +0 -2
  178. package/dist/version.js +0 -9
  179. package/dist/version.js.map +0 -1
  180. package/dist/ws.tunnel.builder.d.ts +0 -176
  181. package/dist/ws.tunnel.builder.js +0 -271
  182. package/dist/ws.tunnel.builder.js.map +0 -1
  183. package/dist/ws.tunnel.d.ts +0 -455
  184. package/dist/ws.tunnel.js +0 -1425
  185. package/dist/ws.tunnel.js.map +0 -1
  186. /package/dist/{broker/grammars → grammars}/default/en.json +0 -0
  187. /package/dist/{broker/grammars → grammars}/default/fr.json +0 -0
  188. /package/dist/{broker/grammars → grammars}/default/zh.json +0 -0
@@ -1,4 +1,4 @@
1
- import { ResourcePath } from "./resource.path.js";
1
+ import { ResourcePath } from "./resource.path";
2
2
 
3
3
  export interface ISlotResourceResolver {
4
4
  resolve(slot: string): ResourcePath | undefined;
@@ -1,4 +1,4 @@
1
- import type { IAuthorizationSubject, ISubjectMappingConfig } from "./policy.types.js";
1
+ import type { IAuthorizationSubject, ISubjectMappingConfig } from "./policy.types";
2
2
 
3
3
  export interface ISubjectMapper {
4
4
  map(claims: Readonly<Record<string, unknown>>): IAuthorizationSubject;
package/src/bin.ts CHANGED
@@ -16,7 +16,7 @@
16
16
  * - Env-var paths (`MCP_BROKER_*_DIR`, `MCP_BROKER_TLS_*`) are resolved
17
17
  * against `process.cwd()`.
18
18
  * - Config-file paths (`tls.cert`, `www.mounts[*].dir`, `stdioUpstreams[*]`,
19
- * `mcpbBundles[*]`) are resolved against the **config file's directory** — so a config in
19
+ * `mcpbBundles[*]`) are resolved against the **config file's directory**: so a config in
20
20
  * `./.mcp-broker/config.json` referring to `"certs/cert.pem"` points at
21
21
  * `./.mcp-broker/certs/cert.pem`. The folder is self-contained.
22
22
  *
@@ -48,13 +48,15 @@
48
48
  * | | | provider (Claude Desktop bridge). |
49
49
  * | MCP_BROKER_LOCALE | en | Locale used for tool descriptions on the `_broker` |
50
50
  * | | | slot. ISO 639-1 base; regional variants accepted. |
51
+ * | MCP_BROKER_ALLOWED_ORIGINS | (none) | Comma-separated browser origins allowed on |
52
+ * | | | `/<slot>/mcp`. None means no browser origin passes. |
51
53
  */
52
54
  import * as fs from "fs";
53
55
  import * as path from "path";
54
56
  import open from "open";
55
- import { WsTunnelBuilder } from "./index.js";
56
- import { loadBrokerConfig } from "./config.js";
57
- import { loadMcpbBundle } from "./mcpb.loader.js";
57
+ import { WsTunnelBuilder } from "./index";
58
+ import { loadBrokerConfig } from "./config";
59
+ import { loadMcpbBundle } from "./mcpb/mcpb.loader";
58
60
 
59
61
  // ---------------------------------------------------------------------------
60
62
  // Configuration
@@ -65,7 +67,7 @@ const cwd = process.cwd();
65
67
 
66
68
  /**
67
69
  * Fills an env var from the config file when the env var is not already set.
68
- * Used for **non-path** scalars — path-bearing fields are read directly so
70
+ * Used for **non-path** scalars, path-bearing fields are read directly so
69
71
  * they can be resolved against `baseDir` (config file's directory) instead
70
72
  * of `cwd` (deploy environment).
71
73
  */
@@ -92,7 +94,7 @@ envFromConfig("MCP_BROKER_PROVIDER_SECRET", config.auth?.providerSecret);
92
94
 
93
95
  const stdioProvider = process.env["MCP_BROKER_STDIO_PROVIDER"];
94
96
 
95
- // In stdio mode stdout is reserved for JSON-RPC — redirect all console output to stderr.
97
+ // In stdio mode stdout is reserved for JSON-RPC, redirect all console output to stderr.
96
98
  if (stdioProvider) {
97
99
  const toStderr = (...args: unknown[]) => process.stderr.write(args.join(" ") + "\n");
98
100
  console.log = toStderr;
@@ -125,6 +127,30 @@ if (protocolOverride === "https" && (!tlsCertPath || !tlsKeyPath)) {
125
127
 
126
128
  const useTls = protocolOverride === "http" ? false : protocolOverride === "https" ? true : !!(tlsCertPath && tlsKeyPath);
127
129
 
130
+ // ── Allowed browser origins for /<slot>/mcp ──────────────────────────────────
131
+ // Absent leaves the endpoint closed to browsers, which is the safe default.
132
+ // The env var only carries the list form; a pattern needs the config file,
133
+ // since a regular expression cannot survive comma-splitting.
134
+ const envOrigins = process.env["MCP_BROKER_ALLOWED_ORIGINS"]
135
+ ?.split(",")
136
+ .map((origin) => origin.trim())
137
+ .filter(Boolean);
138
+
139
+ let allowedOrigins: readonly string[] | RegExp | undefined;
140
+ if (envOrigins?.length) {
141
+ allowedOrigins = envOrigins;
142
+ } else if (Array.isArray(config.allowedOrigins)) {
143
+ allowedOrigins = config.allowedOrigins;
144
+ } else if (config.allowedOrigins) {
145
+ try {
146
+ allowedOrigins = new RegExp(config.allowedOrigins.pattern, config.allowedOrigins.flags);
147
+ } catch (err) {
148
+ // Falling back to the closed default rather than starting with a rule
149
+ // the operator believes is in force but which never compiled.
150
+ console.error(`[mcp-broker] Ignoring allowedOrigins.pattern: ${(err as Error).message}`);
151
+ }
152
+ }
153
+
128
154
  // ── Env-var static mount shortcuts (relative to cwd) ─────────────────────────
129
155
  const envWwwDir = process.env["MCP_BROKER_WWW_DIR"] ? path.resolve(cwd, process.env["MCP_BROKER_WWW_DIR"]) : null;
130
156
  const envBundleDir = process.env["MCP_BROKER_BUNDLE_DIR"] ? path.resolve(cwd, process.env["MCP_BROKER_BUNDLE_DIR"]) : null;
@@ -149,6 +175,10 @@ async function main(): Promise<void> {
149
175
  builder.withTlsFiles(tlsCertPath!, tlsKeyPath!);
150
176
  }
151
177
 
178
+ if (allowedOrigins) {
179
+ builder.withAllowedOrigins(allowedOrigins);
180
+ }
181
+
152
182
  // ── Static mounts ────────────────────────────────────────────────────────
153
183
  // First the env-var shortcuts (relative to cwd), then the config `www.mounts`
154
184
  // (relative to baseDir). Order matters: /bundle before / so the prefix router
@@ -254,7 +284,7 @@ async function main(): Promise<void> {
254
284
 
255
285
  // ── Provider authentication (independent of client OAuth) ────────────────
256
286
  // Requires every provider connecting to /provider/<slot> or /providers to
257
- // present the shared secret — closes off slot occupation by strangers.
287
+ // present the shared secret, closes off slot occupation by strangers.
258
288
  const providerSecret = process.env["MCP_BROKER_PROVIDER_SECRET"];
259
289
  if (providerSecret) {
260
290
  builder.withProviderSecret(providerSecret);
@@ -298,7 +328,7 @@ async function main(): Promise<void> {
298
328
 
299
329
  // ── Signal handlers ─────────────────────────────────────────────────────
300
330
  const shutdown = async (signal: string): Promise<void> => {
301
- console.log(`\n⛔ ${signal} received — shutting down…`);
331
+ console.log(`\n⛔ ${signal} received, shutting down…`);
302
332
  await tunnel.stop();
303
333
  process.exit(0);
304
334
  };
@@ -1,6 +1,6 @@
1
1
  import { McpAdapterBase, McpToolResults } from "@cyanmycelium/mcp-core";
2
2
  import type { McpResourceContent, McpToolResult } from "@cyanmycelium/mcp-core";
3
- import type { IBrokerContext } from "../broker.context.js";
3
+ import type { IBrokerContext } from "../broker.context";
4
4
 
5
5
  /** URI of the static resource backing the `broker_info` snapshot. */
6
6
  export const BROKER_INFO_URI = "broker://info";
@@ -1,6 +1,6 @@
1
1
  import { McpAdapterBase, McpToolResults } from "@cyanmycelium/mcp-core";
2
2
  import type { McpResourceContent, McpToolResult } from "@cyanmycelium/mcp-core";
3
- import type { IBrokerContext, IBrokerProviderInfo } from "../broker.context.js";
3
+ import type { IBrokerContext, IBrokerProviderInfo } from "../broker.context";
4
4
 
5
5
  /** URI of the static resource that lists every provider slot. */
6
6
  export const PROVIDERS_URI = "broker://providers";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * AggregateCatalog — merges the tool and prompt catalogs of several providers
2
+ * AggregateCatalog, merges the tool and prompt catalogs of several providers
3
3
  * into a single namespaced view, and routes aggregated names back to their
4
4
  * origin provider.
5
5
  *
@@ -1,6 +1,6 @@
1
1
  import type { IMessageTransport } from "@cyanmycelium/mcp-core";
2
- import type { IInternalClient } from "../../ws.tunnel.js";
3
- import type { AggregateScopeFilter, IPrincipal } from "../../auth/index.js";
2
+ import type { IInternalClient } from "../../ws/ws.interfaces";
3
+ import type { AggregateScopeFilter, IPrincipal } from "../../auth/index";
4
4
  import {
5
5
  SubjectMappingError,
6
6
  makeAuthorizationAuditEvent,
@@ -9,9 +9,9 @@ import {
9
9
  type IAuthorizationSubject,
10
10
  type IMcpOperation,
11
11
  type IPolicyAuthorization,
12
- } from "../../authorization/index.js";
13
- import { AggregateCatalog } from "./aggregate.catalog.js";
14
- import { ProviderClientSession } from "./provider.client.session.js";
12
+ } from "../../authorization/index";
13
+ import { AggregateCatalog } from "./aggregate.catalog";
14
+ import { ProviderClientSession } from "./provider.client.session";
15
15
 
16
16
  /** MCP protocol version advertised by the aggregate server. */
17
17
  const PROTOCOL_VERSION = "2024-11-05";
@@ -232,7 +232,7 @@ export class AggregateServer implements IMessageTransport {
232
232
  return;
233
233
  }
234
234
  const id = msg.id;
235
- if (id == null) return; // client notification — nothing to answer
235
+ if (id == null) return; // client notification: nothing to answer
236
236
 
237
237
  switch (msg.method) {
238
238
  case "initialize":
@@ -275,7 +275,7 @@ export class AggregateServer implements IMessageTransport {
275
275
  private async _route(id: string | number, params: unknown, kind: "tool" | "prompt", principal: IPrincipal | null): Promise<void> {
276
276
  const p = (params ?? {}) as ICallParams;
277
277
  const route = p.name ? (kind === "tool" ? this._catalog.resolveTool(p.name) : this._catalog.resolvePrompt(p.name)) : undefined;
278
- // Treat a provider the caller may not see as if it did not exist — do not
278
+ // Treat a provider the caller may not see as if it did not exist, do not
279
279
  // leak its presence through a distinct "forbidden" error.
280
280
  const operation: IMcpOperation | undefined = route
281
281
  ? kind === "tool"
@@ -1,5 +1,5 @@
1
- import type { IInternalClient } from "../../ws.tunnel.js";
2
- import type { ICatalogTool, ICatalogPrompt } from "./aggregate.catalog.js";
1
+ import type { IInternalClient } from "../../ws/ws.interfaces";
2
+ import type { ICatalogTool, ICatalogPrompt } from "./aggregate.catalog";
3
3
 
4
4
  /** MCP protocol version the aggregate sessions negotiate with sub-providers. */
5
5
  const PROTOCOL_VERSION = "2024-11-05";
@@ -1,8 +1,8 @@
1
1
  import { McpBehavior } from "@cyanmycelium/mcp-core";
2
2
  import type { McpResource, McpTool } from "@cyanmycelium/mcp-core";
3
- import { BROKER_INFO_URI, BrokerInfoAdapter } from "../adapters/broker.adapter.info.js";
4
- import { brokerBaselineResourceDescription, brokerBaselineResourceName, brokerBaselineToolDescription } from "../broker.grammars.js";
5
- import type { IBrokerContext } from "../broker.context.js";
3
+ import { BROKER_INFO_URI, BrokerInfoAdapter } from "../adapters/broker.adapter.info";
4
+ import { brokerBaselineResourceDescription, brokerBaselineResourceName, brokerBaselineToolDescription } from "../broker.grammars";
5
+ import type { IBrokerContext } from "../broker.context";
6
6
 
7
7
  /**
8
8
  * Exposes basic broker identity (name, version, uptime, listening config) as
@@ -1,6 +1,6 @@
1
1
  import { McpBehavior } from "@cyanmycelium/mcp-core";
2
2
  import type { McpResource, McpResourceTemplate, McpTool } from "@cyanmycelium/mcp-core";
3
- import { BrokerProvidersAdapter, PROVIDERS_URI, PROVIDER_URI_TEMPLATE } from "../adapters/broker.adapter.providers.js";
3
+ import { BrokerProvidersAdapter, PROVIDERS_URI, PROVIDER_URI_TEMPLATE } from "../adapters/broker.adapter.providers";
4
4
  import {
5
5
  brokerBaselinePropertyDescription,
6
6
  brokerBaselineResourceDescription,
@@ -8,15 +8,15 @@ import {
8
8
  brokerBaselineResourceTemplateDescription,
9
9
  brokerBaselineResourceTemplateName,
10
10
  brokerBaselineToolDescription,
11
- } from "../broker.grammars.js";
12
- import type { IBrokerContext } from "../broker.context.js";
11
+ } from "../broker.grammars";
12
+ import type { IBrokerContext } from "../broker.context";
13
13
 
14
14
  /**
15
15
  * Exposes the broker's provider slots so an MCP agent can discover what is
16
16
  * currently routable behind the broker, with two tools:
17
17
  *
18
- * - `providers_list` — every slot (including disconnected ones).
19
- * - `provider_status({ name })` — detail on one slot.
18
+ * - `providers_list`: every slot (including disconnected ones).
19
+ * - `provider_status({ name })`, detail on one slot.
20
20
  *
21
21
  * Plus matching resources at `broker://providers` and `broker://providers/<name>`.
22
22
  */
@@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  import { McpGrammar } from "@cyanmycelium/mcp-core";
5
5
 
6
6
  // ---------------------------------------------------------------------------
7
- // Open string types — extensibility by convention
7
+ // Open string types, extensibility by convention
8
8
  // ---------------------------------------------------------------------------
9
9
 
10
10
  /**
@@ -40,8 +40,8 @@ export type BrokerUserAgent = string;
40
40
  * matrix the broker registers on disk.
41
41
  *
42
42
  * Pattern:
43
- * - `"<userAgent>:<locale>"` (no version) — e.g. `"claude:fr"`, `"default:en"`
44
- * - `"<userAgent>:<locale>@<version>"` (versioned) — e.g. `"claude:fr@v2"`
43
+ * - `"<userAgent>:<locale>"` (no version), e.g. `"claude:fr"`, `"default:en"`
44
+ * - `"<userAgent>:<locale>@<version>"` (versioned), e.g. `"claude:fr@v2"`
45
45
  *
46
46
  * The colon separator is reserved for the `<ua>:<locale>` composition; the
47
47
  * `@` separator is reserved for the optional version suffix. Neither
@@ -94,7 +94,7 @@ export function parseBrokerGrammarStem(stem: string): { locale: BrokerLocale; ve
94
94
  * JSON files live alongside this module in `src/broker/grammars/` during
95
95
  * development and are mirrored under `dist/broker/grammars/` at build time
96
96
  * by `scripts/copy-assets.mjs`. Adding a new `(userAgent, locale)` pair is
97
- * just a matter of dropping a new JSON file — no code change required.
97
+ * just a matter of dropping a new JSON file: no code change required.
98
98
  */
99
99
  const GRAMMARS_DIR = join(dirname(fileURLToPath(import.meta.url)), "grammars");
100
100
 
@@ -132,7 +132,7 @@ export function loadBrokerGrammar(userAgent: BrokerUserAgent, locale: BrokerLoca
132
132
  *
133
133
  * Used by the broker server at startup to bulk-register both the packaged
134
134
  * grammars and any local overrides. No hard-coded list of supported
135
- * user-agents or locales — adding a new grammar is dropping a JSON file.
135
+ * user-agents or locales, adding a new grammar is dropping a JSON file.
136
136
  */
137
137
  export interface IBrokerGrammarEntry {
138
138
  userAgent: BrokerUserAgent;
@@ -186,7 +186,7 @@ export function* iterBrokerGrammarsFrom(grammarsDir: string): Generator<IBrokerG
186
186
  * mcp-broker package). Equivalent to `iterBrokerGrammarsFrom(<packaged-dir>)`.
187
187
  *
188
188
  * For local user overrides, see {@link iterBrokerGrammarsFrom} with a custom
189
- * directory — typically `.mcp-broker/grammars/` next to the config file.
189
+ * directory, typically `.mcp-broker/grammars/` next to the config file.
190
190
  */
191
191
  export function* iterAvailableBrokerGrammars(): Generator<IBrokerGrammarEntry> {
192
192
  yield* iterBrokerGrammarsFrom(GRAMMARS_DIR);
@@ -206,20 +206,20 @@ export type BrokerGrammarEntry = IBrokerGrammarEntry;
206
206
  * Conventionally this is `default:en`. Session-specific grammars selected by
207
207
  * the resolver override individual entries on top of this baseline.
208
208
  *
209
- * Throws if the JSON resource is missing — the broker behaviors cannot be
209
+ * Throws if the JSON resource is missing: the broker behaviors cannot be
210
210
  * built without baseline descriptions.
211
211
  */
212
212
  export function brokerBaselineGrammar(): McpGrammar {
213
213
  const g = loadBrokerGrammar("default", "en");
214
214
  if (!g) {
215
- throw new Error(`Required baseline broker grammar "default:en" is missing — expected at ${join(GRAMMARS_DIR, "default", "en.json")}.`);
215
+ throw new Error(`Required baseline broker grammar "default:en" is missing, expected at ${join(GRAMMARS_DIR, "default", "en.json")}.`);
216
216
  }
217
217
  return g;
218
218
  }
219
219
 
220
220
  /**
221
221
  * Convenience accessor for a baseline tool description. Throws when the
222
- * tool is not listed in the baseline grammar — i.e. the JSON file is missing
222
+ * tool is not listed in the baseline grammar, i.e. the JSON file is missing
223
223
  * an entry for a tool the code knows about.
224
224
  */
225
225
  export function brokerBaselineToolDescription(toolName: string): string {
@@ -1,9 +1,9 @@
1
1
  import { McpServerBuilder, LoopbackTransport } from "@cyanmycelium/mcp-core";
2
2
  import type { GrammarResolverOptions, IMcpServer, IMessageTransport } from "@cyanmycelium/mcp-core";
3
- import { BrokerInfoBehavior } from "./behaviors/broker.behavior.info.js";
4
- import { BrokerProvidersBehavior } from "./behaviors/broker.behavior.providers.js";
5
- import { iterAvailableBrokerGrammars, iterBrokerGrammarsFrom } from "./broker.grammars.js";
6
- import type { IBrokerContext } from "./broker.context.js";
3
+ import { BrokerInfoBehavior } from "./behaviors/broker.behavior.info";
4
+ import { BrokerProvidersBehavior } from "./behaviors/broker.behavior.providers";
5
+ import { iterAvailableBrokerGrammars, iterBrokerGrammarsFrom } from "./broker.grammars";
6
+ import type { IBrokerContext } from "./broker.context";
7
7
 
8
8
  /**
9
9
  * Reserved provider slot name under which the broker exposes itself as an MCP
@@ -44,7 +44,7 @@ export interface IStartBrokerServerOptions {
44
44
  * via `withGrammar(brokerGrammarKey(ua, locale), grammar)`. The
45
45
  * candidate-chain resolution implemented by `McpServer.initialize` in
46
46
  * mcp-core@0.3.0 then walks the chain and merges the four layers
47
- * (behavior, adapter, static, store) for the first matching key —
47
+ * (behavior, adapter, static, store) for the first matching key ,
48
48
  * the old hand-rolled pre-merge cascade is no longer needed.
49
49
  *
50
50
  * When `undefined` (default), only the packaged grammars are loaded.
@@ -93,7 +93,7 @@ export async function startBrokerServer(
93
93
  // Register every `(userAgent, locale)` JSON found on disk as a raw
94
94
  // grammar layer. The candidate-chain resolution in
95
95
  // McpServer.initialize (mcp-core@0.3.0) walks the resolver's chain
96
- // and merges all matching layers — so partial user-agent files no
96
+ // and merges all matching layers: so partial user-agent files no
97
97
  // longer need to be pre-merged with the default-locale baseline at
98
98
  // boot. Local overrides come after packaged entries; identical keys
99
99
  // get overlaid via the registry's last-write-wins.
@@ -7,7 +7,7 @@
7
7
  "description": "List every provider slot on this broker, connected or not. Each entry tells you the transport kind, current client count, and how many requests are in flight. Use this to discover what you can route to."
8
8
  },
9
9
  "provider_status": {
10
- "description": "Get the full status of one provider slot by name. Returns an error if the slot does not exist — start with providers_list if you are not sure of the name.",
10
+ "description": "Get the full status of one provider slot by name. Returns an error if the slot does not exist, start with providers_list if you are not sure of the name.",
11
11
  "properties": {
12
12
  "name": "The exact provider slot name. Case-sensitive. Use providers_list to discover valid values."
13
13
  }
@@ -7,7 +7,7 @@
7
7
  "description": "Liste tous les emplacements de providers de ce broker, connectés ou non. Chaque entrée indique le type de transport, le nombre de clients actuels et le nombre de requêtes en cours. Utilise-le pour découvrir vers qui tu peux router."
8
8
  },
9
9
  "provider_status": {
10
- "description": "Récupère le statut complet d'un emplacement de provider par son nom. Retourne une erreur si l'emplacement n'existe pas — commence par providers_list si tu n'es pas sûr du nom.",
10
+ "description": "Récupère le statut complet d'un emplacement de provider par son nom. Retourne une erreur si l'emplacement n'existe pas, commence par providers_list si tu n'es pas sûr du nom.",
11
11
  "properties": {
12
12
  "name": "Le nom exact de l'emplacement provider. Sensible à la casse. Utilise providers_list pour découvrir les valeurs valides."
13
13
  }
@@ -1,9 +1,9 @@
1
- export { BrokerInfoBehavior } from "./behaviors/broker.behavior.info.js";
2
- export { BrokerProvidersBehavior } from "./behaviors/broker.behavior.providers.js";
3
- export { BrokerInfoAdapter, BROKER_INFO_URI } from "./adapters/broker.adapter.info.js";
4
- export { BrokerProvidersAdapter, PROVIDERS_URI, PROVIDER_URI_TEMPLATE } from "./adapters/broker.adapter.providers.js";
5
- export { startBrokerServer, BROKER_PROVIDER_NAME } from "./broker.server.js";
6
- export type { IStartBrokerServerOptions, StartBrokerServerOptions } from "./broker.server.js";
1
+ export { BrokerInfoBehavior } from "./behaviors/broker.behavior.info";
2
+ export { BrokerProvidersBehavior } from "./behaviors/broker.behavior.providers";
3
+ export { BrokerInfoAdapter, BROKER_INFO_URI } from "./adapters/broker.adapter.info";
4
+ export { BrokerProvidersAdapter, PROVIDERS_URI, PROVIDER_URI_TEMPLATE } from "./adapters/broker.adapter.providers";
5
+ export { startBrokerServer, BROKER_PROVIDER_NAME } from "./broker.server";
6
+ export type { IStartBrokerServerOptions, StartBrokerServerOptions } from "./broker.server";
7
7
  export {
8
8
  brokerBaselineGrammar,
9
9
  brokerBaselinePropertyDescription,
@@ -17,6 +17,6 @@ export {
17
17
  iterBrokerGrammarsFrom,
18
18
  loadBrokerGrammar,
19
19
  parseBrokerGrammarStem,
20
- } from "./broker.grammars.js";
21
- export type { IBrokerGrammarEntry, BrokerGrammarEntry, BrokerLocale, BrokerUserAgent } from "./broker.grammars.js";
22
- export type { IBrokerContext, IBrokerProviderInfo, BrokerContext, BrokerProviderInfo, BrokerProviderTransport } from "./broker.context.js";
20
+ } from "./broker.grammars";
21
+ export type { IBrokerGrammarEntry, BrokerGrammarEntry, BrokerLocale, BrokerUserAgent } from "./broker.grammars";
22
+ export type { IBrokerContext, IBrokerProviderInfo, BrokerContext, BrokerProviderInfo, BrokerProviderTransport } from "./broker.context";
package/src/config.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, resolve } from "node:path";
3
- import type { IAuthorizationPolicyConfig } from "./authorization/index.js";
3
+ import type { IAuthorizationPolicyConfig } from "./authorization/index";
4
4
 
5
5
  export interface IBrokerAuthConfig extends IAuthorizationPolicyConfig {
6
6
  /** Master switch. Absent/`false` keeps the broker unauthenticated. */
@@ -67,6 +67,28 @@ export interface IBrokerConfig {
67
67
  /** Logical broker name reported by `broker_info`. */
68
68
  brokerName?: string;
69
69
 
70
+ /**
71
+ * Browser origins allowed to reach `/<slot>/mcp`.
72
+ *
73
+ * Absent means no browser origin is accepted: a request carrying an
74
+ * `Origin` header gets `403`, one carrying none (Claude Desktop, MCP
75
+ * Inspector, any server-side SDK) always passes. The MCP specification asks
76
+ * for this check, since without it any web page loaded in a browser that
77
+ * can reach the broker could drive it.
78
+ *
79
+ * An array lists origins matched **exactly**. An object supplies a regular
80
+ * expression tested against the whole `Origin` header. Also maps to
81
+ * `MCP_BROKER_ALLOWED_ORIGINS` (comma-separated list form only; a pattern
82
+ * needs the config file).
83
+ *
84
+ * @example
85
+ * ```json
86
+ * { "allowedOrigins": ["https://app.example.com", "http://localhost:5173"] }
87
+ * { "allowedOrigins": { "pattern": "^https://[a-z0-9-]+\\.example\\.com$" } }
88
+ * ```
89
+ */
90
+ allowedOrigins?: string[] | { pattern: string; flags?: string };
91
+
70
92
  /**
71
93
  * OAuth 2.1 resource-server authorization. When `enabled` is `true`, every
72
94
  * HTTP client request to a slot must carry a valid `Authorization: Bearer`
@@ -184,12 +206,12 @@ export const LEGACY_CONFIG_FILENAME = "mcp-broker.config.json";
184
206
  * 1. The `path` argument when provided (explicit override).
185
207
  * 2. The `MCP_BROKER_CONFIG` env var.
186
208
  * 3. `./.mcp-broker/config.json` relative to `process.cwd()`.
187
- * 4. `./mcp-broker.config.json` relative to `process.cwd()` (legacy layout —
209
+ * 4. `./mcp-broker.config.json` relative to `process.cwd()` (legacy layout ,
188
210
  * a deprecation warning is written to stderr).
189
211
  *
190
212
  * When no file is found, returns the built-in empty config with
191
213
  * `baseDir = process.cwd()`. On invalid JSON, logs a warning to stderr and
192
- * returns the same empty config — never throws.
214
+ * returns the same empty config, never throws.
193
215
  *
194
216
  * Paths inside the config file are intended to be resolved against
195
217
  * {@link ILoadedBrokerConfig.baseDir} by the consumer.
package/src/index.ts CHANGED
@@ -1,21 +1,21 @@
1
- export { WsTunnel } from "./ws.tunnel.js";
2
- export { WsTunnelBuilder } from "./ws.tunnel.builder.js";
3
- export type { IInternalClient, IWsTunnelOptions, IStaticMount, InternalClient, WsTunnelOptions, StaticMount } from "./ws.tunnel.js";
4
- export { StdioUpstream } from "./stdio.upstream.js";
5
- export type { IStdioUpstreamConfig, StdioUpstreamConfig } from "./stdio.upstream.js";
6
- export { RemoteUpstream } from "./remote.upstream.js";
7
- export type { IRemoteUpstreamConfig, RemoteUpstreamConfig } from "./remote.upstream.js";
8
- export type { IUpstream, Upstream } from "./upstream.js";
1
+ export { WsTunnel } from "./ws/ws.tunnel";
2
+ export { WsTunnelBuilder } from "./ws/ws.tunnel.builder";
3
+ export type { AllowedOrigins, IInternalClient, IWsTunnelOptions, IStaticMount, InternalClient, WsTunnelOptions, StaticMount } from "./ws/ws.interfaces";
4
+ export { StdioUpstream } from "./stdio.upstream";
5
+ export type { IStdioUpstreamConfig, StdioUpstreamConfig } from "./stdio.upstream";
6
+ export { RemoteUpstream } from "./remote.upstream";
7
+ export type { IRemoteUpstreamConfig, RemoteUpstreamConfig } from "./remote.upstream";
8
+ export type { IUpstream, Upstream } from "./upstream";
9
9
 
10
- // `.mcpb` bundle loading — verifies + unpacks a bundle into a stdio upstream.
11
- export { loadMcpbBundle } from "./mcpb.loader.js";
12
- export type { IMcpbBundleConfig, McpbBundleConfig } from "./mcpb.loader.js";
13
- export { unzipMcpb } from "./mcpb.unzip.js";
10
+ // `.mcpb` bundle loading, verifies + unpacks a bundle into a stdio upstream.
11
+ export { loadMcpbBundle } from "./mcpb/mcpb.loader";
12
+ export type { IMcpbBundleConfig, McpbBundleConfig } from "./mcpb/mcpb.loader";
13
+ export { unzipMcpb } from "./mcpb/mcpb.unzip";
14
14
 
15
- // Broker introspection — tier 1.
16
- export { BrokerInfoBehavior, BrokerProvidersBehavior, startBrokerServer, BROKER_PROVIDER_NAME } from "./broker/index.js";
17
- export type { IStartBrokerServerOptions, StartBrokerServerOptions } from "./broker/index.js";
18
- export { brokerGrammarKey, iterAvailableBrokerGrammars, iterBrokerGrammarsFrom, loadBrokerGrammar } from "./broker/index.js";
15
+ // Broker introspection, tier 1.
16
+ export { BrokerInfoBehavior, BrokerProvidersBehavior, startBrokerServer, BROKER_PROVIDER_NAME } from "./broker/index";
17
+ export type { IStartBrokerServerOptions, StartBrokerServerOptions } from "./broker/index";
18
+ export { brokerGrammarKey, iterAvailableBrokerGrammars, iterBrokerGrammarsFrom, loadBrokerGrammar } from "./broker/index";
19
19
  export type {
20
20
  IBrokerContext,
21
21
  IBrokerGrammarEntry,
@@ -26,9 +26,9 @@ export type {
26
26
  BrokerProviderTransport,
27
27
  BrokerLocale,
28
28
  BrokerUserAgent,
29
- } from "./broker/index.js";
29
+ } from "./broker/index";
30
30
 
31
- export { VERSION, PACKAGE_NAME } from "./version.js";
31
+ export { VERSION, PACKAGE_NAME } from "./version";
32
32
 
33
33
  // OAuth 2.1 resource-server authorization.
34
34
  export {
@@ -41,7 +41,7 @@ export {
41
41
  normalizeProviderAuthentication,
42
42
  providerMayPublish,
43
43
  SharedSecretProviderAuthenticator,
44
- } from "./auth/index.js";
44
+ } from "./auth/index";
45
45
  export type {
46
46
  IAccessTokenClaims,
47
47
  ITokenValidator,
@@ -65,7 +65,7 @@ export type {
65
65
  ProviderAuthenticationResult,
66
66
  ProviderAuthenticatorReturn,
67
67
  ProviderPrincipal,
68
- } from "./auth/index.js";
68
+ } from "./auth/index";
69
69
 
70
70
  // Hierarchical, domain-neutral authorization.
71
71
  export {
@@ -80,7 +80,7 @@ export {
80
80
  compileAuthorizationPolicy,
81
81
  hasAuthorizationPolicies,
82
82
  validateCapability,
83
- } from "./authorization/index.js";
83
+ } from "./authorization/index";
84
84
  export type {
85
85
  IAuditContext,
86
86
  IAuthorizationAuditConfig,
@@ -119,9 +119,9 @@ export type {
119
119
  SlotResourceResolver,
120
120
  SubjectMapper,
121
121
  SubjectMappingConfig,
122
- } from "./authorization/index.js";
122
+ } from "./authorization/index";
123
123
 
124
124
  // JSON config file used by `bin.ts` at startup. Exported so a programmatic
125
125
  // embedder can re-use the same loader against a custom path.
126
- export { loadBrokerConfig, DEFAULT_CONFIG_FILENAME } from "./config.js";
127
- export type { IBrokerAuthConfig, IBrokerConfig, ILoadedBrokerConfig, BrokerAuthConfig, BrokerConfig, LoadedBrokerConfig } from "./config.js";
126
+ export { loadBrokerConfig, DEFAULT_CONFIG_FILENAME } from "./config";
127
+ export type { IBrokerAuthConfig, IBrokerConfig, ILoadedBrokerConfig, BrokerAuthConfig, BrokerConfig, LoadedBrokerConfig } from "./config";
@@ -5,14 +5,14 @@
5
5
  * describes a stdio MCP server process. The broker:
6
6
  *
7
7
  * 1. verifies a **detached signature** of the `.mcpb` file against a trusted
8
- * public key (PEM) — integrity *and* provenance, using `node:crypto` only;
8
+ * public key (PEM), integrity *and* provenance, using `node:crypto` only;
9
9
  * 2. unpacks the archive;
10
10
  * 3. reads the manifest, expands the `mcp_config` placeholders, and produces a
11
11
  * `IStdioUpstreamConfig` that the existing upstream wiring spawns.
12
12
  *
13
13
  * The broker stays compatible with the `.mcpb` format without depending on the
14
14
  * `@anthropic-ai/mcpb` package: the bundle's *own* (native PKCS#7) signature is
15
- * never parsed — the detached signature layer is the broker's trust anchor.
15
+ * never parsed: the detached signature layer is the broker's trust anchor.
16
16
  *
17
17
  * Any failure (missing files, bad signature, malformed manifest, missing
18
18
  * `user_config` value) is logged and yields `null`: the bundle is refused and
@@ -22,8 +22,8 @@ import { createPublicKey, verify, X509Certificate, type KeyObject } from "node:c
22
22
  import { existsSync, readFileSync, rmSync } from "node:fs";
23
23
  import { homedir } from "node:os";
24
24
  import { join, resolve, sep } from "node:path";
25
- import { unzipMcpb } from "./mcpb.unzip.js";
26
- import type { IStdioUpstreamConfig } from "./stdio.upstream.js";
25
+ import { unzipMcpb } from "./mcpb.unzip";
26
+ import type { IStdioUpstreamConfig } from "../stdio.upstream";
27
27
 
28
28
  /** A `.mcpb` bundle entry from the broker config file. */
29
29
  export interface IMcpbBundleConfig {
@@ -85,7 +85,7 @@ function resolvePlaceholder(key: string, dirname: string, userConfig: IMcpbBundl
85
85
  function expandScalar(input: string, dirname: string, userConfig: IMcpbBundleConfig["userConfig"]): string {
86
86
  return input.replace(/\$\{([^}]+)\}/g, (match, key: string) => {
87
87
  const value = resolvePlaceholder(key, dirname, userConfig);
88
- if (value === undefined) return match; // unknown placeholder — leave verbatim
88
+ if (value === undefined) return match; // unknown placeholder, leave verbatim
89
89
  if (Array.isArray(value)) throw new Error(`placeholder "\${${key}}" is multi-valued and cannot be used here`);
90
90
  return String(value);
91
91
  });
@@ -122,7 +122,7 @@ export async function loadMcpbBundle(cfg: IMcpbBundleConfig, baseDir: string): P
122
122
  ["signature", signaturePath],
123
123
  ] as const) {
124
124
  if (!existsSync(file)) {
125
- console.error(`${tag}: ${label} file not found at ${file} — bundle refused.`);
125
+ console.error(`${tag}: ${label} file not found at ${file}, bundle refused.`);
126
126
  return null;
127
127
  }
128
128
  }
@@ -132,7 +132,7 @@ export async function loadMcpbBundle(cfg: IMcpbBundleConfig, baseDir: string): P
132
132
  const signatureBytes = readFileSync(signaturePath);
133
133
  const publicKey = loadPublicKey(readFileSync(publicKeyPath, "utf8"));
134
134
  if (!verifyDetachedSignature(bundleBytes, signatureBytes, publicKey)) {
135
- console.error(`${tag}: detached signature is invalid for the configured public key — bundle refused.`);
135
+ console.error(`${tag}: detached signature is invalid for the configured public key, bundle refused.`);
136
136
  return null;
137
137
  }
138
138
 
@@ -144,13 +144,13 @@ export async function loadMcpbBundle(cfg: IMcpbBundleConfig, baseDir: string): P
144
144
  // ── Manifest → mcp_config ───────────────────────────────────────────
145
145
  const manifestPath = join(outputDir, "manifest.json");
146
146
  if (!existsSync(manifestPath)) {
147
- console.error(`${tag}: manifest.json missing inside the bundle — bundle refused.`);
147
+ console.error(`${tag}: manifest.json missing inside the bundle, bundle refused.`);
148
148
  return null;
149
149
  }
150
150
  const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as { server?: { mcp_config?: IMcpConfig } };
151
151
  const mcpConfig = manifest.server?.mcp_config;
152
152
  if (!mcpConfig) {
153
- console.error(`${tag}: manifest has no server.mcp_config — bundle refused.`);
153
+ console.error(`${tag}: manifest has no server.mcp_config, bundle refused.`);
154
154
  return null;
155
155
  }
156
156
 
@@ -160,7 +160,7 @@ export async function loadMcpbBundle(cfg: IMcpbBundleConfig, baseDir: string): P
160
160
  const rawArgs = override?.args ?? mcpConfig.args ?? [];
161
161
  const rawEnv = { ...mcpConfig.env, ...override?.env };
162
162
  if (!command) {
163
- console.error(`${tag}: manifest mcp_config has no command — bundle refused.`);
163
+ console.error(`${tag}: manifest mcp_config has no command, bundle refused.`);
164
164
  return null;
165
165
  }
166
166
 
@@ -180,7 +180,7 @@ export async function loadMcpbBundle(cfg: IMcpbBundleConfig, baseDir: string): P
180
180
  aggregate: cfg.aggregate ?? true,
181
181
  };
182
182
  } catch (err) {
183
- console.error(`${tag}: ${(err as Error).message} — bundle refused.`);
183
+ console.error(`${tag}: ${(err as Error).message}, bundle refused.`);
184
184
  return null;
185
185
  }
186
186
  }
@@ -3,12 +3,12 @@
3
3
  *
4
4
  * A `.mcpb` bundle is an ordinary ZIP archive. The broker deliberately avoids
5
5
  * the `@anthropic-ai/mcpb` package so it stays compatible with the format
6
- * without being coupled to Anthropic's tooling — hence this small reader.
6
+ * without being coupled to Anthropic's tooling, hence this small reader.
7
7
  *
8
8
  * Supports the two compression methods used in practice: stored (0) and
9
9
  * deflate (8). ZIP64 archives are rejected with a clear error (`.mcpb` bundles
10
10
  * are small and never need it). Any trailing bytes after the End Of Central
11
- * Directory record — e.g. a native PKCS#7 signature block — are ignored, since
11
+ * Directory record (e.g. a native PKCS#7 signature block) are ignored, since
12
12
  * extraction is driven entirely by the central directory.
13
13
  */
14
14
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";