@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.
Files changed (71) hide show
  1. package/.mcp-broker.example/README.md +23 -0
  2. package/.mcp-broker.example/config.json +11 -0
  3. package/README.md +1 -16
  4. package/dist/bin.js +30 -3
  5. package/dist/bin.js.map +1 -1
  6. package/dist/broker/aggregate/aggregate.catalog.d.ts +54 -0
  7. package/dist/broker/aggregate/aggregate.catalog.js +105 -0
  8. package/dist/broker/aggregate/aggregate.catalog.js.map +1 -0
  9. package/dist/broker/aggregate/aggregate.server.d.ts +47 -0
  10. package/dist/broker/aggregate/aggregate.server.js +151 -0
  11. package/dist/broker/aggregate/aggregate.server.js.map +1 -0
  12. package/dist/broker/aggregate/provider.client.session.d.ts +52 -0
  13. package/dist/broker/aggregate/provider.client.session.js +140 -0
  14. package/dist/broker/aggregate/provider.client.session.js.map +1 -0
  15. package/dist/broker/broker.grammars.d.ts +50 -86
  16. package/dist/broker/broker.grammars.js +55 -84
  17. package/dist/broker/broker.grammars.js.map +1 -1
  18. package/dist/broker/broker.server.d.ts +23 -21
  19. package/dist/broker/broker.server.js +33 -71
  20. package/dist/broker/broker.server.js.map +1 -1
  21. package/dist/broker/index.d.ts +2 -2
  22. package/dist/broker/index.js +1 -1
  23. package/dist/broker/index.js.map +1 -1
  24. package/dist/config.d.ts +35 -0
  25. package/dist/config.js.map +1 -1
  26. package/dist/index.d.ts +8 -2
  27. package/dist/index.js +5 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/mcpb.loader.d.ts +24 -0
  30. package/dist/mcpb.loader.js +161 -0
  31. package/dist/mcpb.loader.js.map +1 -0
  32. package/dist/mcpb.unzip.d.ts +6 -0
  33. package/dist/mcpb.unzip.js +95 -0
  34. package/dist/mcpb.unzip.js.map +1 -0
  35. package/dist/remote.transports.d.ts +16 -0
  36. package/dist/remote.transports.js +297 -0
  37. package/dist/remote.transports.js.map +1 -0
  38. package/dist/remote.upstream.d.ts +36 -0
  39. package/dist/remote.upstream.js +52 -0
  40. package/dist/remote.upstream.js.map +1 -0
  41. package/dist/stdio.upstream.d.ts +4 -1
  42. package/dist/stdio.upstream.js.map +1 -1
  43. package/dist/upstream.d.ts +33 -0
  44. package/dist/upstream.js +2 -0
  45. package/dist/upstream.js.map +1 -0
  46. package/dist/ws.tunnel.builder.d.ts +14 -8
  47. package/dist/ws.tunnel.builder.js +17 -9
  48. package/dist/ws.tunnel.builder.js.map +1 -1
  49. package/dist/ws.tunnel.d.ts +85 -22
  50. package/dist/ws.tunnel.js +201 -82
  51. package/dist/ws.tunnel.js.map +1 -1
  52. package/package.json +3 -2
  53. package/scripts/pack-mcpb.mjs +84 -0
  54. package/scripts/sign-bundle.mjs +61 -0
  55. package/src/bin.ts +32 -3
  56. package/src/broker/aggregate/aggregate.catalog.ts +145 -0
  57. package/src/broker/aggregate/aggregate.server.ts +178 -0
  58. package/src/broker/aggregate/provider.client.session.ts +172 -0
  59. package/src/broker/broker.grammars.ts +74 -122
  60. package/src/broker/broker.server.ts +57 -99
  61. package/src/broker/index.ts +3 -5
  62. package/src/config.ts +37 -0
  63. package/src/index.ts +10 -10
  64. package/src/mcpb.loader.ts +186 -0
  65. package/src/mcpb.unzip.ts +103 -0
  66. package/src/remote.transports.ts +316 -0
  67. package/src/remote.upstream.ts +75 -0
  68. package/src/stdio.upstream.ts +4 -1
  69. package/src/upstream.ts +33 -0
  70. package/src/ws.tunnel.builder.ts +19 -9
  71. 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":"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"}
@@ -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: an application can introduce any
6
- * value its custom resolver and JSON resources support.
4
+ * `<userAgent>/<locale>.json`. Open string: a host application can use any
5
+ * value its grammar resources support.
7
6
  *
8
- * The {@link defaultBrokerLocaleResolver} returns the ISO 639-1 prefix of a
9
- * BCP-47 tag (`"fr-CA"` → `"fr"`, `"zh-Hans"` → `"zh"`).
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. The {@link defaultBrokerUserAgentResolver}
15
- * recognizes the common LLM families and returns `"default"` for everything else.
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
- * Picks an **ordered fallback chain** of {@link BrokerLocale} values from a raw
20
- * input. Typically the raw input is `process.env.MCP_BROKER_LOCALE`, but any
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
- * The returned array is consumed by the broker server most-specific-first, so
24
- * the resolver controls the BCP-47 narrowing policy. The default resolver
25
- * follows the standard `lang-region` → `lang` → `en` shape:
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
- * raw = "fr-CA" → ["fr-ca", "fr", "en"]
29
- * raw = "en-US" → ["en-us", "en"]
30
- * raw = "zh" → ["zh", "en"]
31
- * raw = "" → ["en"]
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
- * A custom resolver may shape the chain however it wants — e.g. inject a
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 type BrokerLocaleResolver = (raw: string | undefined) => BrokerLocale[];
39
- /**
40
- * Picks a {@link BrokerUserAgent} from the connecting client's identity. Called
41
- * by the embedded broker `McpServer` once per session, during the MCP
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
- * Builds the canonical grammar key for the `(userAgent, locale)` matrix.
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
- * Pattern: `"<userAgent>:<locale>"` — e.g. `"claude:fr"`, `"default:en"`.
87
- * The colon separator is reserved for this composition and never appears in
88
- * user-agent or locale identifiers.
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 declare function iterBrokerGrammarsFrom(grammarsDir: string): Generator<{
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
- // Default resolvers
6
+ // Canonical grammar key
7
7
  // ---------------------------------------------------------------------------
8
8
  /**
9
- * Default locale resolver — emits the BCP-47 narrowing chain for a raw locale
10
- * tag, from most specific to least specific, always ending with the universal
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
- * The broker server tries each candidate in turn against `<userAgent>/<locale>.json`
20
- * — so dropping a `claude/fr-ca.json` lets Canadian-French Claude clients
21
- * pick up that specific dialect, while clients with `fr` or `fr-FR` fall back
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
- * Examples:
25
- * - `"fr-CA"` → `["fr-ca", "fr", "en"]`
26
- * - `"fr"` → `["fr", "en"]`
27
- * - `"zh-CN"` → `["zh-cn", "zh", "en"]`
28
- * - `"en-US"` → `["en-us", "en"]`
29
- * - `""` / `undefined` → `["en"]`
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 const defaultBrokerLocaleResolver = (raw) => {
32
- const a = [];
33
- if (raw) {
34
- const sep = "-";
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
- * Builds the canonical grammar key for the `(userAgent, locale)` matrix.
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
- * Pattern: `"<userAgent>:<locale>"` — e.g. `"claude:fr"`, `"default:en"`.
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 brokerGrammarKey(userAgent, locale) {
80
- return `${userAgent}:${locale}`;
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)` combination.
109
- * Returns `undefined` (instead of throwing) when the resource file is missing,
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 path = join(GRAMMARS_DIR, userAgent, `${locale}.json`);
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 locale = file.slice(0, -".json".length);
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 { userAgent, locale, key: brokerGrammarKey(userAgent, locale), grammar };
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":"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"}
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
- * Picks a locale (BCP-47 base language, by convention) for the current
20
- * session. Defaults to {@link defaultBrokerLocaleResolver} which reads
21
- * `MCP_BROKER_LOCALE` and keeps the ISO 639-1 prefix.
22
- */
23
- localeResolver?: BrokerLocaleResolver;
24
- /**
25
- * Picks a user-agent family for the connecting client. Defaults to
26
- * {@link defaultBrokerUserAgentResolver} which substring-matches
27
- * `clientInfo.name` against known LLM families.
28
- */
29
- userAgentResolver?: BrokerUserAgentResolver;
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
- localeSource?: () => string | undefined;
31
+ grammarResolverOptions?: Partial<GrammarResolverOptions>;
36
32
  /**
37
33
  * Path to a user-supplied grammars directory whose `<userAgent>/<locale>.json`
38
- * files are merged **on top of** the packaged grammars. Local entries win
39
- * on conflicts; missing entries fall through to the packaged values.
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 used.
43
+ * When `undefined` (default), only the packaged grammars are loaded.
42
44
  */
43
45
  localGrammarsDir?: string;
44
46
  }