@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.
- package/.mcp-broker.example/CONFIGURATION-EN.md +861 -0
- package/.mcp-broker.example/CONFIGURATION-FR.md +871 -0
- package/.mcp-broker.example/README.md +8 -3
- package/.mcp-broker.example/config.json +86 -0
- package/README.md +122 -3
- package/dist/bin.d.ts +0 -1
- package/dist/bin.js +180 -208
- package/dist/bin.js.map +1 -1
- package/dist/chunk-FTDKH2C4.js +3670 -0
- package/dist/chunk-FTDKH2C4.js.map +1 -0
- package/dist/{broker/grammars → grammars}/claude/en.json +1 -1
- package/dist/{broker/grammars → grammars}/claude/fr.json +1 -1
- package/dist/index.d.ts +1790 -18
- package/dist/index.js +2 -14
- package/dist/index.js.map +1 -1
- package/package.json +14 -8
- package/scripts/copy-assets.mjs +16 -10
- package/scripts/gen-cert.mjs +5 -5
- package/scripts/pack-mcpb.mjs +5 -5
- package/scripts/sign-bundle.mjs +6 -6
- package/src/auth/auth.config.ts +98 -0
- package/src/auth/auth.types.ts +120 -0
- package/src/auth/http.auth.ts +128 -0
- package/src/auth/index.ts +34 -0
- package/src/auth/jwt.validator.ts +63 -0
- package/src/auth/provider.auth.ts +114 -0
- package/src/auth/resource.metadata.ts +34 -0
- package/src/authorization/audit.ts +35 -0
- package/src/authorization/capability.classifier.ts +114 -0
- package/src/authorization/index.ts +37 -0
- package/src/authorization/policy.engine.ts +212 -0
- package/src/authorization/policy.types.ts +105 -0
- package/src/authorization/resource.path.ts +126 -0
- package/src/authorization/runtime.ts +56 -0
- package/src/authorization/slot.resource.ts +50 -0
- package/src/authorization/subject.mapper.ts +81 -0
- package/src/bin.ts +90 -7
- package/src/broker/adapters/broker.adapter.info.ts +3 -3
- package/src/broker/adapters/broker.adapter.providers.ts +3 -3
- package/src/broker/aggregate/aggregate.catalog.ts +49 -21
- package/src/broker/aggregate/aggregate.server.ts +149 -19
- package/src/broker/aggregate/provider.client.session.ts +22 -22
- package/src/broker/behaviors/broker.behavior.info.ts +4 -4
- package/src/broker/behaviors/broker.behavior.providers.ts +6 -6
- package/src/broker/broker.context.ts +10 -4
- package/src/broker/broker.grammars.ts +16 -13
- package/src/broker/broker.server.ts +12 -9
- package/src/broker/grammars/claude/en.json +1 -1
- package/src/broker/grammars/claude/fr.json +1 -1
- package/src/broker/index.ts +9 -9
- package/src/config.ts +81 -8
- package/src/index.ts +121 -20
- package/src/{mcpb.loader.ts → mcpb/mcpb.loader.ts} +24 -21
- package/src/{mcpb.unzip.ts → mcpb/mcpb.unzip.ts} +2 -2
- package/src/remote.transports.ts +11 -8
- package/src/remote.upstream.ts +11 -8
- package/src/stdio.upstream.ts +9 -6
- package/src/upstream.ts +6 -3
- package/src/ws/ws.interfaces.ts +357 -0
- package/src/{ws.tunnel.builder.ts → ws/ws.tunnel.builder.ts} +112 -9
- package/src/{ws.tunnel.ts → ws/ws.tunnel.ts} +646 -457
- package/web/README.md +94 -0
- package/web/assets/logo.png +0 -0
- package/web/broker-self-mcp.html +338 -0
- package/web/css/styles.css +580 -0
- package/web/demos/DemoPlaceholder.html +258 -0
- package/web/demos/broker-explorer/css/app.css +393 -0
- package/web/demos/broker-explorer/index.html +94 -0
- package/web/demos/broker-explorer/js/app.js +271 -0
- package/web/demos/broker-explorer/js/mcp-ws-client.js +132 -0
- package/web/demos/oauth-lab/README.md +94 -0
- package/web/demos/oauth-lab/config.json +117 -0
- package/web/demos/oauth-lab/css/app.css +1097 -0
- package/web/demos/oauth-lab/index.html +323 -0
- package/web/demos/oauth-lab/js/app.js +654 -0
- package/web/demos/oauth-lab/server/auth-server.mjs +426 -0
- package/web/demos/oauth-lab/server/factory-provider.mjs +269 -0
- package/web/demos/oauth-lab/server/smoke-test.mjs +308 -0
- package/web/demos/oauth-lab/server/start.mjs +106 -0
- package/web/demos/provider-tunnel/css/app.css +384 -0
- package/web/demos/provider-tunnel/index.html +99 -0
- package/web/demos/provider-tunnel/js/app.js +226 -0
- package/web/demos/provider-tunnel/js/toolbox-server.js +186 -0
- package/web/index.html +558 -0
- package/web/js/lib/broker-tunnel.js +173 -0
- package/dist/broker/adapters/broker.adapter.info.d.ts +0 -16
- package/dist/broker/adapters/broker.adapter.info.js +0 -43
- package/dist/broker/adapters/broker.adapter.info.js.map +0 -1
- package/dist/broker/adapters/broker.adapter.providers.d.ts +0 -18
- package/dist/broker/adapters/broker.adapter.providers.js +0 -61
- package/dist/broker/adapters/broker.adapter.providers.js.map +0 -1
- package/dist/broker/aggregate/aggregate.catalog.d.ts +0 -54
- package/dist/broker/aggregate/aggregate.catalog.js +0 -105
- package/dist/broker/aggregate/aggregate.catalog.js.map +0 -1
- package/dist/broker/aggregate/aggregate.server.d.ts +0 -47
- package/dist/broker/aggregate/aggregate.server.js +0 -151
- package/dist/broker/aggregate/aggregate.server.js.map +0 -1
- package/dist/broker/aggregate/provider.client.session.d.ts +0 -52
- package/dist/broker/aggregate/provider.client.session.js +0 -140
- package/dist/broker/aggregate/provider.client.session.js.map +0 -1
- package/dist/broker/behaviors/broker.behavior.info.d.ts +0 -15
- package/dist/broker/behaviors/broker.behavior.info.js +0 -41
- package/dist/broker/behaviors/broker.behavior.info.js.map +0 -1
- package/dist/broker/behaviors/broker.behavior.providers.d.ts +0 -19
- package/dist/broker/behaviors/broker.behavior.providers.js +0 -69
- package/dist/broker/behaviors/broker.behavior.providers.js.map +0 -1
- package/dist/broker/broker.context.d.ts +0 -59
- package/dist/broker/broker.context.js +0 -2
- package/dist/broker/broker.context.js.map +0 -1
- package/dist/broker/broker.grammars.d.ts +0 -130
- package/dist/broker/broker.grammars.js +0 -229
- package/dist/broker/broker.grammars.js.map +0 -1
- package/dist/broker/broker.server.d.ts +0 -66
- package/dist/broker/broker.server.js +0 -73
- package/dist/broker/broker.server.js.map +0 -1
- package/dist/broker/index.d.ts +0 -9
- package/dist/broker/index.js +0 -7
- package/dist/broker/index.js.map +0 -1
- package/dist/config.d.ts +0 -136
- package/dist/config.js +0 -61
- package/dist/config.js.map +0 -1
- package/dist/mcpb.loader.d.ts +0 -24
- package/dist/mcpb.loader.js +0 -161
- package/dist/mcpb.loader.js.map +0 -1
- package/dist/mcpb.unzip.d.ts +0 -6
- package/dist/mcpb.unzip.js +0 -95
- package/dist/mcpb.unzip.js.map +0 -1
- package/dist/remote.transports.d.ts +0 -16
- package/dist/remote.transports.js +0 -297
- package/dist/remote.transports.js.map +0 -1
- package/dist/remote.upstream.d.ts +0 -36
- package/dist/remote.upstream.js +0 -52
- package/dist/remote.upstream.js.map +0 -1
- package/dist/stdio.upstream.d.ts +0 -45
- package/dist/stdio.upstream.js +0 -85
- package/dist/stdio.upstream.js.map +0 -1
- package/dist/upstream.d.ts +0 -33
- package/dist/upstream.js +0 -2
- package/dist/upstream.js.map +0 -1
- package/dist/version.d.ts +0 -2
- package/dist/version.js +0 -9
- package/dist/version.js.map +0 -1
- package/dist/ws.tunnel.builder.d.ts +0 -139
- package/dist/ws.tunnel.builder.js +0 -205
- package/dist/ws.tunnel.builder.js.map +0 -1
- package/dist/ws.tunnel.d.ts +0 -373
- package/dist/ws.tunnel.js +0 -1090
- package/dist/ws.tunnel.js.map +0 -1
- /package/dist/{broker/grammars → grammars}/default/en.json +0 -0
- /package/dist/{broker/grammars → grammars}/default/fr.json +0 -0
- /package/dist/{broker/grammars → grammars}/default/zh.json +0 -0
package/src/remote.transports.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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>):
|
|
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;
|
package/src/remote.upstream.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { createRemoteTransport, detectTransport, type
|
|
1
|
+
import type { IUpstream } from "./upstream";
|
|
2
|
+
import { createRemoteTransport, detectTransport, type IRemoteTransport, type RemoteTransportKind } from "./remote.transports";
|
|
3
3
|
|
|
4
|
-
export interface
|
|
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
|
|
22
|
+
* the transport, Streamable HTTP / SSE / WebSocket instead of a child process.
|
|
23
23
|
*/
|
|
24
|
-
export class RemoteUpstream implements
|
|
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:
|
|
33
|
-
private _transport:
|
|
32
|
+
private readonly _config: IRemoteUpstreamConfig;
|
|
33
|
+
private _transport: IRemoteTransport | null = null;
|
|
34
34
|
private _open = false;
|
|
35
35
|
|
|
36
|
-
constructor(config:
|
|
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;
|
package/src/stdio.upstream.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { spawn, ChildProcess } from "node:child_process";
|
|
2
|
-
import type {
|
|
2
|
+
import type { IUpstream } from "./upstream";
|
|
3
3
|
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
5
|
// Configuration
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
7
|
|
|
8
|
-
export interface
|
|
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
|
|
33
|
+
export class StdioUpstream implements IUpstream {
|
|
34
34
|
readonly name: string;
|
|
35
35
|
|
|
36
|
-
private readonly _config:
|
|
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:
|
|
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
|
|
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`
|
|
9
|
-
* - `RemoteUpstream
|
|
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
|
|
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;
|