@cyanmycelium/mcp-broker 0.2.0 → 0.4.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.grammars.d.ts +50 -86
- package/dist/broker/broker.grammars.js +55 -84
- package/dist/broker/broker.grammars.js.map +1 -1
- package/dist/broker/broker.server.d.ts +23 -21
- package/dist/broker/broker.server.js +33 -71
- package/dist/broker/broker.server.js.map +1 -1
- package/dist/broker/index.d.ts +2 -2
- package/dist/broker/index.js +1 -1
- package/dist/broker/index.js.map +1 -1
- package/dist/config.d.ts +35 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +5 -1
- 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 +85 -22
- package/dist/ws.tunnel.js +201 -82
- 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.grammars.ts +74 -122
- package/src/broker/broker.server.ts +57 -99
- package/src/broker/index.ts +3 -5
- package/src/config.ts +37 -0
- package/src/index.ts +10 -10
- 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 +258 -99
|
@@ -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":"AAGA,gFAAgF;AAChF,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAEtC,kDAAkD;AAClD,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAoBlC;;;;;;;;GAQG;AACH,MAAM,OAAO,qBAAqB;IACrB,QAAQ,CAAS;IAET,OAAO,CAAiB;IACxB,SAAS,CAAS;IAClB,QAAQ,GAAG,IAAI,GAAG,EAA0B,CAAC;IACtD,OAAO,GAAG,CAAC,CAAC;IACZ,MAAM,GAAkB,EAAE,CAAC;IAC3B,QAAQ,GAAoB,EAAE,CAAC;IAC/B,OAAO,GAAG,KAAK,CAAC;IAExB,+EAA+E;IAC/E,gBAAgB,GAAwB,IAAI,CAAC;IAE7C,2DAA2D;IAC3D,QAAQ,GAAwB,IAAI,CAAC;IAErC,YAAY,QAAgB,EAAE,MAAsB;QAChD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,SAAS,GAAG,OAAO,QAAQ,GAAG,CAAC;QACpC,MAAM,CAAC,SAAS,GAAG,CAAC,IAAY,EAAQ,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QACrE,MAAM,CAAC,OAAO,GAAG,GAAS,EAAE;YACxB,IAAI,IAAI,CAAC,OAAO;gBAAE,OAAO;YACzB,IAAI,CAAC,UAAU,CAAC,uBAAuB,CAAC,CAAC;YACzC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;QACtB,CAAC,CAAC;IACN,CAAC;IAED,IAAI,KAAK;QACL,OAAO,IAAI,CAAC,MAAM,CAAC;IACvB,CAAC;IAED,IAAI,OAAO;QACP,OAAO,IAAI,CAAC,QAAQ,CAAC;IACzB,CAAC;IAED,mEAAmE;IACnE,KAAK,CAAC,UAAU;QACZ,MAAM,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE;YAC9B,eAAe,EAAE,gBAAgB;YACjC,YAAY,EAAE,EAAE;YAChB,UAAU,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,GAAG,EAAE;SAC7D,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC,CAAC,CAAC;QAC3F,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;IAC1B,CAAC;IAED,yEAAyE;IACzE,QAAQ,CAAC,IAAY,EAAE,IAA6B;QAChD,OAAO,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,0EAA0E;IAC1E,SAAS,CAAC,IAAY,EAAE,IAA6B;QACjD,OAAO,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,oDAAoD;IACpD,KAAK;QACD,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;QAClC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC;IAEO,KAAK,CAAC,QAAQ;QAClB,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAc,YAAY,EAAE,OAAO,CAAC,CAAC;QACtE,IAAI,CAAC,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAgB,cAAc,EAAE,SAAS,CAAC,CAAC;QAC9E,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,QAAQ,CAAI,MAAc,EAAE,GAAW;QACjD,MAAM,KAAK,GAAQ,EAAE,CAAC;QACtB,IAAI,MAA0B,CAAC;QAC/B,GAAG,CAAC;YACA,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAChF,IAAI,KAAK;gBAAE,OAAO,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,EAAE,CAA4B,CAAC;YACvD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;YACvB,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,GAAI,IAAY,CAAC,CAAC;YACtD,MAAM,GAAG,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;QAC/E,CAAC,QAAQ,MAAM,EAAE;QACjB,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,QAAQ,CAAC,MAAc,EAAE,MAAe;QAC5C,OAAO,IAAI,OAAO,CAAa,CAAC,OAAO,EAAE,EAAE;YACvC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACf,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE,CAAC,CAAC;gBAChE,OAAO;YACX,CAAC;YACD,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;YACnD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC1B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACzB,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,YAAY,MAAM,aAAa,EAAE,EAAE,CAAC,CAAC;YACnF,CAAC,EAAE,kBAAkB,CAAC,CAAC;YACvB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;YAC1C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QAC9E,CAAC,CAAC,CAAC;IACP,CAAC;IAEO,cAAc,CAAC,IAAY;QAC/B,IAAI,GAAoB,CAAC;QACzB,IAAI,CAAC;YACD,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoB,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACL,OAAO;QACX,CAAC;QACD,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC1C,IAAI,OAAO,EAAE,CAAC;gBACV,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC7B,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBAC5B,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;YAC9D,CAAC;YACD,OAAO;QACX,CAAC;QACD,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,kCAAkC,IAAI,GAAG,CAAC,MAAM,KAAK,oCAAoC,CAAC,EAAE,CAAC;YAC/H,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;QACzB,CAAC;IACL,CAAC;IAEO,UAAU,CAAC,MAAc;QAC7B,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC5B,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;QAClE,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;CACJ"}
|
|
@@ -1,99 +1,64 @@
|
|
|
1
|
-
import type { McpClientInfo } from "@cyanmycelium/mcp-core";
|
|
2
1
|
import { McpGrammar } from "@cyanmycelium/mcp-core";
|
|
3
2
|
/**
|
|
4
3
|
* Locale identifier used to look up a grammar JSON file under
|
|
5
|
-
* `<userAgent>/<locale>.json`. Open string:
|
|
6
|
-
* value its
|
|
4
|
+
* `<userAgent>/<locale>.json`. Open string: a host application can use any
|
|
5
|
+
* value its grammar resources support.
|
|
7
6
|
*
|
|
8
|
-
* The
|
|
9
|
-
*
|
|
7
|
+
* The broker registers each `(userAgent, locale)` pair found on disk as a
|
|
8
|
+
* separate `McpGrammar` keyed by {@link brokerGrammarKey}. The actual
|
|
9
|
+
* resolution of "which key to use for this session" is delegated to
|
|
10
|
+
* `@cyanmycelium/mcp-core@0.3.0`'s `grammarResolverFromOptions`, which
|
|
11
|
+
* handles BCP-47 narrowing (`fr-CA` → `fr` → `en`), agent-family fallback,
|
|
12
|
+
* and the optional version dimension natively.
|
|
10
13
|
*/
|
|
11
14
|
export type BrokerLocale = string;
|
|
12
15
|
/**
|
|
13
16
|
* User-agent family identifier used to look up a grammar JSON file under
|
|
14
|
-
* `<userAgent>/<locale>.json`. Open string.
|
|
15
|
-
*
|
|
17
|
+
* `<userAgent>/<locale>.json`. Open string. Conventional values follow
|
|
18
|
+
* the defaults emitted by `grammarResolverFromOptions`: `claude`, `gpt`,
|
|
19
|
+
* `mistral`, `copilot`, plus the universal `default`. Custom families
|
|
20
|
+
* are supported by passing a custom `agents` map in
|
|
21
|
+
* `StartBrokerServerOptions.grammarResolverOptions`.
|
|
16
22
|
*/
|
|
17
23
|
export type BrokerUserAgent = string;
|
|
18
24
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* string source works (HTTP header, session metadata, etc.).
|
|
25
|
+
* Builds the canonical grammar key for the `(userAgent, locale, version?)`
|
|
26
|
+
* matrix the broker registers on disk.
|
|
22
27
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
28
|
+
* Pattern:
|
|
29
|
+
* - `"<userAgent>:<locale>"` (no version) — e.g. `"claude:fr"`, `"default:en"`
|
|
30
|
+
* - `"<userAgent>:<locale>@<version>"` (versioned) — e.g. `"claude:fr@v2"`
|
|
26
31
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
32
|
+
* The colon separator is reserved for the `<ua>:<locale>` composition; the
|
|
33
|
+
* `@` separator is reserved for the optional version suffix. Neither
|
|
34
|
+
* character is allowed inside the identifier segments. This matches the
|
|
35
|
+
* default `composeKey` of `grammarResolverFromOptions` exactly, so a
|
|
36
|
+
* broker-loaded grammar at `claude/fr@v2.json` is automatically picked up
|
|
37
|
+
* when a Claude session resolves to the `claude:fr@v2` candidate.
|
|
38
|
+
*/
|
|
39
|
+
export declare function brokerGrammarKey(userAgent: BrokerUserAgent, locale: BrokerLocale, version?: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Parses a grammar JSON filename of the form `<locale>.json` or
|
|
42
|
+
* `<locale>@<version>.json` (without the `.json` suffix) into its
|
|
43
|
+
* components. The first `@` (if any) separates locale from version; any
|
|
44
|
+
* additional `@` is folded into the version string.
|
|
33
45
|
*
|
|
34
|
-
*
|
|
35
|
-
* project-specific dialect first, skip the bare language prefix, or pull
|
|
36
|
-
* candidates from a session config.
|
|
46
|
+
* Returns `null` when the input cannot be split into a usable locale.
|
|
37
47
|
*/
|
|
38
|
-
export
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
* `initialize` handshake.
|
|
43
|
-
*/
|
|
44
|
-
export type BrokerUserAgentResolver = (clientInfo: McpClientInfo | undefined) => BrokerUserAgent;
|
|
45
|
-
/**
|
|
46
|
-
* Default locale resolver — emits the BCP-47 narrowing chain for a raw locale
|
|
47
|
-
* tag, from most specific to least specific, always ending with the universal
|
|
48
|
-
* `"en"` fallback.
|
|
49
|
-
*
|
|
50
|
-
* Steps for an input `raw`:
|
|
51
|
-
* 1. Lowercase the input.
|
|
52
|
-
* 2. Push it as the most-specific candidate (only if non-empty).
|
|
53
|
-
* 3. If it contains a `-` separator, push its bare language prefix next.
|
|
54
|
-
* 4. Always push `"en"` last as the universal fallback.
|
|
55
|
-
*
|
|
56
|
-
* The broker server tries each candidate in turn against `<userAgent>/<locale>.json`
|
|
57
|
-
* — so dropping a `claude/fr-ca.json` lets Canadian-French Claude clients
|
|
58
|
-
* pick up that specific dialect, while clients with `fr` or `fr-FR` fall back
|
|
59
|
-
* to `claude/fr.json` or `default/fr.json` automatically.
|
|
60
|
-
*
|
|
61
|
-
* Examples:
|
|
62
|
-
* - `"fr-CA"` → `["fr-ca", "fr", "en"]`
|
|
63
|
-
* - `"fr"` → `["fr", "en"]`
|
|
64
|
-
* - `"zh-CN"` → `["zh-cn", "zh", "en"]`
|
|
65
|
-
* - `"en-US"` → `["en-us", "en"]`
|
|
66
|
-
* - `""` / `undefined` → `["en"]`
|
|
67
|
-
*/
|
|
68
|
-
export declare const defaultBrokerLocaleResolver: BrokerLocaleResolver;
|
|
69
|
-
/**
|
|
70
|
-
* Default user-agent resolver — substring match on `clientInfo.name` against
|
|
71
|
-
* a list of known LLM family hints. Unknown clients fall through to
|
|
72
|
-
* `"default"` which is the universal baseline.
|
|
73
|
-
*
|
|
74
|
-
* This is intentionally a heuristic: MCP does not yet standardize an
|
|
75
|
-
* agent-family field in `clientInfo`. Override the resolver in the broker
|
|
76
|
-
* options if you need richer logic (header inspection, allow-list, etc.).
|
|
77
|
-
*/
|
|
78
|
-
export declare const defaultBrokerUserAgentResolver: BrokerUserAgentResolver;
|
|
79
|
-
/** @deprecated Use {@link defaultBrokerLocaleResolver}. Kept as backward-compat alias. */
|
|
80
|
-
export declare const resolveBrokerLocale: BrokerLocaleResolver;
|
|
81
|
-
/** @deprecated Use {@link defaultBrokerUserAgentResolver}. Kept as backward-compat alias. */
|
|
82
|
-
export declare const resolveBrokerUserAgent: (clientName: string | undefined) => BrokerUserAgent;
|
|
48
|
+
export declare function parseBrokerGrammarStem(stem: string): {
|
|
49
|
+
locale: BrokerLocale;
|
|
50
|
+
version?: string;
|
|
51
|
+
} | null;
|
|
83
52
|
/**
|
|
84
|
-
*
|
|
53
|
+
* Loads and caches the grammar for a given `(userAgent, locale, version?)`
|
|
54
|
+
* combination. Returns `undefined` (instead of throwing) when the resource
|
|
55
|
+
* file is missing, so the caller can implement a fallback chain.
|
|
85
56
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*/
|
|
90
|
-
export declare function brokerGrammarKey(userAgent: BrokerUserAgent, locale: BrokerLocale): string;
|
|
91
|
-
/**
|
|
92
|
-
* Loads and caches the grammar for a given `(userAgent, locale)` combination.
|
|
93
|
-
* Returns `undefined` (instead of throwing) when the resource file is missing,
|
|
94
|
-
* so the caller can implement a fallback chain.
|
|
57
|
+
* Filename convention on disk:
|
|
58
|
+
* - `<userAgent>/<locale>.json` (no version)
|
|
59
|
+
* - `<userAgent>/<locale>@<version>.json` (versioned)
|
|
95
60
|
*/
|
|
96
|
-
export declare function loadBrokerGrammar(userAgent: BrokerUserAgent, locale: BrokerLocale): McpGrammar | undefined;
|
|
61
|
+
export declare function loadBrokerGrammar(userAgent: BrokerUserAgent, locale: BrokerLocale, version?: string): McpGrammar | undefined;
|
|
97
62
|
/**
|
|
98
63
|
* Walks a grammars directory and yields every `(userAgent, locale)` pair
|
|
99
64
|
* found on disk. The directory must follow the layout
|
|
@@ -103,12 +68,16 @@ export declare function loadBrokerGrammar(userAgent: BrokerUserAgent, locale: Br
|
|
|
103
68
|
* grammars and any local overrides. No hard-coded list of supported
|
|
104
69
|
* user-agents or locales — adding a new grammar is dropping a JSON file.
|
|
105
70
|
*/
|
|
106
|
-
export
|
|
71
|
+
export interface BrokerGrammarEntry {
|
|
107
72
|
userAgent: BrokerUserAgent;
|
|
108
73
|
locale: BrokerLocale;
|
|
74
|
+
/** Set only for filenames carrying an `@<version>` suffix. */
|
|
75
|
+
version?: string;
|
|
76
|
+
/** Composed via {@link brokerGrammarKey} from the three segments above. */
|
|
109
77
|
key: string;
|
|
110
78
|
grammar: McpGrammar;
|
|
111
|
-
}
|
|
79
|
+
}
|
|
80
|
+
export declare function iterBrokerGrammarsFrom(grammarsDir: string): Generator<BrokerGrammarEntry>;
|
|
112
81
|
/**
|
|
113
82
|
* Walks the **packaged** grammars directory (the one shipped with the
|
|
114
83
|
* mcp-broker package). Equivalent to `iterBrokerGrammarsFrom(<packaged-dir>)`.
|
|
@@ -116,12 +85,7 @@ export declare function iterBrokerGrammarsFrom(grammarsDir: string): Generator<{
|
|
|
116
85
|
* For local user overrides, see {@link iterBrokerGrammarsFrom} with a custom
|
|
117
86
|
* directory — typically `.mcp-broker/grammars/` next to the config file.
|
|
118
87
|
*/
|
|
119
|
-
export declare function iterAvailableBrokerGrammars(): Generator<
|
|
120
|
-
userAgent: BrokerUserAgent;
|
|
121
|
-
locale: BrokerLocale;
|
|
122
|
-
key: string;
|
|
123
|
-
grammar: McpGrammar;
|
|
124
|
-
}>;
|
|
88
|
+
export declare function iterAvailableBrokerGrammars(): Generator<BrokerGrammarEntry>;
|
|
125
89
|
/**
|
|
126
90
|
* Returns the baseline grammar used by the broker behaviors as their
|
|
127
91
|
* source-of-truth for inline tool / property descriptions.
|
|
@@ -3,81 +3,44 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { McpGrammar } from "@cyanmycelium/mcp-core";
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
6
|
-
//
|
|
6
|
+
// Canonical grammar key
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `"en"` fallback.
|
|
12
|
-
*
|
|
13
|
-
* Steps for an input `raw`:
|
|
14
|
-
* 1. Lowercase the input.
|
|
15
|
-
* 2. Push it as the most-specific candidate (only if non-empty).
|
|
16
|
-
* 3. If it contains a `-` separator, push its bare language prefix next.
|
|
17
|
-
* 4. Always push `"en"` last as the universal fallback.
|
|
9
|
+
* Builds the canonical grammar key for the `(userAgent, locale, version?)`
|
|
10
|
+
* matrix the broker registers on disk.
|
|
18
11
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* to `claude/fr.json` or `default/fr.json` automatically.
|
|
12
|
+
* Pattern:
|
|
13
|
+
* - `"<userAgent>:<locale>"` (no version) — e.g. `"claude:fr"`, `"default:en"`
|
|
14
|
+
* - `"<userAgent>:<locale>@<version>"` (versioned) — e.g. `"claude:fr@v2"`
|
|
23
15
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* - `
|
|
29
|
-
*
|
|
16
|
+
* The colon separator is reserved for the `<ua>:<locale>` composition; the
|
|
17
|
+
* `@` separator is reserved for the optional version suffix. Neither
|
|
18
|
+
* character is allowed inside the identifier segments. This matches the
|
|
19
|
+
* default `composeKey` of `grammarResolverFromOptions` exactly, so a
|
|
20
|
+
* broker-loaded grammar at `claude/fr@v2.json` is automatically picked up
|
|
21
|
+
* when a Claude session resolves to the `claude:fr@v2` candidate.
|
|
30
22
|
*/
|
|
31
|
-
export
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
raw = raw.toLowerCase();
|
|
36
|
-
a.push(raw);
|
|
37
|
-
if (raw.indexOf(sep) !== -1) {
|
|
38
|
-
a.push(raw.split(sep)[0]);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
a.push("en");
|
|
42
|
-
return a;
|
|
43
|
-
};
|
|
44
|
-
/**
|
|
45
|
-
* Default user-agent resolver — substring match on `clientInfo.name` against
|
|
46
|
-
* a list of known LLM family hints. Unknown clients fall through to
|
|
47
|
-
* `"default"` which is the universal baseline.
|
|
48
|
-
*
|
|
49
|
-
* This is intentionally a heuristic: MCP does not yet standardize an
|
|
50
|
-
* agent-family field in `clientInfo`. Override the resolver in the broker
|
|
51
|
-
* options if you need richer logic (header inspection, allow-list, etc.).
|
|
52
|
-
*/
|
|
53
|
-
export const defaultBrokerUserAgentResolver = (clientInfo) => {
|
|
54
|
-
const n = (clientInfo?.name ?? "").toLowerCase();
|
|
55
|
-
if (n.includes("claude"))
|
|
56
|
-
return "claude";
|
|
57
|
-
if (n.includes("gpt") || n.includes("openai"))
|
|
58
|
-
return "gpt";
|
|
59
|
-
if (n.includes("mistral"))
|
|
60
|
-
return "mistral";
|
|
61
|
-
if (n.includes("copilot"))
|
|
62
|
-
return "copilot";
|
|
63
|
-
return "default";
|
|
64
|
-
};
|
|
65
|
-
/** @deprecated Use {@link defaultBrokerLocaleResolver}. Kept as backward-compat alias. */
|
|
66
|
-
export const resolveBrokerLocale = defaultBrokerLocaleResolver;
|
|
67
|
-
/** @deprecated Use {@link defaultBrokerUserAgentResolver}. Kept as backward-compat alias. */
|
|
68
|
-
export const resolveBrokerUserAgent = (clientName) => defaultBrokerUserAgentResolver({ name: clientName ?? "", version: "" });
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
// Canonical grammar key
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
23
|
+
export function brokerGrammarKey(userAgent, locale, version) {
|
|
24
|
+
const base = `${userAgent}:${locale}`;
|
|
25
|
+
return version ? `${base}@${version}` : base;
|
|
26
|
+
}
|
|
72
27
|
/**
|
|
73
|
-
*
|
|
28
|
+
* Parses a grammar JSON filename of the form `<locale>.json` or
|
|
29
|
+
* `<locale>@<version>.json` (without the `.json` suffix) into its
|
|
30
|
+
* components. The first `@` (if any) separates locale from version; any
|
|
31
|
+
* additional `@` is folded into the version string.
|
|
74
32
|
*
|
|
75
|
-
*
|
|
76
|
-
* The colon separator is reserved for this composition and never appears in
|
|
77
|
-
* user-agent or locale identifiers.
|
|
33
|
+
* Returns `null` when the input cannot be split into a usable locale.
|
|
78
34
|
*/
|
|
79
|
-
export function
|
|
80
|
-
|
|
35
|
+
export function parseBrokerGrammarStem(stem) {
|
|
36
|
+
const at = stem.indexOf("@");
|
|
37
|
+
if (at < 0)
|
|
38
|
+
return stem.length > 0 ? { locale: stem } : null;
|
|
39
|
+
const locale = stem.slice(0, at);
|
|
40
|
+
const version = stem.slice(at + 1);
|
|
41
|
+
if (locale.length === 0 || version.length === 0)
|
|
42
|
+
return null;
|
|
43
|
+
return { locale, version };
|
|
81
44
|
}
|
|
82
45
|
// ---------------------------------------------------------------------------
|
|
83
46
|
// JSON resource loading
|
|
@@ -105,16 +68,21 @@ export function brokerGrammarKey(userAgent, locale) {
|
|
|
105
68
|
const GRAMMARS_DIR = join(dirname(fileURLToPath(import.meta.url)), "grammars");
|
|
106
69
|
const _cache = new Map();
|
|
107
70
|
/**
|
|
108
|
-
* Loads and caches the grammar for a given `(userAgent, locale)`
|
|
109
|
-
* Returns `undefined` (instead of throwing) when the resource
|
|
110
|
-
* so the caller can implement a fallback chain.
|
|
71
|
+
* Loads and caches the grammar for a given `(userAgent, locale, version?)`
|
|
72
|
+
* combination. Returns `undefined` (instead of throwing) when the resource
|
|
73
|
+
* file is missing, so the caller can implement a fallback chain.
|
|
74
|
+
*
|
|
75
|
+
* Filename convention on disk:
|
|
76
|
+
* - `<userAgent>/<locale>.json` (no version)
|
|
77
|
+
* - `<userAgent>/<locale>@<version>.json` (versioned)
|
|
111
78
|
*/
|
|
112
|
-
export function loadBrokerGrammar(userAgent, locale) {
|
|
113
|
-
const key = brokerGrammarKey(userAgent, locale);
|
|
79
|
+
export function loadBrokerGrammar(userAgent, locale, version) {
|
|
80
|
+
const key = brokerGrammarKey(userAgent, locale, version);
|
|
114
81
|
const cached = _cache.get(key);
|
|
115
82
|
if (cached)
|
|
116
83
|
return cached;
|
|
117
|
-
const
|
|
84
|
+
const filename = version ? `${locale}@${version}.json` : `${locale}.json`;
|
|
85
|
+
const path = join(GRAMMARS_DIR, userAgent, filename);
|
|
118
86
|
if (!existsSync(path))
|
|
119
87
|
return undefined;
|
|
120
88
|
const raw = readFileSync(path, "utf-8");
|
|
@@ -123,15 +91,6 @@ export function loadBrokerGrammar(userAgent, locale) {
|
|
|
123
91
|
_cache.set(key, grammar);
|
|
124
92
|
return grammar;
|
|
125
93
|
}
|
|
126
|
-
/**
|
|
127
|
-
* Walks a grammars directory and yields every `(userAgent, locale)` pair
|
|
128
|
-
* found on disk. The directory must follow the layout
|
|
129
|
-
* `<dir>/<userAgent>/<locale>.json`.
|
|
130
|
-
*
|
|
131
|
-
* Used by the broker server at startup to bulk-register both the packaged
|
|
132
|
-
* grammars and any local overrides. No hard-coded list of supported
|
|
133
|
-
* user-agents or locales — adding a new grammar is dropping a JSON file.
|
|
134
|
-
*/
|
|
135
94
|
export function* iterBrokerGrammarsFrom(grammarsDir) {
|
|
136
95
|
if (!existsSync(grammarsDir))
|
|
137
96
|
return;
|
|
@@ -144,13 +103,25 @@ export function* iterBrokerGrammarsFrom(grammarsDir) {
|
|
|
144
103
|
for (const file of files) {
|
|
145
104
|
if (!file.endsWith(".json"))
|
|
146
105
|
continue;
|
|
147
|
-
const
|
|
106
|
+
const stem = file.slice(0, -".json".length);
|
|
107
|
+
const parsed = parseBrokerGrammarStem(stem);
|
|
108
|
+
if (!parsed) {
|
|
109
|
+
process.stderr.write(`[mcp-broker] Skipping unparseable grammar filename ${file} in ${uaDir}\n`);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const { locale, version } = parsed;
|
|
148
113
|
const path = join(uaDir, file);
|
|
149
114
|
try {
|
|
150
115
|
const raw = readFileSync(path, "utf-8");
|
|
151
116
|
const data = JSON.parse(raw);
|
|
152
117
|
const grammar = McpGrammar.fromJSON(data);
|
|
153
|
-
yield {
|
|
118
|
+
yield {
|
|
119
|
+
userAgent,
|
|
120
|
+
locale,
|
|
121
|
+
version,
|
|
122
|
+
key: brokerGrammarKey(userAgent, locale, version),
|
|
123
|
+
grammar,
|
|
124
|
+
};
|
|
154
125
|
}
|
|
155
126
|
catch (err) {
|
|
156
127
|
process.stderr.write(`[mcp-broker] Failed to load grammar ${path}: ${err.message}\n`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broker.grammars.js","sourceRoot":"","sources":["../../src/broker/broker.grammars.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"broker.grammars.js","sourceRoot":"","sources":["../../src/broker/broker.grammars.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { IMcpServer, IMessageTransport } from "@cyanmycelium/mcp-core";
|
|
2
|
-
import type { BrokerLocaleResolver, BrokerUserAgentResolver } from "./broker.grammars.js";
|
|
1
|
+
import type { GrammarResolverOptions, IMcpServer, IMessageTransport } from "@cyanmycelium/mcp-core";
|
|
3
2
|
import type { BrokerContext } from "./broker.context.js";
|
|
4
3
|
/**
|
|
5
4
|
* Reserved provider slot name under which the broker exposes itself as an MCP
|
|
@@ -16,29 +15,32 @@ export declare const BROKER_PROVIDER_NAME = "_broker";
|
|
|
16
15
|
*/
|
|
17
16
|
export interface StartBrokerServerOptions {
|
|
18
17
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* Source of the raw locale string fed to the locale resolver. Defaults
|
|
32
|
-
* to `process.env.MCP_BROKER_LOCALE`. Override when the locale lives
|
|
33
|
-
* somewhere else (config file, session metadata, HTTP header proxy, ...).
|
|
18
|
+
* Overrides for the built-in grammar resolver from `@cyanmycelium/mcp-core`.
|
|
19
|
+
*
|
|
20
|
+
* The broker installs sensible defaults: `localeSource` reads
|
|
21
|
+
* `process.env.MCP_BROKER_LOCALE`, the `agents` map uses the mcp-core
|
|
22
|
+
* defaults (`claude`, `gpt`, `mistral`, `copilot`, `default`), the
|
|
23
|
+
* narrowing chain is BCP-47-style, and `fallbackKey` is `default:en`
|
|
24
|
+
* so the baseline grammar always matches as last resort.
|
|
25
|
+
*
|
|
26
|
+
* Pass partial overrides here to inject a custom `localeSource` (e.g.
|
|
27
|
+
* pull from an HTTP header proxied by your transport), enable the
|
|
28
|
+
* `versionFrom` dimension, or extend the `agents` map with additional
|
|
29
|
+
* LLM families. Anything you omit keeps the broker default.
|
|
34
30
|
*/
|
|
35
|
-
|
|
31
|
+
grammarResolverOptions?: Partial<GrammarResolverOptions>;
|
|
36
32
|
/**
|
|
37
33
|
* Path to a user-supplied grammars directory whose `<userAgent>/<locale>.json`
|
|
38
|
-
* files are
|
|
39
|
-
*
|
|
34
|
+
* files are registered **in addition to** the packaged grammars.
|
|
35
|
+
*
|
|
36
|
+
* Both packaged and local entries are registered raw against the server
|
|
37
|
+
* via `withGrammar(brokerGrammarKey(ua, locale), grammar)`. The
|
|
38
|
+
* candidate-chain resolution implemented by `McpServer.initialize` in
|
|
39
|
+
* mcp-core@0.3.0 then walks the chain and merges the four layers
|
|
40
|
+
* (behavior, adapter, static, store) for the first matching key —
|
|
41
|
+
* the old hand-rolled pre-merge cascade is no longer needed.
|
|
40
42
|
*
|
|
41
|
-
* When `undefined` (default), only the packaged grammars are
|
|
43
|
+
* When `undefined` (default), only the packaged grammars are loaded.
|
|
42
44
|
*/
|
|
43
45
|
localGrammarsDir?: string;
|
|
44
46
|
}
|