@cyanmycelium/mcp-broker 0.2.0 → 0.3.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/README.md +23 -0
- package/.mcp-broker.example/config.json +11 -0
- package/README.md +1 -16
- package/dist/bin.js +30 -3
- package/dist/bin.js.map +1 -1
- package/dist/broker/aggregate/aggregate.catalog.d.ts +54 -0
- package/dist/broker/aggregate/aggregate.catalog.js +105 -0
- package/dist/broker/aggregate/aggregate.catalog.js.map +1 -0
- package/dist/broker/aggregate/aggregate.server.d.ts +47 -0
- package/dist/broker/aggregate/aggregate.server.js +151 -0
- package/dist/broker/aggregate/aggregate.server.js.map +1 -0
- package/dist/broker/aggregate/provider.client.session.d.ts +52 -0
- package/dist/broker/aggregate/provider.client.session.js +140 -0
- package/dist/broker/aggregate/provider.client.session.js.map +1 -0
- package/dist/broker/broker.server.js +12 -1
- package/dist/broker/broker.server.js.map +1 -1
- package/dist/config.d.ts +35 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mcpb.loader.d.ts +24 -0
- package/dist/mcpb.loader.js +161 -0
- package/dist/mcpb.loader.js.map +1 -0
- package/dist/mcpb.unzip.d.ts +6 -0
- package/dist/mcpb.unzip.js +95 -0
- package/dist/mcpb.unzip.js.map +1 -0
- package/dist/remote.transports.d.ts +16 -0
- package/dist/remote.transports.js +297 -0
- package/dist/remote.transports.js.map +1 -0
- package/dist/remote.upstream.d.ts +36 -0
- package/dist/remote.upstream.js +52 -0
- package/dist/remote.upstream.js.map +1 -0
- package/dist/stdio.upstream.d.ts +4 -1
- package/dist/stdio.upstream.js.map +1 -1
- package/dist/upstream.d.ts +33 -0
- package/dist/upstream.js +2 -0
- package/dist/upstream.js.map +1 -0
- package/dist/ws.tunnel.builder.d.ts +14 -8
- package/dist/ws.tunnel.builder.js +17 -9
- package/dist/ws.tunnel.builder.js.map +1 -1
- package/dist/ws.tunnel.d.ts +67 -2
- package/dist/ws.tunnel.js +200 -79
- package/dist/ws.tunnel.js.map +1 -1
- package/package.json +3 -2
- package/scripts/pack-mcpb.mjs +84 -0
- package/scripts/sign-bundle.mjs +61 -0
- package/src/bin.ts +32 -3
- package/src/broker/aggregate/aggregate.catalog.ts +145 -0
- package/src/broker/aggregate/aggregate.server.ts +178 -0
- package/src/broker/aggregate/provider.client.session.ts +172 -0
- package/src/broker/broker.server.ts +12 -1
- package/src/config.ts +37 -0
- package/src/index.ts +8 -0
- package/src/mcpb.loader.ts +186 -0
- package/src/mcpb.unzip.ts +103 -0
- package/src/remote.transports.ts +316 -0
- package/src/remote.upstream.ts +75 -0
- package/src/stdio.upstream.ts +4 -1
- package/src/upstream.ts +33 -0
- package/src/ws.tunnel.builder.ts +19 -9
- package/src/ws.tunnel.ts +239 -74
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/** MCP protocol version the aggregate sessions negotiate with sub-providers. */
|
|
2
|
+
const PROTOCOL_VERSION = "2024-11-05";
|
|
3
|
+
/** Per-request timeout for sub-provider calls. */
|
|
4
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
5
|
+
/**
|
|
6
|
+
* A hand-rolled JSON-RPC client session to one aggregated provider, running
|
|
7
|
+
* over an in-process {@link InternalClient}.
|
|
8
|
+
*
|
|
9
|
+
* Performs the MCP `initialize` handshake, caches the provider's `tools/list`
|
|
10
|
+
* and `prompts/list`, and re-fetches them when the provider emits a
|
|
11
|
+
* `list_changed` notification. `tools/call` and `prompts/get` are forwarded and
|
|
12
|
+
* their raw result or error relayed back unchanged.
|
|
13
|
+
*/
|
|
14
|
+
export class ProviderClientSession {
|
|
15
|
+
provider;
|
|
16
|
+
_client;
|
|
17
|
+
_idPrefix;
|
|
18
|
+
_pending = new Map();
|
|
19
|
+
_nextId = 0;
|
|
20
|
+
_tools = [];
|
|
21
|
+
_prompts = [];
|
|
22
|
+
_closed = false;
|
|
23
|
+
/** Fires after the cached catalog changes (initial load or `list_changed`). */
|
|
24
|
+
onCatalogChanged = null;
|
|
25
|
+
/** Fires when the underlying provider slot disconnects. */
|
|
26
|
+
onClosed = null;
|
|
27
|
+
constructor(provider, client) {
|
|
28
|
+
this.provider = provider;
|
|
29
|
+
this._client = client;
|
|
30
|
+
this._idPrefix = `agg-${provider}-`;
|
|
31
|
+
client.onMessage = (data) => this._handleMessage(data);
|
|
32
|
+
client.onClose = () => {
|
|
33
|
+
if (this._closed)
|
|
34
|
+
return;
|
|
35
|
+
this._rejectAll("provider disconnected");
|
|
36
|
+
this.onClosed?.();
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
get tools() {
|
|
40
|
+
return this._tools;
|
|
41
|
+
}
|
|
42
|
+
get prompts() {
|
|
43
|
+
return this._prompts;
|
|
44
|
+
}
|
|
45
|
+
/** Runs the `initialize` handshake and the first catalog fetch. */
|
|
46
|
+
async initialize() {
|
|
47
|
+
await this._request("initialize", {
|
|
48
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
49
|
+
capabilities: {},
|
|
50
|
+
clientInfo: { name: "mcp-broker-aggregate", version: "0" },
|
|
51
|
+
});
|
|
52
|
+
this._client.send(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }));
|
|
53
|
+
await this._refresh();
|
|
54
|
+
}
|
|
55
|
+
/** Forwards a `tools/call` to the provider, relaying the raw outcome. */
|
|
56
|
+
callTool(name, args) {
|
|
57
|
+
return this._request("tools/call", { name, arguments: args });
|
|
58
|
+
}
|
|
59
|
+
/** Forwards a `prompts/get` to the provider, relaying the raw outcome. */
|
|
60
|
+
getPrompt(name, args) {
|
|
61
|
+
return this._request("prompts/get", { name, arguments: args });
|
|
62
|
+
}
|
|
63
|
+
/** Detaches the session and its internal client. */
|
|
64
|
+
close() {
|
|
65
|
+
if (this._closed)
|
|
66
|
+
return;
|
|
67
|
+
this._closed = true;
|
|
68
|
+
this._rejectAll("session closed");
|
|
69
|
+
this._client.close();
|
|
70
|
+
}
|
|
71
|
+
async _refresh() {
|
|
72
|
+
this._tools = await this._listAll("tools/list", "tools");
|
|
73
|
+
this._prompts = await this._listAll("prompts/list", "prompts");
|
|
74
|
+
this.onCatalogChanged?.();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Calls a list method, following `nextCursor` pagination. A provider that
|
|
78
|
+
* does not implement the primitive answers with an error, which is treated
|
|
79
|
+
* as an empty list.
|
|
80
|
+
*/
|
|
81
|
+
async _listAll(method, key) {
|
|
82
|
+
const items = [];
|
|
83
|
+
let cursor;
|
|
84
|
+
do {
|
|
85
|
+
const { result, error } = await this._request(method, cursor ? { cursor } : {});
|
|
86
|
+
if (error)
|
|
87
|
+
return [];
|
|
88
|
+
const page = (result ?? {});
|
|
89
|
+
const list = page[key];
|
|
90
|
+
if (Array.isArray(list))
|
|
91
|
+
items.push(...list);
|
|
92
|
+
cursor = typeof page.nextCursor === "string" ? page.nextCursor : undefined;
|
|
93
|
+
} while (cursor);
|
|
94
|
+
return items;
|
|
95
|
+
}
|
|
96
|
+
_request(method, params) {
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
if (this._closed) {
|
|
99
|
+
resolve({ error: { code: -32000, message: "session closed" } });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const id = this._idPrefix + String(++this._nextId);
|
|
103
|
+
const timer = setTimeout(() => {
|
|
104
|
+
this._pending.delete(id);
|
|
105
|
+
resolve({ error: { code: -32000, message: `request "${method}" timed out` } });
|
|
106
|
+
}, REQUEST_TIMEOUT_MS);
|
|
107
|
+
this._pending.set(id, { resolve, timer });
|
|
108
|
+
this._client.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
_handleMessage(data) {
|
|
112
|
+
let msg;
|
|
113
|
+
try {
|
|
114
|
+
msg = JSON.parse(data);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (typeof msg.id === "string") {
|
|
120
|
+
const pending = this._pending.get(msg.id);
|
|
121
|
+
if (pending) {
|
|
122
|
+
this._pending.delete(msg.id);
|
|
123
|
+
clearTimeout(pending.timer);
|
|
124
|
+
pending.resolve({ result: msg.result, error: msg.error });
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (msg.id == null && (msg.method === "notifications/tools/list_changed" || msg.method === "notifications/prompts/list_changed")) {
|
|
129
|
+
void this._refresh();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
_rejectAll(reason) {
|
|
133
|
+
for (const pending of this._pending.values()) {
|
|
134
|
+
clearTimeout(pending.timer);
|
|
135
|
+
pending.resolve({ error: { code: -32000, message: reason } });
|
|
136
|
+
}
|
|
137
|
+
this._pending.clear();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=provider.client.session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.client.session.js","sourceRoot":"","sources":["../../../src/broker/aggregate/provider.client.session.ts"],"names":[],"mappings":"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"}
|
|
@@ -31,7 +31,18 @@ export async function startBrokerServer(context, options = {}) {
|
|
|
31
31
|
const userAgentResolver = options.userAgentResolver ?? defaultBrokerUserAgentResolver;
|
|
32
32
|
const localeSource = options.localeSource ?? (() => process.env["MCP_BROKER_LOCALE"]);
|
|
33
33
|
const [serverEnd, clientEnd] = LoopbackTransport.createPair();
|
|
34
|
-
|
|
34
|
+
// Without an initializer, McpServerBuilder reports `version: "0.0.0"` in the
|
|
35
|
+
// `initialize` handshake. Supply the real package version from the context.
|
|
36
|
+
const builder = new McpServerBuilder()
|
|
37
|
+
.withName(BROKER_PROVIDER_NAME)
|
|
38
|
+
.withTransport(serverEnd)
|
|
39
|
+
.withInitializer({
|
|
40
|
+
initialize: () => ({
|
|
41
|
+
protocolVersion: "2024-11-05",
|
|
42
|
+
serverInfo: { name: BROKER_PROVIDER_NAME, version: context.version },
|
|
43
|
+
}),
|
|
44
|
+
})
|
|
45
|
+
.register(new BrokerInfoBehavior(context), new BrokerProvidersBehavior(context));
|
|
35
46
|
// Discover every grammar by scanning two directories:
|
|
36
47
|
// 1. The packaged grammars shipped with the broker.
|
|
37
48
|
// 2. Optionally, a user-supplied directory whose entries are merged
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broker.server.js","sourceRoot":"","sources":["../../src/broker/broker.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEzF,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AACzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AACnF,OAAO,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,8BAA8B,EAAE,2BAA2B,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAI1K;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAAC;AAuC9C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACnC,OAAsB,EACtB,UAAoC,EAAE;IAKtC,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,2BAA2B,CAAC;IAC7E,MAAM,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,IAAI,8BAA8B,CAAC;IACtF,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAEtF,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,GAAG,iBAAiB,CAAC,UAAU,EAAE,CAAC;IAE9D,MAAM,OAAO,GAAG,IAAI,gBAAgB,EAAE,
|
|
1
|
+
{"version":3,"file":"broker.server.js","sourceRoot":"","sources":["../../src/broker/broker.server.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -61,6 +61,41 @@ export interface BrokerConfig {
|
|
|
61
61
|
command: string;
|
|
62
62
|
args?: string[];
|
|
63
63
|
env?: Record<string, string>;
|
|
64
|
+
/** When `true`, the upstream joins the `_all` aggregate slot once connected. */
|
|
65
|
+
aggregate?: boolean;
|
|
66
|
+
}>;
|
|
67
|
+
/**
|
|
68
|
+
* Remote MCP servers the broker connects out to and exposes as provider
|
|
69
|
+
* slots. Each entry is reached by URL (Streamable HTTP / SSE / WebSocket);
|
|
70
|
+
* local servers should be shipped as `.mcpb` bundles instead.
|
|
71
|
+
*/
|
|
72
|
+
mcpServers?: Array<{
|
|
73
|
+
name: string;
|
|
74
|
+
url: string;
|
|
75
|
+
transport?: "streamable-http" | "sse" | "websocket";
|
|
76
|
+
headers?: Record<string, string>;
|
|
77
|
+
/** Defaults to `true`; set to `false` to exclude this upstream from the `_all` aggregate slot. */
|
|
78
|
+
aggregate?: boolean;
|
|
79
|
+
}>;
|
|
80
|
+
/**
|
|
81
|
+
* Local `.mcpb` bundles the broker loads at startup and runs as stdio
|
|
82
|
+
* provider slots. A bundle is a ZIP with a `manifest.json`; the broker
|
|
83
|
+
* verifies a detached signature against a trusted public key before
|
|
84
|
+
* unpacking and spawning it.
|
|
85
|
+
*/
|
|
86
|
+
mcpbBundles?: Array<{
|
|
87
|
+
/** Provider slot name the bundle is bound to. */
|
|
88
|
+
name: string;
|
|
89
|
+
/** Path to the `.mcpb` file (resolved against the config file's directory). */
|
|
90
|
+
path: string;
|
|
91
|
+
/** Path to the trusted public key (PEM) used to verify the detached signature. */
|
|
92
|
+
publicKey: string;
|
|
93
|
+
/** Path to the detached signature file. Defaults to `<path>.sig`. */
|
|
94
|
+
signature?: string;
|
|
95
|
+
/** Values substituted into the manifest's `${user_config.*}` placeholders. */
|
|
96
|
+
userConfig?: Record<string, string | number | boolean | Array<string | number>>;
|
|
97
|
+
/** Defaults to `true`; set to `false` to exclude this bundle from the `_all` aggregate slot. */
|
|
98
|
+
aggregate?: boolean;
|
|
64
99
|
}>;
|
|
65
100
|
}
|
|
66
101
|
/**
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+H7C,oFAAoF;AACpF,MAAM,CAAC,MAAM,kBAAkB,GAAG,aAAa,CAAC;AAEhD,iEAAiE;AACjE,MAAM,CAAC,MAAM,uBAAuB,GAAG,aAAa,CAAC;AAErD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AAE/D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAa;IAC1C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAEjD,IAAI,UAAU,GAAkB,IAAI,CAAC;IAErC,IAAI,IAAI,EAAE,CAAC;QACP,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACpC,CAAC;SAAM,IAAI,OAAO,EAAE,CAAC;QACjB,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC;SAAM,CAAC;QACJ,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,kBAAkB,EAAE,uBAAuB,CAAC,CAAC;QACzE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAAC;QACpD,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACrB,UAAU,GAAG,MAAM,CAAC;QACxB,CAAC;aAAM,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,UAAU,GAAG,MAAM,CAAC;YACpB,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,uCAAuC,MAAM,IAAI,GAAG,cAAc,OAAO,CAAC,GAAG,EAAE,kBAAkB,EAAE,uBAAuB,CAAC,GAAG,GAAG,4BAA4B,CAChK,CAAC;QACN,CAAC;IACL,CAAC;IAED,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACzC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IAC1D,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAEpC,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAC;QAC/C,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;IAC3C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+CAA+C,UAAU,KAAM,GAAa,CAAC,OAAO,IAAI,CAAC,CAAC;QAC/G,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IAC1D,CAAC;AACL,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,12 @@ export { WsTunnelBuilder } from "./ws.tunnel.builder.js";
|
|
|
3
3
|
export type { WsTunnelOptions, StaticMount } from "./ws.tunnel.js";
|
|
4
4
|
export { StdioUpstream } from "./stdio.upstream.js";
|
|
5
5
|
export type { StdioUpstreamConfig } from "./stdio.upstream.js";
|
|
6
|
+
export { RemoteUpstream } from "./remote.upstream.js";
|
|
7
|
+
export type { RemoteUpstreamConfig } from "./remote.upstream.js";
|
|
8
|
+
export type { Upstream } from "./upstream.js";
|
|
9
|
+
export { loadMcpbBundle } from "./mcpb.loader.js";
|
|
10
|
+
export type { McpbBundleConfig } from "./mcpb.loader.js";
|
|
11
|
+
export { unzipMcpb } from "./mcpb.unzip.js";
|
|
6
12
|
export { BrokerInfoBehavior, BrokerProvidersBehavior, startBrokerServer, BROKER_PROVIDER_NAME } from "./broker/index.js";
|
|
7
13
|
export type { StartBrokerServerOptions } from "./broker/index.js";
|
|
8
14
|
export { brokerGrammarKey, defaultBrokerLocaleResolver, defaultBrokerUserAgentResolver, iterAvailableBrokerGrammars, loadBrokerGrammar, resolveBrokerLocale, resolveBrokerUserAgent, } from "./broker/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export { WsTunnel } from "./ws.tunnel.js";
|
|
2
2
|
export { WsTunnelBuilder } from "./ws.tunnel.builder.js";
|
|
3
3
|
export { StdioUpstream } from "./stdio.upstream.js";
|
|
4
|
+
export { RemoteUpstream } from "./remote.upstream.js";
|
|
5
|
+
// `.mcpb` bundle loading — verifies + unpacks a bundle into a stdio upstream.
|
|
6
|
+
export { loadMcpbBundle } from "./mcpb.loader.js";
|
|
7
|
+
export { unzipMcpb } from "./mcpb.unzip.js";
|
|
4
8
|
// Broker introspection — tier 1.
|
|
5
9
|
export { BrokerInfoBehavior, BrokerProvidersBehavior, startBrokerServer, BROKER_PROVIDER_NAME } from "./broker/index.js";
|
|
6
10
|
export { brokerGrammarKey, defaultBrokerLocaleResolver, defaultBrokerUserAgentResolver, iterAvailableBrokerGrammars, loadBrokerGrammar, resolveBrokerLocale, resolveBrokerUserAgent, } from "./broker/index.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAItD,8EAA8E;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,iCAAiC;AACjC,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzH,OAAO,EACH,gBAAgB,EAChB,2BAA2B,EAC3B,8BAA8B,EAC9B,2BAA2B,EAC3B,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,GACzB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAErD,2EAA2E;AAC3E,6DAA6D;AAC7D,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { StdioUpstreamConfig } from "./stdio.upstream.js";
|
|
2
|
+
/** A `.mcpb` bundle entry from the broker config file. */
|
|
3
|
+
export interface McpbBundleConfig {
|
|
4
|
+
/** Provider slot name the bundle is bound to. */
|
|
5
|
+
name: string;
|
|
6
|
+
/** Path to the `.mcpb` file. */
|
|
7
|
+
path: string;
|
|
8
|
+
/** Path to the trusted public key (PEM) verifying the detached signature. */
|
|
9
|
+
publicKey: string;
|
|
10
|
+
/** Path to the detached signature file. Defaults to `<path>.sig`. */
|
|
11
|
+
signature?: string;
|
|
12
|
+
/** Values substituted into the manifest's `${user_config.*}` placeholders. */
|
|
13
|
+
userConfig?: Record<string, string | number | boolean | Array<string | number>>;
|
|
14
|
+
/** When `false`, the bundle stays out of the `_all` aggregate slot. Defaults to `true`. */
|
|
15
|
+
aggregate?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Verifies, unpacks and resolves a `.mcpb` bundle into a `StdioUpstreamConfig`.
|
|
19
|
+
*
|
|
20
|
+
* @param cfg The bundle entry from the broker config.
|
|
21
|
+
* @param baseDir Directory the bundle paths are resolved against.
|
|
22
|
+
* @returns A ready upstream config, or `null` when the bundle is refused.
|
|
23
|
+
*/
|
|
24
|
+
export declare function loadMcpbBundle(cfg: McpbBundleConfig, baseDir: string): Promise<StdioUpstreamConfig | null>;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads a local `.mcpb` bundle into a {@link StdioUpstreamConfig}.
|
|
3
|
+
*
|
|
4
|
+
* A `.mcpb` bundle is a ZIP holding a `manifest.json` whose `server.mcp_config`
|
|
5
|
+
* describes a stdio MCP server process. The broker:
|
|
6
|
+
*
|
|
7
|
+
* 1. verifies a **detached signature** of the `.mcpb` file against a trusted
|
|
8
|
+
* public key (PEM) — integrity *and* provenance, using `node:crypto` only;
|
|
9
|
+
* 2. unpacks the archive;
|
|
10
|
+
* 3. reads the manifest, expands the `mcp_config` placeholders, and produces a
|
|
11
|
+
* `StdioUpstreamConfig` that the existing upstream wiring spawns.
|
|
12
|
+
*
|
|
13
|
+
* The broker stays compatible with the `.mcpb` format without depending on the
|
|
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.
|
|
16
|
+
*
|
|
17
|
+
* Any failure (missing files, bad signature, malformed manifest, missing
|
|
18
|
+
* `user_config` value) is logged and yields `null`: the bundle is refused and
|
|
19
|
+
* no process is ever spawned. Never auto-runs an unverified bundle.
|
|
20
|
+
*/
|
|
21
|
+
import { createPublicKey, verify, X509Certificate } from "node:crypto";
|
|
22
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
import { join, resolve, sep } from "node:path";
|
|
25
|
+
import { unzipMcpb } from "./mcpb.unzip.js";
|
|
26
|
+
/** Loads a PEM that holds either an X.509 certificate or a bare public key. */
|
|
27
|
+
function loadPublicKey(pem) {
|
|
28
|
+
if (pem.includes("BEGIN CERTIFICATE")) {
|
|
29
|
+
return new X509Certificate(pem).publicKey;
|
|
30
|
+
}
|
|
31
|
+
return createPublicKey(pem);
|
|
32
|
+
}
|
|
33
|
+
/** Verifies the detached `signature` of `data` against `publicKey`. */
|
|
34
|
+
function verifyDetachedSignature(data, signature, publicKey) {
|
|
35
|
+
// Ed25519/Ed448 are used without a separate hash algorithm; RSA/EC need one.
|
|
36
|
+
const keyType = publicKey.asymmetricKeyType;
|
|
37
|
+
const algorithm = keyType === "ed25519" || keyType === "ed448" ? null : "sha256";
|
|
38
|
+
return verify(algorithm, data, publicKey, signature);
|
|
39
|
+
}
|
|
40
|
+
/** Resolves a single `${...}` placeholder key, or `undefined` when unknown. */
|
|
41
|
+
function resolvePlaceholder(key, dirname, userConfig) {
|
|
42
|
+
if (key === "__dirname")
|
|
43
|
+
return dirname;
|
|
44
|
+
if (key === "HOME")
|
|
45
|
+
return homedir();
|
|
46
|
+
if (key === "DESKTOP")
|
|
47
|
+
return join(homedir(), "Desktop");
|
|
48
|
+
if (key === "DOCUMENTS")
|
|
49
|
+
return join(homedir(), "Documents");
|
|
50
|
+
if (key === "DOWNLOADS")
|
|
51
|
+
return join(homedir(), "Downloads");
|
|
52
|
+
if (key === "pathSeparator" || key === "/")
|
|
53
|
+
return sep;
|
|
54
|
+
if (key.startsWith("user_config.")) {
|
|
55
|
+
const name = key.slice("user_config.".length);
|
|
56
|
+
const value = userConfig?.[name];
|
|
57
|
+
if (value === undefined)
|
|
58
|
+
throw new Error(`missing user_config value: "${name}"`);
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
/** Expands placeholders in a scalar string (command, env value). */
|
|
64
|
+
function expandScalar(input, dirname, userConfig) {
|
|
65
|
+
return input.replace(/\$\{([^}]+)\}/g, (match, key) => {
|
|
66
|
+
const value = resolvePlaceholder(key, dirname, userConfig);
|
|
67
|
+
if (value === undefined)
|
|
68
|
+
return match; // unknown placeholder — leave verbatim
|
|
69
|
+
if (Array.isArray(value))
|
|
70
|
+
throw new Error(`placeholder "\${${key}}" is multi-valued and cannot be used here`);
|
|
71
|
+
return String(value);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Expands one manifest argument; a standalone multi-valued placeholder spreads. */
|
|
75
|
+
function expandArg(arg, dirname, userConfig) {
|
|
76
|
+
const standalone = /^\$\{(user_config\.[^}]+)\}$/.exec(arg);
|
|
77
|
+
if (standalone) {
|
|
78
|
+
const value = resolvePlaceholder(standalone[1], dirname, userConfig);
|
|
79
|
+
if (Array.isArray(value))
|
|
80
|
+
return value.map(String);
|
|
81
|
+
return [String(value)];
|
|
82
|
+
}
|
|
83
|
+
return [expandScalar(arg, dirname, userConfig)];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Verifies, unpacks and resolves a `.mcpb` bundle into a `StdioUpstreamConfig`.
|
|
87
|
+
*
|
|
88
|
+
* @param cfg The bundle entry from the broker config.
|
|
89
|
+
* @param baseDir Directory the bundle paths are resolved against.
|
|
90
|
+
* @returns A ready upstream config, or `null` when the bundle is refused.
|
|
91
|
+
*/
|
|
92
|
+
export async function loadMcpbBundle(cfg, baseDir) {
|
|
93
|
+
const tag = `[mcp-broker] mcpb bundle "${cfg.name}"`;
|
|
94
|
+
try {
|
|
95
|
+
const mcpbPath = resolve(baseDir, cfg.path);
|
|
96
|
+
const publicKeyPath = resolve(baseDir, cfg.publicKey);
|
|
97
|
+
const signaturePath = cfg.signature ? resolve(baseDir, cfg.signature) : `${mcpbPath}.sig`;
|
|
98
|
+
for (const [label, file] of [
|
|
99
|
+
["bundle", mcpbPath],
|
|
100
|
+
["public key", publicKeyPath],
|
|
101
|
+
["signature", signaturePath],
|
|
102
|
+
]) {
|
|
103
|
+
if (!existsSync(file)) {
|
|
104
|
+
console.error(`${tag}: ${label} file not found at ${file} — bundle refused.`);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// ── Signature verification (mandatory) ──────────────────────────────
|
|
109
|
+
const bundleBytes = readFileSync(mcpbPath);
|
|
110
|
+
const signatureBytes = readFileSync(signaturePath);
|
|
111
|
+
const publicKey = loadPublicKey(readFileSync(publicKeyPath, "utf8"));
|
|
112
|
+
if (!verifyDetachedSignature(bundleBytes, signatureBytes, publicKey)) {
|
|
113
|
+
console.error(`${tag}: detached signature is invalid for the configured public key — bundle refused.`);
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
// ── Unpack ──────────────────────────────────────────────────────────
|
|
117
|
+
const outputDir = resolve(baseDir, ".cache", "mcpb", cfg.name);
|
|
118
|
+
rmSync(outputDir, { recursive: true, force: true });
|
|
119
|
+
unzipMcpb(mcpbPath, outputDir);
|
|
120
|
+
// ── Manifest → mcp_config ───────────────────────────────────────────
|
|
121
|
+
const manifestPath = join(outputDir, "manifest.json");
|
|
122
|
+
if (!existsSync(manifestPath)) {
|
|
123
|
+
console.error(`${tag}: manifest.json missing inside the bundle — bundle refused.`);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
127
|
+
const mcpConfig = manifest.server?.mcp_config;
|
|
128
|
+
if (!mcpConfig) {
|
|
129
|
+
console.error(`${tag}: manifest has no server.mcp_config — bundle refused.`);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
// Platform-specific overrides replace the base fields when present.
|
|
133
|
+
const override = mcpConfig.platform_overrides?.[process.platform];
|
|
134
|
+
const command = override?.command ?? mcpConfig.command;
|
|
135
|
+
const rawArgs = override?.args ?? mcpConfig.args ?? [];
|
|
136
|
+
const rawEnv = { ...mcpConfig.env, ...override?.env };
|
|
137
|
+
if (!command) {
|
|
138
|
+
console.error(`${tag}: manifest mcp_config has no command — bundle refused.`);
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
// ── Placeholder expansion ───────────────────────────────────────────
|
|
142
|
+
const expandedCommand = expandScalar(command, outputDir, cfg.userConfig);
|
|
143
|
+
const expandedArgs = rawArgs.flatMap((arg) => expandArg(arg, outputDir, cfg.userConfig));
|
|
144
|
+
const expandedEnv = {};
|
|
145
|
+
for (const [key, value] of Object.entries(rawEnv)) {
|
|
146
|
+
expandedEnv[key] = expandScalar(value, outputDir, cfg.userConfig);
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
name: cfg.name,
|
|
150
|
+
command: expandedCommand,
|
|
151
|
+
args: expandedArgs,
|
|
152
|
+
env: Object.keys(expandedEnv).length > 0 ? expandedEnv : undefined,
|
|
153
|
+
aggregate: cfg.aggregate ?? true,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
console.error(`${tag}: ${err.message} — bundle refused.`);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=mcpb.loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcpb.loader.js","sourceRoot":"","sources":["../src/mcpb.loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts every entry of the `.mcpb` archive at `mcpbPath` into `destDir`.
|
|
3
|
+
* `destDir` is created if missing. Entries whose resolved path would escape
|
|
4
|
+
* `destDir` (zip-slip) are rejected.
|
|
5
|
+
*/
|
|
6
|
+
export declare function unzipMcpb(mcpbPath: string, destDir: string): void;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal ZIP extractor for `.mcpb` bundles, built on `node:zlib` only.
|
|
3
|
+
*
|
|
4
|
+
* A `.mcpb` bundle is an ordinary ZIP archive. The broker deliberately avoids
|
|
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.
|
|
7
|
+
*
|
|
8
|
+
* Supports the two compression methods used in practice: stored (0) and
|
|
9
|
+
* deflate (8). ZIP64 archives are rejected with a clear error (`.mcpb` bundles
|
|
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
|
|
12
|
+
* extraction is driven entirely by the central directory.
|
|
13
|
+
*/
|
|
14
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
16
|
+
import { inflateRawSync } from "node:zlib";
|
|
17
|
+
const EOCD_SIGNATURE = 0x06054b50;
|
|
18
|
+
const CENTRAL_HEADER_SIGNATURE = 0x02014b50;
|
|
19
|
+
const LOCAL_HEADER_SIGNATURE = 0x04034b50;
|
|
20
|
+
/** Locates the End Of Central Directory record by scanning backwards. */
|
|
21
|
+
function findEocd(buf) {
|
|
22
|
+
// The EOCD is 22 bytes plus a comment of up to 65535 bytes.
|
|
23
|
+
const minPos = Math.max(0, buf.length - 22 - 0xffff);
|
|
24
|
+
for (let pos = buf.length - 22; pos >= minPos; pos--) {
|
|
25
|
+
if (buf.readUInt32LE(pos) === EOCD_SIGNATURE)
|
|
26
|
+
return pos;
|
|
27
|
+
}
|
|
28
|
+
throw new Error("not a ZIP archive (no End Of Central Directory record found)");
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Extracts every entry of the `.mcpb` archive at `mcpbPath` into `destDir`.
|
|
32
|
+
* `destDir` is created if missing. Entries whose resolved path would escape
|
|
33
|
+
* `destDir` (zip-slip) are rejected.
|
|
34
|
+
*/
|
|
35
|
+
export function unzipMcpb(mcpbPath, destDir) {
|
|
36
|
+
const buf = readFileSync(mcpbPath);
|
|
37
|
+
const eocd = findEocd(buf);
|
|
38
|
+
const totalEntries = buf.readUInt16LE(eocd + 10);
|
|
39
|
+
const centralDirOffset = buf.readUInt32LE(eocd + 16);
|
|
40
|
+
if (centralDirOffset === 0xffffffff || totalEntries === 0xffff) {
|
|
41
|
+
throw new Error("ZIP64 archives are not supported");
|
|
42
|
+
}
|
|
43
|
+
const absDest = resolve(destDir);
|
|
44
|
+
mkdirSync(absDest, { recursive: true });
|
|
45
|
+
let pos = centralDirOffset;
|
|
46
|
+
for (let i = 0; i < totalEntries; i++) {
|
|
47
|
+
if (buf.readUInt32LE(pos) !== CENTRAL_HEADER_SIGNATURE) {
|
|
48
|
+
throw new Error(`corrupt ZIP: bad central directory header at offset ${pos}`);
|
|
49
|
+
}
|
|
50
|
+
const method = buf.readUInt16LE(pos + 10);
|
|
51
|
+
const compressedSize = buf.readUInt32LE(pos + 20);
|
|
52
|
+
const uncompressedSize = buf.readUInt32LE(pos + 24);
|
|
53
|
+
const nameLen = buf.readUInt16LE(pos + 28);
|
|
54
|
+
const extraLen = buf.readUInt16LE(pos + 30);
|
|
55
|
+
const commentLen = buf.readUInt16LE(pos + 32);
|
|
56
|
+
const localHeaderOffset = buf.readUInt32LE(pos + 42);
|
|
57
|
+
const name = buf.toString("utf8", pos + 46, pos + 46 + nameLen);
|
|
58
|
+
pos += 46 + nameLen + extraLen + commentLen;
|
|
59
|
+
if (compressedSize === 0xffffffff || uncompressedSize === 0xffffffff || localHeaderOffset === 0xffffffff) {
|
|
60
|
+
throw new Error("ZIP64 archives are not supported");
|
|
61
|
+
}
|
|
62
|
+
// Resolve the destination path and reject zip-slip escapes.
|
|
63
|
+
const target = resolve(absDest, name);
|
|
64
|
+
const rel = relative(absDest, target);
|
|
65
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
66
|
+
throw new Error(`unsafe ZIP entry path (zip-slip): ${name}`);
|
|
67
|
+
}
|
|
68
|
+
// Directory entry.
|
|
69
|
+
if (name.endsWith("/")) {
|
|
70
|
+
mkdirSync(target, { recursive: true });
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// Parse the local header to locate the entry's data.
|
|
74
|
+
if (buf.readUInt32LE(localHeaderOffset) !== LOCAL_HEADER_SIGNATURE) {
|
|
75
|
+
throw new Error(`corrupt ZIP: bad local header for "${name}"`);
|
|
76
|
+
}
|
|
77
|
+
const localNameLen = buf.readUInt16LE(localHeaderOffset + 26);
|
|
78
|
+
const localExtraLen = buf.readUInt16LE(localHeaderOffset + 28);
|
|
79
|
+
const dataStart = localHeaderOffset + 30 + localNameLen + localExtraLen;
|
|
80
|
+
const compressed = buf.subarray(dataStart, dataStart + compressedSize);
|
|
81
|
+
let data;
|
|
82
|
+
if (method === 0) {
|
|
83
|
+
data = compressed;
|
|
84
|
+
}
|
|
85
|
+
else if (method === 8) {
|
|
86
|
+
data = inflateRawSync(compressed);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
throw new Error(`unsupported ZIP compression method ${method} for "${name}"`);
|
|
90
|
+
}
|
|
91
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
92
|
+
writeFileSync(target, data);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=mcpb.unzip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcpb.unzip.js","sourceRoot":"","sources":["../src/mcpb.unzip.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Lifecycle + callbacks shared by every remote client transport. */
|
|
2
|
+
export interface RemoteTransport {
|
|
3
|
+
onMessage: ((data: string) => void) | null;
|
|
4
|
+
onOpen: (() => void) | null;
|
|
5
|
+
onClose: (() => void) | null;
|
|
6
|
+
onError: ((error: Error) => void) | null;
|
|
7
|
+
connect(): void;
|
|
8
|
+
send(data: string): void;
|
|
9
|
+
close(): void;
|
|
10
|
+
}
|
|
11
|
+
/** The three supported remote transports. */
|
|
12
|
+
export type RemoteTransportKind = "streamable-http" | "sse" | "websocket";
|
|
13
|
+
/** Builds the transport of the given kind. */
|
|
14
|
+
export declare function createRemoteTransport(url: string, kind: RemoteTransportKind, headers: Record<string, string>): RemoteTransport;
|
|
15
|
+
/** Heuristic transport detection from the URL when none is configured. */
|
|
16
|
+
export declare function detectTransport(url: string): RemoteTransportKind;
|