@cyanmycelium/mcp-broker 0.4.0 → 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 (151) 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 +8 -3
  4. package/.mcp-broker.example/config.json +86 -0
  5. package/README.md +122 -3
  6. package/dist/bin.d.ts +0 -1
  7. package/dist/bin.js +180 -208
  8. package/dist/bin.js.map +1 -1
  9. package/dist/chunk-FTDKH2C4.js +3670 -0
  10. package/dist/chunk-FTDKH2C4.js.map +1 -0
  11. package/dist/{broker/grammars → grammars}/claude/en.json +1 -1
  12. package/dist/{broker/grammars → grammars}/claude/fr.json +1 -1
  13. package/dist/index.d.ts +1790 -18
  14. package/dist/index.js +2 -14
  15. package/dist/index.js.map +1 -1
  16. package/package.json +14 -8
  17. package/scripts/copy-assets.mjs +16 -10
  18. package/scripts/gen-cert.mjs +5 -5
  19. package/scripts/pack-mcpb.mjs +5 -5
  20. package/scripts/sign-bundle.mjs +6 -6
  21. package/src/auth/auth.config.ts +98 -0
  22. package/src/auth/auth.types.ts +120 -0
  23. package/src/auth/http.auth.ts +128 -0
  24. package/src/auth/index.ts +34 -0
  25. package/src/auth/jwt.validator.ts +63 -0
  26. package/src/auth/provider.auth.ts +114 -0
  27. package/src/auth/resource.metadata.ts +34 -0
  28. package/src/authorization/audit.ts +35 -0
  29. package/src/authorization/capability.classifier.ts +114 -0
  30. package/src/authorization/index.ts +37 -0
  31. package/src/authorization/policy.engine.ts +212 -0
  32. package/src/authorization/policy.types.ts +105 -0
  33. package/src/authorization/resource.path.ts +126 -0
  34. package/src/authorization/runtime.ts +56 -0
  35. package/src/authorization/slot.resource.ts +50 -0
  36. package/src/authorization/subject.mapper.ts +81 -0
  37. package/src/bin.ts +90 -7
  38. package/src/broker/adapters/broker.adapter.info.ts +3 -3
  39. package/src/broker/adapters/broker.adapter.providers.ts +3 -3
  40. package/src/broker/aggregate/aggregate.catalog.ts +49 -21
  41. package/src/broker/aggregate/aggregate.server.ts +149 -19
  42. package/src/broker/aggregate/provider.client.session.ts +22 -22
  43. package/src/broker/behaviors/broker.behavior.info.ts +4 -4
  44. package/src/broker/behaviors/broker.behavior.providers.ts +6 -6
  45. package/src/broker/broker.context.ts +10 -4
  46. package/src/broker/broker.grammars.ts +16 -13
  47. package/src/broker/broker.server.ts +12 -9
  48. package/src/broker/grammars/claude/en.json +1 -1
  49. package/src/broker/grammars/claude/fr.json +1 -1
  50. package/src/broker/index.ts +9 -9
  51. package/src/config.ts +81 -8
  52. package/src/index.ts +121 -20
  53. package/src/{mcpb.loader.ts → mcpb/mcpb.loader.ts} +24 -21
  54. package/src/{mcpb.unzip.ts → mcpb/mcpb.unzip.ts} +2 -2
  55. package/src/remote.transports.ts +11 -8
  56. package/src/remote.upstream.ts +11 -8
  57. package/src/stdio.upstream.ts +9 -6
  58. package/src/upstream.ts +6 -3
  59. package/src/ws/ws.interfaces.ts +357 -0
  60. package/src/{ws.tunnel.builder.ts → ws/ws.tunnel.builder.ts} +112 -9
  61. package/src/{ws.tunnel.ts → ws/ws.tunnel.ts} +646 -457
  62. package/web/README.md +94 -0
  63. package/web/assets/logo.png +0 -0
  64. package/web/broker-self-mcp.html +338 -0
  65. package/web/css/styles.css +580 -0
  66. package/web/demos/DemoPlaceholder.html +258 -0
  67. package/web/demos/broker-explorer/css/app.css +393 -0
  68. package/web/demos/broker-explorer/index.html +94 -0
  69. package/web/demos/broker-explorer/js/app.js +271 -0
  70. package/web/demos/broker-explorer/js/mcp-ws-client.js +132 -0
  71. package/web/demos/oauth-lab/README.md +94 -0
  72. package/web/demos/oauth-lab/config.json +117 -0
  73. package/web/demos/oauth-lab/css/app.css +1097 -0
  74. package/web/demos/oauth-lab/index.html +323 -0
  75. package/web/demos/oauth-lab/js/app.js +654 -0
  76. package/web/demos/oauth-lab/server/auth-server.mjs +426 -0
  77. package/web/demos/oauth-lab/server/factory-provider.mjs +269 -0
  78. package/web/demos/oauth-lab/server/smoke-test.mjs +308 -0
  79. package/web/demos/oauth-lab/server/start.mjs +106 -0
  80. package/web/demos/provider-tunnel/css/app.css +384 -0
  81. package/web/demos/provider-tunnel/index.html +99 -0
  82. package/web/demos/provider-tunnel/js/app.js +226 -0
  83. package/web/demos/provider-tunnel/js/toolbox-server.js +186 -0
  84. package/web/index.html +558 -0
  85. package/web/js/lib/broker-tunnel.js +173 -0
  86. package/dist/broker/adapters/broker.adapter.info.d.ts +0 -16
  87. package/dist/broker/adapters/broker.adapter.info.js +0 -43
  88. package/dist/broker/adapters/broker.adapter.info.js.map +0 -1
  89. package/dist/broker/adapters/broker.adapter.providers.d.ts +0 -18
  90. package/dist/broker/adapters/broker.adapter.providers.js +0 -61
  91. package/dist/broker/adapters/broker.adapter.providers.js.map +0 -1
  92. package/dist/broker/aggregate/aggregate.catalog.d.ts +0 -54
  93. package/dist/broker/aggregate/aggregate.catalog.js +0 -105
  94. package/dist/broker/aggregate/aggregate.catalog.js.map +0 -1
  95. package/dist/broker/aggregate/aggregate.server.d.ts +0 -47
  96. package/dist/broker/aggregate/aggregate.server.js +0 -151
  97. package/dist/broker/aggregate/aggregate.server.js.map +0 -1
  98. package/dist/broker/aggregate/provider.client.session.d.ts +0 -52
  99. package/dist/broker/aggregate/provider.client.session.js +0 -140
  100. package/dist/broker/aggregate/provider.client.session.js.map +0 -1
  101. package/dist/broker/behaviors/broker.behavior.info.d.ts +0 -15
  102. package/dist/broker/behaviors/broker.behavior.info.js +0 -41
  103. package/dist/broker/behaviors/broker.behavior.info.js.map +0 -1
  104. package/dist/broker/behaviors/broker.behavior.providers.d.ts +0 -19
  105. package/dist/broker/behaviors/broker.behavior.providers.js +0 -69
  106. package/dist/broker/behaviors/broker.behavior.providers.js.map +0 -1
  107. package/dist/broker/broker.context.d.ts +0 -59
  108. package/dist/broker/broker.context.js +0 -2
  109. package/dist/broker/broker.context.js.map +0 -1
  110. package/dist/broker/broker.grammars.d.ts +0 -130
  111. package/dist/broker/broker.grammars.js +0 -229
  112. package/dist/broker/broker.grammars.js.map +0 -1
  113. package/dist/broker/broker.server.d.ts +0 -66
  114. package/dist/broker/broker.server.js +0 -73
  115. package/dist/broker/broker.server.js.map +0 -1
  116. package/dist/broker/index.d.ts +0 -9
  117. package/dist/broker/index.js +0 -7
  118. package/dist/broker/index.js.map +0 -1
  119. package/dist/config.d.ts +0 -136
  120. package/dist/config.js +0 -61
  121. package/dist/config.js.map +0 -1
  122. package/dist/mcpb.loader.d.ts +0 -24
  123. package/dist/mcpb.loader.js +0 -161
  124. package/dist/mcpb.loader.js.map +0 -1
  125. package/dist/mcpb.unzip.d.ts +0 -6
  126. package/dist/mcpb.unzip.js +0 -95
  127. package/dist/mcpb.unzip.js.map +0 -1
  128. package/dist/remote.transports.d.ts +0 -16
  129. package/dist/remote.transports.js +0 -297
  130. package/dist/remote.transports.js.map +0 -1
  131. package/dist/remote.upstream.d.ts +0 -36
  132. package/dist/remote.upstream.js +0 -52
  133. package/dist/remote.upstream.js.map +0 -1
  134. package/dist/stdio.upstream.d.ts +0 -45
  135. package/dist/stdio.upstream.js +0 -85
  136. package/dist/stdio.upstream.js.map +0 -1
  137. package/dist/upstream.d.ts +0 -33
  138. package/dist/upstream.js +0 -2
  139. package/dist/upstream.js.map +0 -1
  140. package/dist/version.d.ts +0 -2
  141. package/dist/version.js +0 -9
  142. package/dist/version.js.map +0 -1
  143. package/dist/ws.tunnel.builder.d.ts +0 -139
  144. package/dist/ws.tunnel.builder.js +0 -205
  145. package/dist/ws.tunnel.builder.js.map +0 -1
  146. package/dist/ws.tunnel.d.ts +0 -373
  147. package/dist/ws.tunnel.js +0 -1090
  148. package/dist/ws.tunnel.js.map +0 -1
  149. /package/dist/{broker/grammars → grammars}/default/en.json +0 -0
  150. /package/dist/{broker/grammars → grammars}/default/fr.json +0 -0
  151. /package/dist/{broker/grammars → grammars}/default/zh.json +0 -0
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * No `@modelcontextprotocol/sdk` dependency: the broker stays SDK-free, the
5
5
  * official SDK is only used outside the broker (in the demo) to prove the
6
- * broker is implementation-agnostic. Each transport is a thin frame relay —
6
+ * broker is implementation-agnostic. Each transport is a thin frame relay ,
7
7
  * it does not interpret MCP, it pipes JSON-RPC strings both ways.
8
8
  */
9
9
  import * as http from "node:http";
@@ -12,7 +12,7 @@ import type { ClientRequest, IncomingMessage, RequestOptions } from "node:http";
12
12
  import { WebSocket } from "ws";
13
13
 
14
14
  /** Lifecycle + callbacks shared by every remote client transport. */
15
- export interface RemoteTransport {
15
+ export interface IRemoteTransport {
16
16
  onMessage: ((data: string) => void) | null;
17
17
  onOpen: (() => void) | null;
18
18
  onClose: (() => void) | null;
@@ -63,7 +63,7 @@ class SseDecoder {
63
63
  // WebSocket
64
64
  // ---------------------------------------------------------------------------
65
65
 
66
- class WebSocketRemoteTransport implements RemoteTransport {
66
+ class WebSocketRemoteTransport implements IRemoteTransport {
67
67
  onMessage: ((data: string) => void) | null = null;
68
68
  onOpen: (() => void) | null = null;
69
69
  onClose: (() => void) | null = null;
@@ -99,7 +99,7 @@ class WebSocketRemoteTransport implements RemoteTransport {
99
99
  // Legacy MCP SSE (GET event stream + POST message endpoint)
100
100
  // ---------------------------------------------------------------------------
101
101
 
102
- class SseRemoteTransport implements RemoteTransport {
102
+ class SseRemoteTransport implements IRemoteTransport {
103
103
  onMessage: ((data: string) => void) | null = null;
104
104
  onOpen: (() => void) | null = null;
105
105
  onClose: (() => void) | null = null;
@@ -163,7 +163,7 @@ class SseRemoteTransport implements RemoteTransport {
163
163
  // Streamable HTTP (MCP 2025-03-26)
164
164
  // ---------------------------------------------------------------------------
165
165
 
166
- class StreamableHttpRemoteTransport implements RemoteTransport {
166
+ class StreamableHttpRemoteTransport implements IRemoteTransport {
167
167
  onMessage: ((data: string) => void) | null = null;
168
168
  onOpen: (() => void) | null = null;
169
169
  onClose: (() => void) | null = null;
@@ -179,7 +179,7 @@ class StreamableHttpRemoteTransport implements RemoteTransport {
179
179
  ) {}
180
180
 
181
181
  connect(): void {
182
- // Streamable HTTP is stateless — there is no connection to open. The
182
+ // Streamable HTTP is stateless, there is no connection to open. The
183
183
  // transport is usable as soon as connect() is called; the session id
184
184
  // is captured later from the first response that carries one.
185
185
  this._closed = false;
@@ -244,7 +244,7 @@ class StreamableHttpRemoteTransport implements RemoteTransport {
244
244
  return;
245
245
  }
246
246
  } catch {
247
- /* not JSON — forward verbatim */
247
+ /* not JSON, forward verbatim */
248
248
  }
249
249
  this.onMessage?.(trimmed);
250
250
  }
@@ -291,7 +291,7 @@ class StreamableHttpRemoteTransport implements RemoteTransport {
291
291
  // ---------------------------------------------------------------------------
292
292
 
293
293
  /** Builds the transport of the given kind. */
294
- export function createRemoteTransport(url: string, kind: RemoteTransportKind, headers: Record<string, string>): RemoteTransport {
294
+ export function createRemoteTransport(url: string, kind: RemoteTransportKind, headers: Record<string, string>): IRemoteTransport {
295
295
  switch (kind) {
296
296
  case "websocket":
297
297
  return new WebSocketRemoteTransport(url, headers);
@@ -302,6 +302,9 @@ export function createRemoteTransport(url: string, kind: RemoteTransportKind, he
302
302
  }
303
303
  }
304
304
 
305
+ /** @deprecated Use {@link IRemoteTransport}. */
306
+ export type RemoteTransport = IRemoteTransport;
307
+
305
308
  /** Heuristic transport detection from the URL when none is configured. */
306
309
  export function detectTransport(url: string): RemoteTransportKind {
307
310
  let parsed: URL;
@@ -1,7 +1,7 @@
1
- import type { Upstream } from "./upstream.js";
2
- import { createRemoteTransport, detectTransport, type RemoteTransport, type RemoteTransportKind } from "./remote.transports.js";
1
+ import type { IUpstream } from "./upstream";
2
+ import { createRemoteTransport, detectTransport, type IRemoteTransport, type RemoteTransportKind } from "./remote.transports";
3
3
 
4
- export interface RemoteUpstreamConfig {
4
+ export interface IRemoteUpstreamConfig {
5
5
  /** Provider slot name this upstream is bound to. */
6
6
  name: string;
7
7
  /** URL of the remote MCP server. */
@@ -19,9 +19,9 @@ export interface RemoteUpstreamConfig {
19
19
  *
20
20
  * The broker-facing contract is identical to {@link StdioUpstream}: the broker
21
21
  * muxes the slot's clients onto this single upstream. The only difference is
22
- * the transport — Streamable HTTP / SSE / WebSocket instead of a child process.
22
+ * the transport, Streamable HTTP / SSE / WebSocket instead of a child process.
23
23
  */
24
- export class RemoteUpstream implements Upstream {
24
+ export class RemoteUpstream implements IUpstream {
25
25
  readonly name: string;
26
26
 
27
27
  onMessage: ((data: string) => void) | null = null;
@@ -29,11 +29,11 @@ export class RemoteUpstream implements Upstream {
29
29
  onClose: (() => void) | null = null;
30
30
  onError: ((error: Error) => void) | null = null;
31
31
 
32
- private readonly _config: RemoteUpstreamConfig;
33
- private _transport: RemoteTransport | null = null;
32
+ private readonly _config: IRemoteUpstreamConfig;
33
+ private _transport: IRemoteTransport | null = null;
34
34
  private _open = false;
35
35
 
36
- constructor(config: RemoteUpstreamConfig) {
36
+ constructor(config: IRemoteUpstreamConfig) {
37
37
  this.name = config.name;
38
38
  this._config = config;
39
39
  }
@@ -73,3 +73,6 @@ export class RemoteUpstream implements Upstream {
73
73
  this._transport = null;
74
74
  }
75
75
  }
76
+
77
+ /** @deprecated Use {@link IRemoteUpstreamConfig}. */
78
+ export type RemoteUpstreamConfig = IRemoteUpstreamConfig;
@@ -1,11 +1,11 @@
1
1
  import { spawn, ChildProcess } from "node:child_process";
2
- import type { Upstream } from "./upstream.js";
2
+ import type { IUpstream } from "./upstream";
3
3
 
4
4
  // ---------------------------------------------------------------------------
5
5
  // Configuration
6
6
  // ---------------------------------------------------------------------------
7
7
 
8
- export interface StdioUpstreamConfig {
8
+ export interface IStdioUpstreamConfig {
9
9
  /** Logical name of this provider (matched against incoming WebSocket provider names). */
10
10
  name: string;
11
11
  /** Executable to spawn (e.g. `"node"`, `"python"`, an absolute path). */
@@ -30,10 +30,10 @@ export interface StdioUpstreamConfig {
30
30
  * One instance per configured provider. The broker uses this to bridge
31
31
  * WebSocket/SSE/HTTP clients to local MCP server processes.
32
32
  */
33
- export class StdioUpstream implements Upstream {
33
+ export class StdioUpstream implements IUpstream {
34
34
  readonly name: string;
35
35
 
36
- private readonly _config: StdioUpstreamConfig;
36
+ private readonly _config: IStdioUpstreamConfig;
37
37
  private _proc: ChildProcess | null = null;
38
38
  private _buffer = "";
39
39
  private _open = false;
@@ -51,7 +51,7 @@ export class StdioUpstream implements Upstream {
51
51
  /** Called on spawn or runtime errors. */
52
52
  onError: ((error: Error) => void) | null = null;
53
53
 
54
- constructor(config: StdioUpstreamConfig) {
54
+ constructor(config: IStdioUpstreamConfig) {
55
55
  this.name = config.name;
56
56
  this._config = config;
57
57
  }
@@ -72,7 +72,7 @@ export class StdioUpstream implements Upstream {
72
72
 
73
73
  this._proc.on("error", (err: Error) => {
74
74
  this._open = false;
75
- this.onError?.(new Error(`StdioUpstream "${this.name}": process error — ${err.message}`));
75
+ this.onError?.(new Error(`StdioUpstream "${this.name}": process error, ${err.message}`));
76
76
  });
77
77
 
78
78
  this._proc.on("spawn", () => {
@@ -115,3 +115,6 @@ export class StdioUpstream implements Upstream {
115
115
  this._proc = null;
116
116
  }
117
117
  }
118
+
119
+ /** @deprecated Use {@link IStdioUpstreamConfig}. */
120
+ export type StdioUpstreamConfig = IStdioUpstreamConfig;
package/src/upstream.ts CHANGED
@@ -5,13 +5,13 @@
5
5
  * onto the single upstream connection, regardless of the underlying transport.
6
6
  *
7
7
  * Implementations:
8
- * - `StdioUpstream` — a local child process (newline-delimited JSON-RPC over stdio).
9
- * - `RemoteUpstream` — a remote MCP server reached by URL (Streamable HTTP / SSE / WebSocket).
8
+ * - `StdioUpstream` : a local child process (newline-delimited JSON-RPC over stdio).
9
+ * - `RemoteUpstream`: a remote MCP server reached by URL (Streamable HTTP / SSE / WebSocket).
10
10
  *
11
11
  * `StdioUpstream` is reused as-is by the future `.mcpb` bundle loader: a bundle
12
12
  * is a local server whose `mcp_config` maps directly onto a stdio upstream.
13
13
  */
14
- export interface Upstream {
14
+ export interface IUpstream {
15
15
  /** Provider slot name this upstream is bound to. */
16
16
  readonly name: string;
17
17
  /** Whether the upstream connection is currently usable. */
@@ -31,3 +31,6 @@ export interface Upstream {
31
31
  /** Closes the upstream connection. */
32
32
  close(): void;
33
33
  }
34
+
35
+ /** @deprecated Use {@link IUpstream}. */
36
+ export type Upstream = IUpstream;
@@ -0,0 +1,357 @@
1
+ import type { ServerResponse } from "http";
2
+ import type { WebSocket } from "ws";
3
+ import type { GrammarResolverOptions, IMessageTransport } from "@cyanmycelium/mcp-core";
4
+ import type { StreamableHttpEndpoint } from "@cyanmycelium/mcp-core/node";
5
+ import type { IStdioUpstreamConfig } from "../stdio.upstream";
6
+ import type { IRemoteUpstreamConfig } from "../remote.upstream";
7
+ import type { IResolvedAuth, IProviderAuthenticator, IPrincipal } from "../auth/index";
8
+ import type { IPolicyAuthorization, ISlotResourceResolver } from "../authorization/index";
9
+
10
+ /**
11
+ * Every type the WebSocket tunnel exchanges or is configured with.
12
+ *
13
+ * Split out of `ws.tunnel.ts` so the contract can be read without wading
14
+ * through the implementation: the options an operator sets, the handles the
15
+ * broker hands to in-process components, and the per-slot state the relay
16
+ * keeps. The internal ones are exported for the tunnel's own modules and are
17
+ * deliberately absent from the package entry point.
18
+ */
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Internal types
22
+ // ---------------------------------------------------------------------------
23
+
24
+ /**
25
+ * Where a JSON-RPC response should be delivered.
26
+ * Either a WebSocket socket (raw WS client), a legacy SSE session, a Streamable
27
+ * HTTP session, the process stdout (stdio transport for Claude Desktop), or an
28
+ * in-process internal client (e.g. the aggregate server).
29
+ *
30
+ * The Streamable HTTP sink names a **session**, not a held-open response: which
31
+ * HTTP exchange a frame answers is worked out by `HttpSessionTransport`, from
32
+ * the JSON-RPC id. The broker only has to know which session asked.
33
+ */
34
+ export type ResponseSink =
35
+ | { type: "ws"; socket: WebSocket }
36
+ | { type: "sse"; sessionId: string }
37
+ | { type: "http-session"; sessionId: string }
38
+ | { type: "stdio" }
39
+ | { type: "internal"; client: IInternalClient };
40
+
41
+ /**
42
+ * The client-transport handlers a provider route can resolve to.
43
+ *
44
+ * `mcp` covers GET, POST and DELETE at once, because the whole Streamable HTTP
45
+ * state machine is delegated to `StreamableHttpEndpoint`: the broker routes to
46
+ * it and no longer decides per method what to do.
47
+ */
48
+ export type McpEndpointKind = "mcp" | "sse-connect" | "sse-message";
49
+
50
+ /**
51
+ * How `/<slot>/mcp` decides whether a browser origin may reach it.
52
+ *
53
+ * - a list of origins, matched exactly against the whole `Origin` header
54
+ * - a `RegExp`, tested against the whole header
55
+ * - a predicate, when the decision needs more than the string
56
+ */
57
+ export type AllowedOrigins = readonly string[] | RegExp | ((origin: string) => boolean);
58
+
59
+ /** One Streamable HTTP session attached to a provider slot. */
60
+ export interface IHttpSession {
61
+ /** The session's server-side transport, as handed over by the endpoint. */
62
+ readonly transport: IMessageTransport;
63
+
64
+ /**
65
+ * The caller behind the most recent request on this session.
66
+ *
67
+ * Refreshed per request rather than fixed at creation: the frame-level
68
+ * policy check needs the identity that presented the current token, and a
69
+ * session outlives the token it started with.
70
+ */
71
+ principal: IPrincipal | null;
72
+ }
73
+
74
+ /**
75
+ * All mutable state for one named provider slot.
76
+ * Created lazily on first client connection; the WebSocket field is set when
77
+ * the provider actually connects (and cleared on disconnect).
78
+ */
79
+ export interface IProviderState {
80
+ /** The active provider WebSocket, or `null` when the provider is not connected. */
81
+ ws: WebSocket | null;
82
+ /** Pending JSON-RPC request ids → response sinks waiting for a reply. */
83
+ readonly pending: Map<string | number, ResponseSink>;
84
+ /** Active legacy SSE sessions (Claude), keyed by session id. */
85
+ readonly sseSessions: Map<string, ServerResponse>;
86
+ /** Active Streamable HTTP sessions, keyed by `Mcp-Session-Id`. */
87
+ readonly httpSessions: Map<string, IHttpSession>;
88
+ /** Raw WebSocket MCP clients connected to this provider. */
89
+ readonly wsClients: Set<WebSocket>;
90
+ /** In-process clients (e.g. the aggregate server) attached to this slot. */
91
+ readonly internalClients: Set<IInternalClient>;
92
+ /** Lazily built Streamable HTTP endpoint serving this slot. */
93
+ httpEndpoint: StreamableHttpEndpoint | null;
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Public types
98
+ // ---------------------------------------------------------------------------
99
+
100
+ /**
101
+ * A single static-file mount: serves the contents of `dir` under `urlPrefix`.
102
+ *
103
+ * @example
104
+ * { urlPrefix: "/", dir: "/absolute/path/to/www" }
105
+ * { urlPrefix: "/bundle", dir: "/absolute/path/to/bundle" }
106
+ */
107
+ export interface IStaticMount {
108
+ /** URL prefix that triggers this mount (e.g. `"/"` or `"/bundle"`). */
109
+ urlPrefix: string;
110
+ /** Absolute path to the directory to serve. */
111
+ dir: string;
112
+ }
113
+
114
+ /**
115
+ * In-process client handle for a provider slot: the symmetric counterpart of
116
+ * {@link WsTunnel.registerLoopbackProvider}. Lets a component inside the broker
117
+ * process (e.g. the aggregate server) issue MCP requests to a provider slot and
118
+ * receive both the responses and the provider's broadcast notifications,
119
+ * without opening a real network connection.
120
+ */
121
+ export interface IInternalClient {
122
+ /**
123
+ * Sends a JSON-RPC message to the provider slot. When the message carries an
124
+ * `id`, the matching response is delivered to {@link onMessage}. When the
125
+ * provider is not connected, a JSON-RPC error is delivered synchronously.
126
+ */
127
+ send(message: string): void;
128
+ /** Receives responses to this client's requests and the provider's notifications. */
129
+ onMessage: ((data: string) => void) | null;
130
+ /** Fires when the provider slot loses its connection. */
131
+ onClose: (() => void) | null;
132
+ /** Detaches this internal client; pending requests are dropped. */
133
+ close(): void;
134
+ }
135
+
136
+ /**
137
+ * Configuration options for a {@link WsTunnel} instance.
138
+ */
139
+ export interface IWsTunnelOptions {
140
+ /** TCP port to listen on. */
141
+ port: number;
142
+
143
+ /**
144
+ * Host/interface to bind to.
145
+ * @default "0.0.0.0"
146
+ */
147
+ host?: string;
148
+
149
+ /**
150
+ * URL path **prefix** the MCP provider connects to via WebSocket.
151
+ * Each provider appends its name: `<providerPath>/<encodedName>`.
152
+ * @default "/provider"
153
+ */
154
+ providerPath?: string;
155
+
156
+ /**
157
+ * URL path for **multiplexed** provider connections.
158
+ * A single WebSocket carries traffic for multiple providers using the
159
+ * envelope protocol `{ provider: string, payload: object }`.
160
+ * @default "/providers"
161
+ */
162
+ providersPath?: string;
163
+
164
+ /**
165
+ * URL path raw WebSocket MCP clients connect to.
166
+ * @default "/"
167
+ */
168
+ clientPath?: string;
169
+
170
+ /**
171
+ * **Suffix** appended to a provider name for the SSE endpoint.
172
+ * Full URL: `/<providerName>/sse`
173
+ * @default "/sse"
174
+ */
175
+ ssePath?: string;
176
+
177
+ /**
178
+ * **Suffix** appended to a provider name for the legacy SSE POST endpoint.
179
+ * Full URL: `/<providerName>/messages`
180
+ * @default "/messages"
181
+ */
182
+ messagesPath?: string;
183
+
184
+ /**
185
+ * **Suffix** appended to a provider name for the Streamable HTTP endpoint (MCP 2025-03-26).
186
+ * Full URL: `/<providerName>/mcp`
187
+ * MCP Inspector connects here.
188
+ * @default "/mcp"
189
+ */
190
+ mcpPath?: string;
191
+
192
+ /**
193
+ * URL path that returns a `{ files: string[] }` JSON listing of every file
194
+ * inside the `samples/` subdirectory of the root static mount.
195
+ * @default "/__samples_index__"
196
+ */
197
+ samplesIndexPath?: string;
198
+
199
+ /**
200
+ * Browser origins allowed to reach `/<slot>/mcp`.
201
+ *
202
+ * The MCP specification requires the `Origin` header to be validated,
203
+ * because without it any web page the operator's browser happens to load
204
+ * can drive a broker that machine can reach. A request carrying **no**
205
+ * `Origin` is always allowed: that covers every non-browser client, which
206
+ * is what Claude Desktop, MCP Inspector and the server-side SDKs are.
207
+ *
208
+ * Omit this and every browser origin is refused with `403`. The default is
209
+ * deliberately closed; opening it is an operator decision.
210
+ *
211
+ * @default undefined, no browser origin is allowed
212
+ */
213
+ allowedOrigins?: AllowedOrigins;
214
+
215
+ /**
216
+ * Optional static-file mounts served over plain HTTP.
217
+ * Matched by longest URL prefix; directory requests fall back to `index.html`.
218
+ */
219
+ staticMounts?: IStaticMount[];
220
+
221
+ /**
222
+ * Stdio upstream providers. Each entry spawns a child process and wires its
223
+ * stdin/stdout as an MCP transport. Clients reach the process using its `name`
224
+ * directly.
225
+ *
226
+ * If a WebSocket provider connects with the same name as a stdio upstream, the
227
+ * connection is rejected and a warning is logged, stdio takes priority.
228
+ * @default undefined: no stdio providers
229
+ */
230
+ stdioUpstreams?: IStdioUpstreamConfig[];
231
+
232
+ /** Remote MCP servers reached by URL, exposed as provider slots. */
233
+ remoteUpstreams?: IRemoteUpstreamConfig[];
234
+
235
+ /**
236
+ * Stdio client transport. When set, the broker reads JSON-RPC from
237
+ * `process.stdin` and writes responses to `process.stdout`, bridging an
238
+ * external MCP client (e.g. Claude Desktop) to the named provider.
239
+ *
240
+ * In this mode ALL logging is redirected to stderr so stdout stays clean
241
+ * for the JSON-RPC stream.
242
+ *
243
+ * Claude Desktop config example:
244
+ * ```json
245
+ * {
246
+ * "command": "npx",
247
+ * "args": ["-y", "@cyanmycelium/mcp-broker"],
248
+ * "env": { "MCP_BROKER_STDIO_PROVIDER": "my-provider" }
249
+ * }
250
+ * ```
251
+ * @default undefined, stdio client transport disabled
252
+ */
253
+ stdioClient?: { providerName: string };
254
+
255
+ /**
256
+ * TLS configuration. When provided, the server uses HTTPS and WSS instead of HTTP and WS.
257
+ * Both `cert` and `key` must be PEM-encoded strings (file contents, not file paths).
258
+ * Use {@link WsTunnelBuilder.withTlsFiles} to load from disk paths.
259
+ * @default undefined, plain HTTP/WS
260
+ */
261
+ tls?: {
262
+ /** PEM-encoded TLS certificate. */
263
+ cert: string;
264
+ /** PEM-encoded private key. */
265
+ key: string;
266
+ };
267
+
268
+ /**
269
+ * When `true` (default), the broker exposes itself as an MCP server under the
270
+ * reserved slot `_broker`. Tier-1 behaviors (`broker_info`, `providers_list`,
271
+ * `provider_status`) become callable at `<host>/_broker/mcp`.
272
+ *
273
+ * Set to `false` to keep the broker invisible to MCP clients.
274
+ * @default true
275
+ */
276
+ enableBrokerProvider?: boolean;
277
+
278
+ /**
279
+ * When `true` (default), the broker exposes the reserved slot `_all`: an
280
+ * aggregate MCP server that unions the tools and prompts of every provider
281
+ * that opted in via the registration handshake. Reachable like any other
282
+ * slot (`<host>/_all/mcp`, etc.).
283
+ *
284
+ * Set to `false` to disable aggregation entirely.
285
+ * @default true
286
+ */
287
+ enableAggregateProvider?: boolean;
288
+
289
+ /**
290
+ * Logical name reported by `broker_info`. Useful when running multiple
291
+ * broker instances and you want to tell them apart from the agent side
292
+ * (e.g. `"broker-eu-west"`).
293
+ * @default PACKAGE_NAME, `@cyanmycelium/mcp-broker`
294
+ */
295
+ brokerName?: string;
296
+
297
+ /**
298
+ * Overrides for the embedded broker server's grammar resolver, passed
299
+ * straight through to `mcp-core`'s `grammarResolverFromOptions`. The
300
+ * broker installs its own default `localeSource` (reads
301
+ * `process.env.MCP_BROKER_LOCALE`); anything you set here wins.
302
+ *
303
+ * Use this to inject a custom `localeSource` (e.g. read from an HTTP
304
+ * header proxied by your transport), enable the optional `versionFrom`
305
+ * dimension, or extend the `agents` map with additional LLM families.
306
+ */
307
+ brokerGrammarResolverOptions?: Partial<GrammarResolverOptions>;
308
+
309
+ /**
310
+ * Path to a user-supplied grammars directory whose `<userAgent>/<locale>.json`
311
+ * files are registered alongside the packaged grammars used by the
312
+ * embedded broker server. Typically pointed at `.mcp-broker/grammars/`.
313
+ *
314
+ * The candidate-chain resolution in `McpServer.initialize`
315
+ * (mcp-core@0.3.0) handles cascade across user-agent and locale
316
+ * dimensions, so partial files no longer need to be pre-merged with a
317
+ * baseline.
318
+ */
319
+ brokerLocalGrammarsDir?: string;
320
+
321
+ /**
322
+ * OAuth 2.1 resource-server authorization. When set, every HTTP client
323
+ * request to a slot (`/<slot>/mcp`, `/<slot>/sse`, `/<slot>/messages`) must
324
+ * carry a valid `Authorization: Bearer` token issued for that slot, and the
325
+ * broker publishes Protected Resource Metadata (RFC 9728) under
326
+ * `/.well-known/oauth-protected-resource/<slot>/<mcp>`.
327
+ *
328
+ * When `undefined` (default), the broker performs **no** authentication ,
329
+ * appropriate only behind a trusted network boundary.
330
+ */
331
+ auth?: IResolvedAuth;
332
+
333
+ /**
334
+ * Authenticates **providers** (engines) connecting to `/provider/<slot>` and
335
+ * the multiplexed `/providers` socket. Independent of {@link auth} (which
336
+ * guards clients): set this to stop strangers from occupying a free slot and
337
+ * impersonating the real engine.
338
+ *
339
+ * When `undefined` (default), provider connections are **not** authenticated.
340
+ */
341
+ providerAuth?: IProviderAuthenticator;
342
+
343
+ /** Hierarchical policy runtime. Absent preserves legacy OAuth behavior. */
344
+ authorization?: IPolicyAuthorization;
345
+
346
+ /** Slot-to-resource resolver also used for provider namespace restrictions. */
347
+ slotResourceResolver?: ISlotResourceResolver;
348
+ }
349
+
350
+ /** @deprecated Use {@link IStaticMount}. */
351
+ export type StaticMount = IStaticMount;
352
+
353
+ /** @deprecated Use {@link IInternalClient}. */
354
+ export type InternalClient = IInternalClient;
355
+
356
+ /** @deprecated Use {@link IWsTunnelOptions}. */
357
+ export type WsTunnelOptions = IWsTunnelOptions;