toolception 0.3.0 → 0.5.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 (31) hide show
  1. package/README.md +182 -29
  2. package/dist/core/DynamicToolManager.d.ts +45 -2
  3. package/dist/core/DynamicToolManager.d.ts.map +1 -1
  4. package/dist/core/ServerOrchestrator.d.ts +24 -2
  5. package/dist/core/ServerOrchestrator.d.ts.map +1 -1
  6. package/dist/http/FastifyTransport.d.ts +17 -0
  7. package/dist/http/FastifyTransport.d.ts.map +1 -1
  8. package/dist/http/customEndpoints.d.ts +247 -0
  9. package/dist/http/customEndpoints.d.ts.map +1 -0
  10. package/dist/http/endpointRegistration.d.ts +35 -0
  11. package/dist/http/endpointRegistration.d.ts.map +1 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +797 -454
  15. package/dist/index.js.map +1 -1
  16. package/dist/meta/registerMetaTools.d.ts +14 -0
  17. package/dist/meta/registerMetaTools.d.ts.map +1 -1
  18. package/dist/mode/ModeResolver.d.ts +7 -0
  19. package/dist/mode/ModeResolver.d.ts.map +1 -1
  20. package/dist/permissions/PermissionAwareFastifyTransport.d.ts +9 -1
  21. package/dist/permissions/PermissionAwareFastifyTransport.d.ts.map +1 -1
  22. package/dist/permissions/PermissionResolver.d.ts +12 -0
  23. package/dist/permissions/PermissionResolver.d.ts.map +1 -1
  24. package/dist/permissions/createPermissionAwareBundle.d.ts +6 -0
  25. package/dist/permissions/createPermissionAwareBundle.d.ts.map +1 -1
  26. package/dist/server/createMcpServer.d.ts +4 -3
  27. package/dist/server/createMcpServer.d.ts.map +1 -1
  28. package/dist/server/createPermissionBasedMcpServer.d.ts.map +1 -1
  29. package/dist/session/ClientResourceCache.d.ts +34 -3
  30. package/dist/session/ClientResourceCache.d.ts.map +1 -1
  31. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,16 +1,16 @@
1
- var w = (r) => {
1
+ var P = (r) => {
2
2
  throw TypeError(r);
3
3
  };
4
- var B = (r, e, t) => e.has(r) || w("Cannot " + t);
5
- var v = (r, e, t) => e.has(r) ? w("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(r) : e.set(r, t);
6
- var u = (r, e, t) => (B(r, e, "access private method"), t);
7
- import { z as T } from "zod";
8
- import S from "fastify";
9
- import M from "@fastify/cors";
10
- import { randomUUID as p } from "node:crypto";
11
- import { StreamableHTTPServerTransport as C } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
12
- import { isInitializeRequest as x } from "@modelcontextprotocol/sdk/types.js";
13
- const b = {
4
+ var ee = (r, e, s) => e.has(r) || P("Cannot " + s);
5
+ var T = (r, e, s) => e.has(r) ? P("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(r) : e.set(r, s);
6
+ var f = (r, e, s) => (ee(r, e, "access private method"), s);
7
+ import { z as b } from "zod";
8
+ import N from "fastify";
9
+ import j from "@fastify/cors";
10
+ import { randomUUID as y } from "node:crypto";
11
+ import { StreamableHTTPServerTransport as k } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
12
+ import { isInitializeRequest as D } from "@modelcontextprotocol/sdk/types.js";
13
+ const L = {
14
14
  dynamic: [
15
15
  "dynamic-tool-discovery",
16
16
  "dynamicToolDiscovery",
@@ -18,89 +18,98 @@ const b = {
18
18
  ],
19
19
  toolsets: ["tool-sets", "toolSets", "FMP_TOOL_SETS"]
20
20
  };
21
- class Y {
21
+ class se {
22
22
  constructor(e = {}) {
23
23
  this.keys = {
24
- dynamic: e.keys?.dynamic ?? b.dynamic,
25
- toolsets: e.keys?.toolsets ?? b.toolsets
24
+ dynamic: e.keys?.dynamic ?? L.dynamic,
25
+ toolsets: e.keys?.toolsets ?? L.toolsets
26
26
  };
27
27
  }
28
- resolveMode(e, t) {
29
- return this.isDynamicEnabled(t) ? "DYNAMIC" : this.getToolsetsString(t) ? "STATIC" : this.isDynamicEnabled(e) ? "DYNAMIC" : this.getToolsetsString(e) ? "STATIC" : null;
28
+ resolveMode(e, s) {
29
+ return this.isDynamicEnabled(s) ? "DYNAMIC" : this.getToolsetsString(s) ? "STATIC" : this.isDynamicEnabled(e) ? "DYNAMIC" : this.getToolsetsString(e) ? "STATIC" : null;
30
30
  }
31
- parseCommaSeparatedToolSets(e, t) {
31
+ parseCommaSeparatedToolSets(e, s) {
32
32
  if (!e || typeof e != "string") return [];
33
- const s = e.split(",").map((a) => a.trim()).filter((a) => a.length > 0), o = new Set(Object.keys(t)), i = [];
34
- for (const a of s)
35
- o.has(a) ? i.push(a) : console.warn(
36
- `Invalid toolset '${a}' ignored. Available: ${Array.from(
33
+ const t = e.split(",").map((n) => n.trim()).filter((n) => n.length > 0), o = new Set(Object.keys(s)), i = [];
34
+ for (const n of t)
35
+ o.has(n) ? i.push(n) : console.warn(
36
+ `Invalid toolset '${n}' ignored. Available: ${Array.from(
37
37
  o
38
38
  ).join(", ")}`
39
39
  );
40
40
  return i;
41
41
  }
42
- getModulesForToolSets(e, t) {
43
- const s = /* @__PURE__ */ new Set();
42
+ getModulesForToolSets(e, s) {
43
+ const t = /* @__PURE__ */ new Set();
44
44
  for (const o of e) {
45
- const i = t[o];
46
- i && (i.modules || []).forEach((a) => s.add(a));
45
+ const i = s[o];
46
+ i && (i.modules || []).forEach((n) => t.add(n));
47
47
  }
48
- return Array.from(s);
48
+ return Array.from(t);
49
49
  }
50
- validateToolsetName(e, t) {
50
+ validateToolsetName(e, s) {
51
51
  if (!e || typeof e != "string")
52
52
  return {
53
53
  isValid: !1,
54
54
  error: `Invalid toolset name provided. Must be a non-empty string. Available toolsets: ${Object.keys(
55
- t
55
+ s
56
56
  ).join(", ")}`
57
57
  };
58
- const s = e.trim();
59
- return s.length === 0 ? {
58
+ const t = e.trim();
59
+ return t.length === 0 ? {
60
60
  isValid: !1,
61
61
  error: `Empty toolset name provided. Available toolsets: ${Object.keys(
62
- t
62
+ s
63
63
  ).join(", ")}`
64
- } : t[s] ? { isValid: !0, sanitized: s } : {
64
+ } : s[t] ? { isValid: !0, sanitized: t } : {
65
65
  isValid: !1,
66
- error: `Toolset '${s}' not found. Available toolsets: ${Object.keys(
67
- t
66
+ error: `Toolset '${t}' not found. Available toolsets: ${Object.keys(
67
+ s
68
68
  ).join(", ")}`
69
69
  };
70
70
  }
71
- validateToolsetModules(e, t) {
71
+ /**
72
+ * Validates and retrieves modules for a set of toolsets.
73
+ * Note: A toolset with only direct tools (no modules) is valid and returns an empty modules array.
74
+ * @param toolsetNames - Array of toolset names to validate
75
+ * @param catalog - The toolset catalog to validate against
76
+ * @returns Validation result with modules array if valid
77
+ */
78
+ validateToolsetModules(e, s) {
72
79
  try {
73
- const s = this.getModulesForToolSets(e, t);
74
- return !s || s.length === 0 ? {
75
- isValid: !1,
76
- error: `No modules found for toolsets: ${e.join(", ")}`
77
- } : { isValid: !0, modules: s };
78
- } catch (s) {
80
+ for (const o of e)
81
+ if (!s[o])
82
+ return {
83
+ isValid: !1,
84
+ error: `Toolset '${o}' not found in catalog`
85
+ };
86
+ return { isValid: !0, modules: this.getModulesForToolSets(e, s) };
87
+ } catch (t) {
79
88
  return {
80
89
  isValid: !1,
81
- error: `Error resolving modules for ${e.join(", ")}: ${s instanceof Error ? s.message : "Unknown error"}`
90
+ error: `Error resolving modules for ${e.join(", ")}: ${t instanceof Error ? t.message : "Unknown error"}`
82
91
  };
83
92
  }
84
93
  }
85
94
  isDynamicEnabled(e) {
86
95
  if (!e) return !1;
87
- for (const t of this.keys.dynamic) {
88
- const s = e[t];
89
- if (s === !0 || typeof s == "string" && s.trim().toLowerCase() === "true")
96
+ for (const s of this.keys.dynamic) {
97
+ const t = e[s];
98
+ if (t === !0 || typeof t == "string" && t.trim().toLowerCase() === "true")
90
99
  return !0;
91
100
  }
92
101
  return !1;
93
102
  }
94
103
  getToolsetsString(e) {
95
104
  if (e)
96
- for (const t of this.keys.toolsets) {
97
- const s = e[t];
98
- if (typeof s == "string" && s.trim().length > 0)
99
- return s;
105
+ for (const s of this.keys.toolsets) {
106
+ const t = e[s];
107
+ if (typeof t == "string" && t.trim().length > 0)
108
+ return t;
100
109
  }
101
110
  }
102
111
  }
103
- class U {
112
+ class te {
104
113
  constructor(e) {
105
114
  this.catalog = e.catalog, this.moduleLoaders = e.moduleLoaders ?? {};
106
115
  }
@@ -118,80 +127,80 @@ class U {
118
127
  ", "
119
128
  )}`
120
129
  };
121
- const t = e.trim();
122
- return t.length === 0 ? {
130
+ const s = e.trim();
131
+ return s.length === 0 ? {
123
132
  isValid: !1,
124
133
  error: `Empty toolset name provided. Available toolsets: ${this.getAvailableToolsets().join(
125
134
  ", "
126
135
  )}`
127
- } : this.catalog[t] ? { isValid: !0, sanitized: t } : {
136
+ } : this.catalog[s] ? { isValid: !0, sanitized: s } : {
128
137
  isValid: !1,
129
- error: `Toolset '${t}' not found. Available toolsets: ${this.getAvailableToolsets().join(
138
+ error: `Toolset '${s}' not found. Available toolsets: ${this.getAvailableToolsets().join(
130
139
  ", "
131
140
  )}`
132
141
  };
133
142
  }
134
- async resolveToolsForToolsets(e, t) {
135
- const s = [];
143
+ async resolveToolsForToolsets(e, s) {
144
+ const t = [];
136
145
  for (const o of e) {
137
146
  const i = this.catalog[o];
138
- if (i && (Array.isArray(i.tools) && i.tools.length > 0 && s.push(...i.tools), Array.isArray(i.modules) && i.modules.length > 0))
139
- for (const a of i.modules) {
140
- const l = this.moduleLoaders[a];
147
+ if (i && (Array.isArray(i.tools) && i.tools.length > 0 && t.push(...i.tools), Array.isArray(i.modules) && i.modules.length > 0))
148
+ for (const n of i.modules) {
149
+ const l = this.moduleLoaders[n];
141
150
  if (l)
142
151
  try {
143
- const n = await l(t);
144
- Array.isArray(n) && n.length > 0 && s.push(...n);
145
- } catch (n) {
152
+ const a = await l(s);
153
+ Array.isArray(a) && a.length > 0 && t.push(...a);
154
+ } catch (a) {
146
155
  console.warn(
147
- `Module loader '${a}' failed for toolset '${o}':`,
148
- n
156
+ `Module loader '${n}' failed for toolset '${o}':`,
157
+ a
149
158
  );
150
159
  }
151
160
  }
152
161
  }
153
- return s;
162
+ return t;
154
163
  }
155
164
  }
156
- class A extends Error {
157
- constructor(e, t, s, o) {
158
- super(e), this.name = "ToolingError", this.code = t, this.details = s;
165
+ class R extends Error {
166
+ constructor(e, s, t, o) {
167
+ super(e), this.name = "ToolingError", this.code = s, this.details = t;
159
168
  }
160
169
  }
161
- class I {
170
+ class z {
162
171
  constructor(e = {}) {
163
172
  this.names = /* @__PURE__ */ new Set(), this.toolsetToNames = /* @__PURE__ */ new Map(), this.options = {
164
173
  namespaceWithToolset: e.namespaceWithToolset ?? !0
165
174
  };
166
175
  }
167
- getSafeName(e, t) {
168
- return !this.options.namespaceWithToolset || t.startsWith(`${e}.`) ? t : `${e}.${t}`;
176
+ getSafeName(e, s) {
177
+ return !this.options.namespaceWithToolset || s.startsWith(`${e}.`) ? s : `${e}.${s}`;
169
178
  }
170
179
  has(e) {
171
180
  return this.names.has(e);
172
181
  }
173
182
  add(e) {
174
183
  if (this.names.has(e))
175
- throw new A(
184
+ throw new R(
176
185
  `Tool name collision: '${e}' already registered`,
177
186
  "E_TOOL_NAME_CONFLICT"
178
187
  );
179
188
  this.names.add(e);
180
189
  }
181
- addForToolset(e, t) {
182
- this.add(t);
183
- const s = this.toolsetToNames.get(e) ?? /* @__PURE__ */ new Set();
184
- s.add(t), this.toolsetToNames.set(e, s);
190
+ addForToolset(e, s) {
191
+ this.add(s);
192
+ const t = this.toolsetToNames.get(e) ?? /* @__PURE__ */ new Set();
193
+ t.add(s), this.toolsetToNames.set(e, t);
185
194
  }
186
- mapAndValidate(e, t) {
187
- return t.map((s) => {
188
- const o = this.getSafeName(e, s.name);
195
+ mapAndValidate(e, s) {
196
+ return s.map((t) => {
197
+ const o = this.getSafeName(e, t.name);
189
198
  if (this.has(o))
190
- throw new A(
199
+ throw new R(
191
200
  `Tool name collision for '${o}'`,
192
201
  "E_TOOL_NAME_CONFLICT"
193
202
  );
194
- return { ...s, name: o };
203
+ return { ...t, name: o };
195
204
  });
196
205
  }
197
206
  list() {
@@ -199,14 +208,28 @@ class I {
199
208
  }
200
209
  listByToolset() {
201
210
  const e = {};
202
- for (const [t, s] of this.toolsetToNames.entries())
203
- e[t] = Array.from(s);
211
+ for (const [s, t] of this.toolsetToNames.entries())
212
+ e[s] = Array.from(t);
204
213
  return e;
205
214
  }
206
215
  }
207
- class W {
216
+ class oe {
208
217
  constructor(e) {
209
- this.activeToolsets = /* @__PURE__ */ new Set(), this.server = e.server, this.resolver = e.resolver, this.context = e.context, this.onToolsListChanged = e.onToolsListChanged, this.exposurePolicy = e.exposurePolicy, this.toolRegistry = e.toolRegistry ?? new I({ namespaceWithToolset: !0 });
218
+ this.activeToolsets = /* @__PURE__ */ new Set(), this.server = e.server, this.resolver = e.resolver, this.context = e.context, this.onToolsListChanged = e.onToolsListChanged, this.exposurePolicy = e.exposurePolicy, this.toolRegistry = e.toolRegistry ?? new z({ namespaceWithToolset: !0 });
219
+ }
220
+ /**
221
+ * Sends a tool list change notification if configured.
222
+ * Logs warnings on failure instead of throwing.
223
+ * @returns Promise that resolves when notification is sent (or skipped)
224
+ * @private
225
+ */
226
+ async notifyToolsChanged() {
227
+ if (this.onToolsListChanged)
228
+ try {
229
+ await this.onToolsListChanged();
230
+ } catch (e) {
231
+ console.warn("Failed to send tool list change notification:", e);
232
+ }
210
233
  }
211
234
  getAvailableToolsets() {
212
235
  return this.resolver.getAvailableToolsets();
@@ -220,90 +243,113 @@ class W {
220
243
  isActive(e) {
221
244
  return this.activeToolsets.has(e);
222
245
  }
223
- async enableToolset(e) {
246
+ /**
247
+ * Enables a single toolset by name.
248
+ * Validates the toolset, checks exposure policies, resolves tools, and registers them.
249
+ * @param toolsetName - The name of the toolset to enable
250
+ * @param skipNotification - If true, skips the tool list change notification (for batch operations)
251
+ * @returns Result object with success status and message
252
+ */
253
+ async enableToolset(e, s = !1) {
224
254
  const t = this.resolver.validateToolsetName(e);
225
255
  if (!t.isValid || !t.sanitized)
226
256
  return {
227
257
  success: !1,
228
258
  message: t.error || "Unknown validation error"
229
259
  };
230
- const s = t.sanitized;
231
- if (this.activeToolsets.has(s))
260
+ const o = t.sanitized;
261
+ if (this.activeToolsets.has(o))
232
262
  return {
233
263
  success: !1,
234
- message: `Toolset '${s}' is already enabled.`
264
+ message: `Toolset '${o}' is already enabled.`
235
265
  };
266
+ const i = this.checkExposurePolicy(o);
267
+ if (!i.allowed)
268
+ return { success: !1, message: i.message };
269
+ const n = [];
236
270
  try {
237
- const o = await this.resolver.resolveToolsForToolsets(
238
- [s],
271
+ const l = await this.resolver.resolveToolsForToolsets(
272
+ [o],
239
273
  this.context
240
274
  );
241
- if (this.exposurePolicy?.allowlist && !this.exposurePolicy.allowlist.includes(s))
242
- return {
243
- success: !1,
244
- message: `Toolset '${s}' is not allowed by policy.`
245
- };
246
- if (this.exposurePolicy?.denylist && this.exposurePolicy.denylist.includes(s))
247
- return {
248
- success: !1,
249
- message: `Toolset '${s}' is denied by policy.`
250
- };
251
- if (this.exposurePolicy?.maxActiveToolsets !== void 0 && this.activeToolsets.size + 1 > this.exposurePolicy.maxActiveToolsets)
252
- return this.exposurePolicy.onLimitExceeded?.(
253
- [s],
254
- Array.from(this.activeToolsets)
255
- ), {
256
- success: !1,
257
- message: `Activation exceeds maxActiveToolsets (${this.exposurePolicy.maxActiveToolsets}).`
258
- };
259
- if (o && o.length > 0) {
260
- const i = this.toolRegistry.mapAndValidate(
261
- s,
262
- o
275
+ if (l && l.length > 0) {
276
+ const a = this.toolRegistry.mapAndValidate(
277
+ o,
278
+ l
263
279
  );
264
- this.registerDirectTools(i, s);
265
- }
266
- this.activeToolsets.add(s);
267
- try {
268
- await this.onToolsListChanged?.();
269
- } catch (i) {
270
- console.warn("Failed to send tool list change notification:", i);
280
+ for (const c of a)
281
+ this.registerSingleTool(c, o), n.push(c.name);
271
282
  }
272
- return {
283
+ return this.activeToolsets.add(o), s || await this.notifyToolsChanged(), {
273
284
  success: !0,
274
- message: `Toolset '${s}' enabled successfully. Registered ${o?.length ?? 0} tools.`
285
+ message: `Toolset '${o}' enabled successfully. Registered ${l?.length ?? 0} tools.`
275
286
  };
276
- } catch (o) {
277
- return this.activeToolsets.delete(s), {
287
+ } catch (l) {
288
+ return n.length > 0 && console.warn(
289
+ `Partial failure enabling toolset '${o}'. ${n.length} tools were registered but toolset activation failed. Tools remain registered due to MCP limitations: ${n.join(", ")}`
290
+ ), {
278
291
  success: !1,
279
- message: `Failed to enable toolset '${s}': ${o instanceof Error ? o.message : "Unknown error"}`
292
+ message: `Failed to enable toolset '${o}': ${l instanceof Error ? l.message : "Unknown error"}`
280
293
  };
281
294
  }
282
295
  }
296
+ /**
297
+ * Checks if a toolset is allowed by the exposure policy.
298
+ * @param toolsetName - The sanitized toolset name to check
299
+ * @returns Object indicating if allowed and reason message if not
300
+ * @private
301
+ */
302
+ checkExposurePolicy(e) {
303
+ return this.exposurePolicy?.allowlist && !this.exposurePolicy.allowlist.includes(e) ? {
304
+ allowed: !1,
305
+ message: `Toolset '${e}' is not allowed by policy.`
306
+ } : this.exposurePolicy?.denylist && this.exposurePolicy.denylist.includes(e) ? {
307
+ allowed: !1,
308
+ message: `Toolset '${e}' is denied by policy.`
309
+ } : this.exposurePolicy?.maxActiveToolsets !== void 0 && this.activeToolsets.size + 1 > this.exposurePolicy.maxActiveToolsets ? (this.exposurePolicy.onLimitExceeded?.(
310
+ [e],
311
+ Array.from(this.activeToolsets)
312
+ ), {
313
+ allowed: !1,
314
+ message: `Activation exceeds maxActiveToolsets (${this.exposurePolicy.maxActiveToolsets}).`
315
+ }) : { allowed: !0, message: "" };
316
+ }
317
+ /**
318
+ * Registers a single tool with the MCP server.
319
+ * @param tool - The tool definition to register
320
+ * @param toolsetKey - The toolset key for tracking
321
+ * @private
322
+ */
323
+ registerSingleTool(e, s) {
324
+ this.server.tool(
325
+ e.name,
326
+ e.description,
327
+ e.inputSchema,
328
+ async (t) => await e.handler(t)
329
+ ), this.toolRegistry.addForToolset(s, e.name);
330
+ }
331
+ /**
332
+ * Disables a toolset by name.
333
+ * Note: Due to MCP limitations, tools remain registered but the toolset is marked inactive.
334
+ * @param toolsetName - The name of the toolset to disable
335
+ * @returns Result object with success status and message
336
+ */
283
337
  async disableToolset(e) {
284
- const t = this.resolver.validateToolsetName(e);
285
- if (!t.isValid || !t.sanitized) {
338
+ const s = this.resolver.validateToolsetName(e);
339
+ if (!s.isValid || !s.sanitized) {
286
340
  const o = Array.from(this.activeToolsets).join(", ") || "none";
287
341
  return {
288
342
  success: !1,
289
- message: `${t.error || "Unknown validation error"} Active toolsets: ${o}`
343
+ message: `${s.error || "Unknown validation error"} Active toolsets: ${o}`
290
344
  };
291
345
  }
292
- const s = t.sanitized;
293
- if (!this.activeToolsets.has(s))
294
- return {
295
- success: !1,
296
- message: `Toolset '${s}' is not currently active. Active toolsets: ${Array.from(this.activeToolsets).join(", ") || "none"}`
297
- };
298
- this.activeToolsets.delete(s);
299
- try {
300
- await this.onToolsListChanged?.();
301
- } catch (o) {
302
- console.warn("Failed to send tool list change notification:", o);
303
- }
304
- return {
346
+ const t = s.sanitized;
347
+ return this.activeToolsets.has(t) ? (this.activeToolsets.delete(t), await this.notifyToolsChanged(), {
305
348
  success: !0,
306
- message: `Toolset '${s}' disabled successfully. Individual tools remain registered due to MCP limitations.`
349
+ message: `Toolset '${t}' disabled successfully. Individual tools remain registered due to MCP limitations.`
350
+ }) : {
351
+ success: !1,
352
+ message: `Toolset '${t}' is not currently active. Active toolsets: ${Array.from(this.activeToolsets).join(", ") || "none"}`
307
353
  };
308
354
  }
309
355
  getStatus() {
@@ -317,121 +363,112 @@ class W {
317
363
  toolsetToTools: this.toolRegistry.listByToolset()
318
364
  };
319
365
  }
366
+ /**
367
+ * Enables multiple toolsets in a batch operation.
368
+ * Sends a single notification after all toolsets are processed.
369
+ * @param toolsetNames - Array of toolset names to enable
370
+ * @returns Result object with overall success status and individual results
371
+ */
320
372
  async enableToolsets(e) {
321
- const t = [];
322
- for (const i of e)
373
+ const s = [];
374
+ for (const n of e)
323
375
  try {
324
- const a = await this.enableToolset(i);
325
- t.push({ name: i, ...a });
326
- } catch (a) {
327
- t.push({
328
- name: i,
376
+ const l = await this.enableToolset(n, !0);
377
+ s.push({ name: n, ...l });
378
+ } catch (l) {
379
+ s.push({
380
+ name: n,
329
381
  success: !1,
330
- message: a instanceof Error ? a.message : "Unknown error",
382
+ message: l instanceof Error ? l.message : "Unknown error",
331
383
  code: "E_INTERNAL"
332
384
  });
333
385
  }
334
- const s = t.every((i) => i.success), o = s ? "All toolsets enabled" : "Some toolsets failed to enable";
335
- if (t.length > 0)
336
- try {
337
- await this.onToolsListChanged?.();
338
- } catch {
339
- }
340
- return { success: s, results: t, message: o };
341
- }
342
- registerDirectTools(e, t) {
343
- for (const s of e)
344
- try {
345
- this.server.tool(
346
- s.name,
347
- s.description,
348
- s.inputSchema,
349
- async (o) => await s.handler(o)
350
- ), t ? this.toolRegistry.addForToolset(t, s.name) : this.toolRegistry.add(s.name);
351
- } catch (o) {
352
- throw console.error(`Failed to register direct tool '${s.name}':`, o), o;
353
- }
386
+ const t = s.every((n) => n.success), o = s.some((n) => n.success), i = t ? "All toolsets enabled" : o ? "Some toolsets failed to enable" : "All toolsets failed to enable";
387
+ return o && await this.notifyToolsChanged(), { success: t, results: s, message: i };
354
388
  }
389
+ /**
390
+ * Enables all available toolsets in a batch operation.
391
+ * @returns Result object with overall success status and individual results
392
+ */
355
393
  async enableAllToolsets() {
356
394
  const e = this.getAvailableToolsets();
357
395
  return this.enableToolsets(e);
358
396
  }
359
397
  }
360
- function H(r, e, t) {
361
- const s = t?.mode ?? "DYNAMIC";
362
- r.tool(
398
+ function re(r, e, s) {
399
+ (s?.mode ?? "DYNAMIC") === "DYNAMIC" && (r.tool(
363
400
  "enable_toolset",
364
401
  "Enable a toolset by name",
365
- { name: T.string().describe("Toolset name") },
402
+ { name: b.string().describe("Toolset name") },
366
403
  async (o) => {
367
- const { name: i } = o, a = await e.enableToolset(i);
404
+ const i = await e.enableToolset(o.name);
368
405
  return {
369
- content: [{ type: "text", text: JSON.stringify(a) }]
406
+ content: [{ type: "text", text: JSON.stringify(i) }]
370
407
  };
371
408
  }
372
409
  ), r.tool(
373
410
  "disable_toolset",
374
411
  "Disable a toolset by name (state only)",
375
- { name: T.string().describe("Toolset name") },
412
+ { name: b.string().describe("Toolset name") },
376
413
  async (o) => {
377
- const { name: i } = o, a = await e.disableToolset(i);
414
+ const i = await e.disableToolset(o.name);
378
415
  return {
379
- content: [{ type: "text", text: JSON.stringify(a) }]
416
+ content: [{ type: "text", text: JSON.stringify(i) }]
380
417
  };
381
418
  }
382
- ), s === "DYNAMIC" && (r.tool(
419
+ ), r.tool(
383
420
  "list_toolsets",
384
421
  "List available toolsets with active status and definitions",
385
422
  {},
386
423
  async () => {
387
- const o = e.getAvailableToolsets(), i = e.getStatus().toolsetToTools, a = o.map((l) => {
388
- const n = e.getToolsetDefinition(l);
424
+ const o = e.getAvailableToolsets(), i = e.getStatus().toolsetToTools, n = o.map((l) => {
425
+ const a = e.getToolsetDefinition(l);
389
426
  return {
390
427
  key: l,
391
428
  active: e.isActive(l),
392
- definition: n ? {
393
- name: n.name,
394
- description: n.description,
395
- modules: n.modules ?? [],
396
- decisionCriteria: n.decisionCriteria ?? void 0
429
+ definition: a ? {
430
+ name: a.name,
431
+ description: a.description,
432
+ modules: a.modules ?? [],
433
+ decisionCriteria: a.decisionCriteria ?? void 0
397
434
  } : null,
398
435
  tools: i[l] ?? []
399
436
  };
400
437
  });
401
438
  return {
402
439
  content: [
403
- { type: "text", text: JSON.stringify({ toolsets: a }) }
440
+ { type: "text", text: JSON.stringify({ toolsets: n }) }
404
441
  ]
405
442
  };
406
443
  }
407
444
  ), r.tool(
408
445
  "describe_toolset",
409
446
  "Describe a toolset with definition, active status and tools",
410
- { name: T.string().describe("Toolset name") },
447
+ { name: b.string().describe("Toolset name") },
411
448
  async (o) => {
412
- const { name: i } = o, a = e.getToolsetDefinition(i), l = e.getStatus().toolsetToTools;
413
- if (!a)
449
+ const i = e.getToolsetDefinition(o.name), n = e.getStatus().toolsetToTools;
450
+ if (!i)
414
451
  return {
415
452
  content: [
416
453
  {
417
454
  type: "text",
418
- text: JSON.stringify({ error: `Unknown toolset '${i}'` })
455
+ text: JSON.stringify({ error: `Unknown toolset '${o.name}'` })
419
456
  }
420
457
  ]
421
458
  };
422
- const n = {
423
- key: i,
424
- active: e.isActive(i),
459
+ const l = {
460
+ key: o.name,
461
+ active: e.isActive(o.name),
425
462
  definition: {
426
- name: a.name,
427
- description: a.description,
428
- modules: a.modules ?? [],
429
- decisionCriteria: a.decisionCriteria ?? void 0
463
+ name: i.name,
464
+ description: i.description,
465
+ modules: i.modules ?? [],
466
+ decisionCriteria: i.decisionCriteria ?? void 0
430
467
  },
431
- tools: l[i] ?? []
468
+ tools: n[o.name] ?? []
432
469
  };
433
470
  return {
434
- content: [{ type: "text", text: JSON.stringify(n) }]
471
+ content: [{ type: "text", text: JSON.stringify(l) }]
435
472
  };
436
473
  }
437
474
  )), r.tool(
@@ -449,41 +486,72 @@ function H(r, e, t) {
449
486
  }
450
487
  );
451
488
  }
452
- class y {
489
+ class w {
453
490
  constructor(e) {
454
- this.toolsetValidator = new Y();
455
- const t = e.startup ?? {}, s = this.resolveStartupConfig(t, e.catalog);
456
- this.mode = s.mode, this.resolver = new U({
491
+ this.initError = null, this.toolsetValidator = new se();
492
+ const s = e.startup ?? {}, t = this.resolveStartupConfig(s, e.catalog);
493
+ this.mode = t.mode, this.resolver = new te({
457
494
  catalog: e.catalog,
458
495
  moduleLoaders: e.moduleLoaders
459
496
  });
460
- const o = new I({
497
+ const o = new z({
461
498
  namespaceWithToolset: e.exposurePolicy?.namespaceToolsWithSetKey ?? !0
462
499
  });
463
- this.manager = new W({
500
+ this.manager = new oe({
464
501
  server: e.server,
465
502
  resolver: this.resolver,
466
503
  context: e.context,
467
504
  onToolsListChanged: e.notifyToolsListChanged,
468
505
  exposurePolicy: e.exposurePolicy,
469
506
  toolRegistry: o
470
- }), e.registerMetaTools !== !1 && H(e.server, this.manager, { mode: this.mode });
471
- const i = s.toolsets;
472
- i === "ALL" ? this.manager.enableToolsets(this.resolver.getAvailableToolsets()) : Array.isArray(i) && i.length > 0 && this.manager.enableToolsets(i);
507
+ }), e.registerMetaTools !== !1 && re(e.server, this.manager, { mode: this.mode });
508
+ const i = t.toolsets;
509
+ this.initPromise = this.initializeToolsets(i);
510
+ }
511
+ /**
512
+ * Initializes toolsets asynchronously during construction.
513
+ * Stores any errors for later retrieval via ensureReady().
514
+ * @param initial - The toolsets to initialize or "ALL"
515
+ * @returns Promise that resolves when initialization is complete
516
+ * @private
517
+ */
518
+ async initializeToolsets(e) {
519
+ try {
520
+ e === "ALL" ? await this.manager.enableToolsets(this.resolver.getAvailableToolsets()) : Array.isArray(e) && e.length > 0 && await this.manager.enableToolsets(e);
521
+ } catch (s) {
522
+ this.initError = s instanceof Error ? s : new Error(String(s)), console.error("Failed to initialize toolsets:", this.initError);
523
+ }
524
+ }
525
+ /**
526
+ * Waits for the orchestrator to be fully initialized.
527
+ * Call this before using the orchestrator to ensure all toolsets are loaded.
528
+ * @throws {Error} If initialization failed
529
+ */
530
+ async ensureReady() {
531
+ if (await this.initPromise, this.initError)
532
+ throw this.initError;
533
+ }
534
+ /**
535
+ * Checks if the orchestrator has finished initialization.
536
+ * Does not throw on error - use ensureReady() for that.
537
+ * @returns Promise that resolves to true if ready, false if initialization failed
538
+ */
539
+ async isReady() {
540
+ return await this.initPromise, this.initError === null;
473
541
  }
474
- resolveStartupConfig(e, t) {
542
+ resolveStartupConfig(e, s) {
475
543
  if (e.mode) {
476
544
  if (e.mode === "DYNAMIC" && e.toolsets)
477
545
  return console.warn("startup.toolsets provided but ignored in DYNAMIC mode"), { mode: "DYNAMIC" };
478
546
  if (e.mode === "STATIC") {
479
547
  if (e.toolsets === "ALL")
480
548
  return { mode: "STATIC", toolsets: "ALL" };
481
- const s = Array.isArray(e.toolsets) ? e.toolsets : [], o = [];
482
- for (const i of s) {
483
- const { isValid: a, sanitized: l, error: n } = this.toolsetValidator.validateToolsetName(i, t);
484
- a && l ? o.push(l) : n && console.warn(n);
549
+ const t = Array.isArray(e.toolsets) ? e.toolsets : [], o = [];
550
+ for (const i of t) {
551
+ const { isValid: n, sanitized: l, error: a } = this.toolsetValidator.validateToolsetName(i, s);
552
+ n && l ? o.push(l) : a && console.warn(a);
485
553
  }
486
- if (s.length > 0 && o.length === 0)
554
+ if (t.length > 0 && o.length === 0)
487
555
  throw new Error(
488
556
  "STATIC mode requires valid toolsets or 'ALL'; none were valid"
489
557
  );
@@ -493,16 +561,16 @@ class y {
493
561
  }
494
562
  if (e.toolsets === "ALL") return { mode: "STATIC", toolsets: "ALL" };
495
563
  if (Array.isArray(e.toolsets) && e.toolsets.length > 0) {
496
- const s = [];
564
+ const t = [];
497
565
  for (const o of e.toolsets) {
498
- const { isValid: i, sanitized: a, error: l } = this.toolsetValidator.validateToolsetName(o, t);
499
- i && a ? s.push(a) : l && console.warn(l);
566
+ const { isValid: i, sanitized: n, error: l } = this.toolsetValidator.validateToolsetName(o, s);
567
+ i && n ? t.push(n) : l && console.warn(l);
500
568
  }
501
- if (s.length === 0)
569
+ if (t.length === 0)
502
570
  throw new Error(
503
571
  "STATIC mode requires valid toolsets or 'ALL'; none were valid"
504
572
  );
505
- return { mode: "STATIC", toolsets: s };
573
+ return { mode: "STATIC", toolsets: t };
506
574
  }
507
575
  return { mode: "DYNAMIC" };
508
576
  }
@@ -513,11 +581,13 @@ class y {
513
581
  return this.manager;
514
582
  }
515
583
  }
516
- class P {
584
+ var v, S;
585
+ class O {
517
586
  constructor(e = {}) {
518
- this.storage = /* @__PURE__ */ new Map(), this.maxSize = e.maxSize ?? 1e3, this.ttlMs = e.ttlMs ?? 1e3 * 60 * 60;
519
- const t = e.pruneIntervalMs ?? 1e3 * 60 * 10;
520
- this.pruneInterval = setInterval(() => this.pruneExpired(), t);
587
+ T(this, v);
588
+ this.storage = /* @__PURE__ */ new Map(), this.maxSize = e.maxSize ?? 1e3, this.ttlMs = e.ttlMs ?? 1e3 * 60 * 60, this.onEvict = e.onEvict;
589
+ const s = e.pruneIntervalMs ?? 1e3 * 60 * 10;
590
+ this.pruneInterval = setInterval(() => this.pruneExpired(), s);
521
591
  }
522
592
  getEntryCount() {
523
593
  return this.storage.size;
@@ -529,47 +599,184 @@ class P {
529
599
  return this.ttlMs;
530
600
  }
531
601
  get(e) {
532
- const t = this.storage.get(e);
533
- return t ? Date.now() - t.lastAccessed > this.ttlMs ? (this.delete(e), null) : (t.lastAccessed = Date.now(), this.storage.delete(e), this.storage.set(e, t), t.resource) : null;
602
+ const s = this.storage.get(e);
603
+ return s ? Date.now() - s.lastAccessed > this.ttlMs ? (this.delete(e), null) : (s.lastAccessed = Date.now(), this.storage.delete(e), this.storage.set(e, s), s.resource) : null;
534
604
  }
535
- set(e, t) {
605
+ set(e, s) {
536
606
  this.storage.size >= this.maxSize && this.evictLeastRecentlyUsed();
537
- const s = { resource: t, lastAccessed: Date.now() };
538
- this.storage.set(e, s);
607
+ const t = { resource: s, lastAccessed: Date.now() };
608
+ this.storage.set(e, t);
539
609
  }
610
+ /**
611
+ * Removes an entry from the cache.
612
+ * Calls the onEvict callback if configured.
613
+ * @param key - The key to remove
614
+ */
540
615
  delete(e) {
541
- this.storage.delete(e);
616
+ const s = this.storage.get(e);
617
+ s && (this.storage.delete(e), f(this, v, S).call(this, e, s.resource));
542
618
  }
543
- stop() {
544
- this.pruneInterval && (clearInterval(this.pruneInterval), this.pruneInterval = void 0);
619
+ /**
620
+ * Stops the background pruning interval and optionally clears all entries.
621
+ * @param clearEntries - If true, also removes all entries and calls onEvict for each
622
+ */
623
+ stop(e = !1) {
624
+ this.pruneInterval && (clearInterval(this.pruneInterval), this.pruneInterval = void 0), e && this.clear();
545
625
  }
626
+ /**
627
+ * Clears all entries from the cache.
628
+ * Calls onEvict for each entry being removed.
629
+ */
630
+ clear() {
631
+ const e = Array.from(this.storage.entries());
632
+ this.storage.clear();
633
+ for (const [s, t] of e)
634
+ f(this, v, S).call(this, s, t.resource);
635
+ }
636
+ /**
637
+ * Evicts the least recently used entry from the cache.
638
+ * @private
639
+ */
546
640
  evictLeastRecentlyUsed() {
547
641
  const e = this.storage.keys().next().value;
548
642
  e && this.delete(e);
549
643
  }
644
+ /**
645
+ * Removes all expired entries from the cache.
646
+ * @private
647
+ */
550
648
  pruneExpired() {
551
- const e = Date.now();
552
- for (const [t, s] of this.storage.entries())
553
- e - s.lastAccessed > this.ttlMs && this.delete(t);
649
+ const e = Date.now(), s = [];
650
+ for (const [t, o] of this.storage.entries())
651
+ e - o.lastAccessed > this.ttlMs && s.push(t);
652
+ for (const t of s)
653
+ this.delete(t);
654
+ }
655
+ }
656
+ v = new WeakSet(), /**
657
+ * Safely calls the evict callback, catching and logging any errors.
658
+ * @param key - The key being evicted
659
+ * @param resource - The resource being evicted
660
+ * @private
661
+ */
662
+ S = function(e, s) {
663
+ if (this.onEvict)
664
+ try {
665
+ const t = this.onEvict(e, s);
666
+ t instanceof Promise && t.catch((o) => {
667
+ console.warn(`Error in cache eviction callback for key '${e}':`, o);
668
+ });
669
+ } catch (t) {
670
+ console.warn(`Error in cache eviction callback for key '${e}':`, t);
671
+ }
672
+ };
673
+ function V(r, e, s, t) {
674
+ const o = ["/mcp", "/healthz", "/tools", "/.well-known/mcp-config"];
675
+ for (const i of s) {
676
+ const n = `${e}${i.path}`;
677
+ if (o.some(
678
+ (c) => n.startsWith(`${e}${c}`)
679
+ )) {
680
+ console.warn(
681
+ `Custom endpoint ${i.method} ${i.path} conflicts with built-in MCP endpoint. Skipping registration.`
682
+ );
683
+ continue;
684
+ }
685
+ const a = i.method.toLowerCase();
686
+ r[a](n, async (c, d) => {
687
+ try {
688
+ const u = c.headers["mcp-client-id"]?.trim(), p = u && u.length > 0 ? u : `anon-${y()}`;
689
+ let C;
690
+ if (i.bodySchema) {
691
+ const m = i.bodySchema.safeParse(c.body);
692
+ if (!m.success)
693
+ return A(d, "body", m.error);
694
+ C = m.data;
695
+ }
696
+ let x = {};
697
+ if (i.querySchema) {
698
+ const m = i.querySchema.safeParse(c.query);
699
+ if (!m.success)
700
+ return A(d, "query", m.error);
701
+ x = m.data;
702
+ }
703
+ let I = {};
704
+ if (i.paramsSchema) {
705
+ const m = i.paramsSchema.safeParse(c.params);
706
+ if (!m.success)
707
+ return A(d, "params", m.error);
708
+ I = m.data;
709
+ }
710
+ const M = {
711
+ body: C,
712
+ query: x,
713
+ params: I,
714
+ headers: c.headers,
715
+ clientId: p
716
+ };
717
+ if (t?.contextExtractor) {
718
+ const m = await t.contextExtractor(c);
719
+ Object.assign(M, m);
720
+ }
721
+ const $ = await i.handler(M);
722
+ if (i.responseSchema) {
723
+ const m = i.responseSchema.safeParse($);
724
+ return m.success ? m.data : (console.error(
725
+ `Response validation failed for ${i.method} ${i.path}:`,
726
+ m.error
727
+ ), d.code(500), {
728
+ error: {
729
+ code: "RESPONSE_VALIDATION_ERROR",
730
+ message: "Internal server error: invalid response format"
731
+ }
732
+ });
733
+ }
734
+ return $;
735
+ } catch (u) {
736
+ return console.error(
737
+ `Error in custom endpoint ${i.method} ${i.path}:`,
738
+ u
739
+ ), d.code(500), {
740
+ error: {
741
+ code: "INTERNAL_ERROR",
742
+ message: u instanceof Error ? u.message : "Internal server error"
743
+ }
744
+ };
745
+ }
746
+ });
554
747
  }
555
748
  }
556
- class J {
557
- constructor(e, t, s = {}, o) {
558
- this.app = null, this.clientCache = new P(), this.defaultManager = e, this.createBundle = t, this.options = {
559
- host: s.host ?? "0.0.0.0",
560
- port: s.port ?? 3e3,
561
- basePath: s.basePath ?? "/",
562
- cors: s.cors ?? !0,
563
- logger: s.logger ?? !1,
564
- app: s.app
749
+ function A(r, e, s) {
750
+ return r.code(400), {
751
+ error: {
752
+ code: "VALIDATION_ERROR",
753
+ message: `Validation failed for ${e}`,
754
+ details: s.errors
755
+ }
756
+ };
757
+ }
758
+ class ie {
759
+ constructor(e, s, t = {}, o) {
760
+ this.app = null, this.clientCache = new O({
761
+ onEvict: (i, n) => {
762
+ this.cleanupBundle(n);
763
+ }
764
+ }), this.defaultManager = e, this.createBundle = s, this.options = {
765
+ host: t.host ?? "0.0.0.0",
766
+ port: t.port ?? 3e3,
767
+ basePath: t.basePath ?? "/",
768
+ cors: t.cors ?? !0,
769
+ logger: t.logger ?? !1,
770
+ app: t.app,
771
+ customEndpoints: t.customEndpoints
565
772
  }, this.configSchema = o;
566
773
  }
567
774
  async start() {
568
775
  if (this.app) return;
569
- const e = this.options.app ?? S({ logger: this.options.logger });
570
- this.options.cors && await e.register(M, { origin: !0 });
571
- const t = this.options.basePath.endsWith("/") ? this.options.basePath.slice(0, -1) : this.options.basePath;
572
- e.get(`${t}/healthz`, async () => ({ ok: !0 })), e.get(`${t}/tools`, async () => this.defaultManager.getStatus()), e.get(`${t}/.well-known/mcp-config`, async (s, o) => (o.header("Content-Type", "application/schema+json; charset=utf-8"), this.configSchema ?? {
776
+ const e = this.options.app ?? N({ logger: this.options.logger });
777
+ this.options.cors && await e.register(j, { origin: !0 });
778
+ const s = this.options.basePath.endsWith("/") ? this.options.basePath.slice(0, -1) : this.options.basePath;
779
+ e.get(`${s}/healthz`, async () => ({ ok: !0 })), e.get(`${s}/tools`, async () => this.defaultManager.getStatus()), e.get(`${s}/.well-known/mcp-config`, async (t, o) => (o.header("Content-Type", "application/schema+json; charset=utf-8"), this.configSchema ?? {
573
780
  $schema: "https://json-schema.org/draft/2020-12/schema",
574
781
  title: "MCP Session Configuration",
575
782
  description: "Schema for the /mcp endpoint configuration",
@@ -579,32 +786,32 @@ class J {
579
786
  "x-mcp-version": "1.0",
580
787
  "x-query-style": "dot+bracket"
581
788
  })), e.post(
582
- `${t}/mcp`,
583
- async (s, o) => {
584
- const i = s.headers["mcp-client-id"]?.trim(), a = i && i.length > 0 ? i : `anon-${p()}`, l = !a.startsWith("anon-");
585
- let n = l ? this.clientCache.get(a) : null;
586
- if (!n) {
587
- const f = this.createBundle(), g = f.sessions;
588
- n = {
589
- server: f.server,
590
- orchestrator: f.orchestrator,
591
- sessions: g instanceof Map ? g : /* @__PURE__ */ new Map()
592
- }, l && this.clientCache.set(a, n);
789
+ `${s}/mcp`,
790
+ async (t, o) => {
791
+ const i = t.headers["mcp-client-id"]?.trim(), n = i && i.length > 0 ? i : `anon-${y()}`, l = !n.startsWith("anon-");
792
+ let a = l ? this.clientCache.get(n) : null;
793
+ if (!a) {
794
+ const u = this.createBundle(), p = u.sessions;
795
+ a = {
796
+ server: u.server,
797
+ orchestrator: u.orchestrator,
798
+ sessions: p instanceof Map ? p : /* @__PURE__ */ new Map()
799
+ }, l && this.clientCache.set(n, a);
593
800
  }
594
- const c = s.headers["mcp-session-id"];
595
- let h;
596
- if (c && n.sessions.get(c))
597
- h = n.sessions.get(c);
598
- else if (!c && x(s.body)) {
599
- const f = p();
600
- h = new C({
601
- sessionIdGenerator: () => f,
602
- onsessioninitialized: (g) => {
603
- n.sessions.set(g, h);
801
+ const c = t.headers["mcp-session-id"];
802
+ let d;
803
+ if (c && a.sessions.get(c))
804
+ d = a.sessions.get(c);
805
+ else if (!c && D(t.body)) {
806
+ const u = y();
807
+ d = new k({
808
+ sessionIdGenerator: () => u,
809
+ onsessioninitialized: (p) => {
810
+ a.sessions.set(p, d);
604
811
  }
605
812
  });
606
813
  try {
607
- await n.server.connect(h);
814
+ await a.server.connect(d);
608
815
  } catch {
609
816
  return o.code(500), {
610
817
  jsonrpc: "2.0",
@@ -612,8 +819,8 @@ class J {
612
819
  id: null
613
820
  };
614
821
  }
615
- h.onclose = () => {
616
- h?.sessionId && n.sessions.delete(h.sessionId);
822
+ d.onclose = () => {
823
+ d?.sessionId && a.sessions.delete(d.sessionId);
617
824
  };
618
825
  } else
619
826
  return o.code(400), {
@@ -621,29 +828,29 @@ class J {
621
828
  error: { code: -32e3, message: "Session not found or expired" },
622
829
  id: null
623
830
  };
624
- return await h.handleRequest(
625
- s.raw,
831
+ return await d.handleRequest(
832
+ t.raw,
626
833
  o.raw,
627
- s.body
834
+ t.body
628
835
  ), o;
629
836
  }
630
- ), e.get(`${t}/mcp`, async (s, o) => {
631
- const i = s.headers["mcp-client-id"]?.trim(), a = i && i.length > 0 ? i : "";
632
- if (!a)
837
+ ), e.get(`${s}/mcp`, async (t, o) => {
838
+ const i = t.headers["mcp-client-id"]?.trim(), n = i && i.length > 0 ? i : "";
839
+ if (!n)
633
840
  return o.code(400), "Missing mcp-client-id";
634
- const l = this.clientCache.get(a);
841
+ const l = this.clientCache.get(n);
635
842
  if (!l)
636
843
  return o.code(400), "Invalid or expired client";
637
- const n = s.headers["mcp-session-id"];
638
- if (!n)
844
+ const a = t.headers["mcp-session-id"];
845
+ if (!a)
639
846
  return o.code(400), "Missing mcp-session-id";
640
- const c = l.sessions.get(n);
641
- return c ? (await c.handleRequest(s.raw, o.raw), o) : (o.code(400), "Invalid or expired session ID");
847
+ const c = l.sessions.get(a);
848
+ return c ? (await c.handleRequest(t.raw, o.raw), o) : (o.code(400), "Invalid or expired session ID");
642
849
  }), e.delete(
643
- `${t}/mcp`,
644
- async (s, o) => {
645
- const i = s.headers["mcp-client-id"]?.trim(), a = i && i.length > 0 ? i : "", l = s.headers["mcp-session-id"];
646
- if (!a || !l)
850
+ `${s}/mcp`,
851
+ async (t, o) => {
852
+ const i = t.headers["mcp-client-id"]?.trim(), n = i && i.length > 0 ? i : "", l = t.headers["mcp-session-id"];
853
+ if (!n || !l)
647
854
  return o.code(400), {
648
855
  jsonrpc: "2.0",
649
856
  error: {
@@ -652,8 +859,8 @@ class J {
652
859
  },
653
860
  id: null
654
861
  };
655
- const n = this.clientCache.get(a), c = n?.sessions.get(l);
656
- if (!n || !c)
862
+ const a = this.clientCache.get(n), c = a?.sessions.get(l);
863
+ if (!a || !c)
657
864
  return o.code(404), {
658
865
  jsonrpc: "2.0",
659
866
  error: { code: -32e3, message: "Session not found or expired" },
@@ -666,62 +873,86 @@ class J {
666
873
  } catch {
667
874
  }
668
875
  } finally {
669
- c?.sessionId ? n.sessions.delete(c.sessionId) : n.sessions.delete(l);
876
+ c?.sessionId ? a.sessions.delete(c.sessionId) : a.sessions.delete(l);
670
877
  }
671
878
  return o.code(204).send(), o;
672
879
  }
673
- ), this.options.app || await e.listen({ host: this.options.host, port: this.options.port }), this.app = e;
880
+ ), this.options.customEndpoints && this.options.customEndpoints.length > 0 && V(e, s, this.options.customEndpoints), this.options.app || await e.listen({ host: this.options.host, port: this.options.port }), this.app = e;
674
881
  }
882
+ /**
883
+ * Stops the Fastify server and cleans up all resources.
884
+ * Closes all client sessions and clears the cache.
885
+ */
675
886
  async stop() {
676
- this.app && (this.options.app || await this.app.close(), this.app = null);
887
+ this.app && (this.clientCache.stop(!0), this.options.app || await this.app.close(), this.app = null);
888
+ }
889
+ /**
890
+ * Cleans up resources associated with a client bundle.
891
+ * Closes all sessions within the bundle.
892
+ * @param bundle - The client bundle to clean up
893
+ * @private
894
+ */
895
+ cleanupBundle(e) {
896
+ for (const [s, t] of e.sessions.entries())
897
+ try {
898
+ typeof t.close == "function" && t.close().catch((o) => {
899
+ console.warn(`Error closing session ${s}:`, o);
900
+ });
901
+ } catch (o) {
902
+ console.warn(`Error closing session ${s}:`, o);
903
+ }
904
+ e.sessions.clear();
677
905
  }
678
906
  }
679
- async function de(r) {
907
+ async function Se(r) {
680
908
  const e = r.startup?.mode ?? "DYNAMIC";
681
909
  if (typeof r.createServer != "function")
682
910
  throw new Error("createMcpServer: `createServer` (factory) is required");
683
- const t = r.createServer(), s = (n) => typeof n?.server?.notification == "function", o = (n) => typeof n?.notifyToolsListChanged == "function", i = async (n) => {
911
+ const s = r.createServer(), t = (a) => typeof a?.server?.notification == "function", o = (a) => typeof a?.notifyToolsListChanged == "function", i = async (a) => {
684
912
  try {
685
- if (s(n)) {
686
- await n.server.notification({
913
+ if (t(a)) {
914
+ await a.server.notification({
687
915
  method: "notifications/tools/list_changed"
688
916
  });
689
917
  return;
690
918
  }
691
- o(n) && await n.notifyToolsListChanged();
692
- } catch {
919
+ o(a) && await a.notifyToolsListChanged();
920
+ } catch (c) {
921
+ if ((c instanceof Error ? c.message : String(c)) === "Not connected")
922
+ return;
923
+ console.warn("Failed to send tools list changed notification:", c);
693
924
  }
694
- }, a = new y({
695
- server: t,
925
+ }, n = new w({
926
+ server: s,
696
927
  catalog: r.catalog,
697
928
  moduleLoaders: r.moduleLoaders,
698
929
  exposurePolicy: r.exposurePolicy,
699
930
  context: r.context,
700
- notifyToolsListChanged: async () => i(t),
931
+ notifyToolsListChanged: async () => i(s),
701
932
  startup: r.startup,
702
933
  registerMetaTools: r.registerMetaTools !== void 0 ? r.registerMetaTools : e === "DYNAMIC"
703
- }), l = new J(
704
- a.getManager(),
934
+ }), l = new ie(
935
+ n.getManager(),
705
936
  () => {
706
937
  if (e === "STATIC")
707
- return { server: t, orchestrator: a };
708
- const n = r.createServer(), c = new y({
709
- server: n,
938
+ return { server: s, orchestrator: n };
939
+ const a = r.createServer(), c = new w({
940
+ server: a,
710
941
  catalog: r.catalog,
711
942
  moduleLoaders: r.moduleLoaders,
712
943
  exposurePolicy: r.exposurePolicy,
713
944
  context: r.context,
714
- notifyToolsListChanged: async () => i(n),
945
+ notifyToolsListChanged: async () => i(a),
715
946
  startup: r.startup,
716
947
  registerMetaTools: r.registerMetaTools !== void 0 ? r.registerMetaTools : e === "DYNAMIC"
717
948
  });
718
- return { server: n, orchestrator: c };
949
+ return { server: a, orchestrator: c };
719
950
  },
720
951
  r.http,
721
952
  r.configSchema
722
953
  );
723
954
  return {
724
- server: t,
955
+ server: s,
725
956
  start: async () => {
726
957
  await l.start();
727
958
  },
@@ -730,16 +961,16 @@ async function de(r) {
730
961
  }
731
962
  };
732
963
  }
733
- function q(r) {
734
- G(r), K(r), Q(r), X(r);
964
+ function ne(r) {
965
+ ae(r), le(r), ce(r), de(r);
735
966
  }
736
- function G(r) {
967
+ function ae(r) {
737
968
  if (!r || typeof r != "object")
738
969
  throw new Error(
739
970
  "Permission configuration is required for createPermissionBasedMcpServer"
740
971
  );
741
972
  }
742
- function K(r) {
973
+ function le(r) {
743
974
  if (!r.source)
744
975
  throw new Error('Permission source must be either "headers" or "config"');
745
976
  if (r.source !== "headers" && r.source !== "config")
@@ -747,19 +978,19 @@ function K(r) {
747
978
  `Invalid permission source: "${r.source}". Must be either "headers" or "config"`
748
979
  );
749
980
  }
750
- function Q(r) {
981
+ function ce(r) {
751
982
  if (r.source === "config" && !r.staticMap && !r.resolver)
752
983
  throw new Error(
753
984
  "Config-based permissions require at least one of: staticMap or resolver function"
754
985
  );
755
986
  }
756
- function X(r) {
987
+ function de(r) {
757
988
  if (r.staticMap !== void 0) {
758
989
  if (typeof r.staticMap != "object" || r.staticMap === null)
759
990
  throw new Error(
760
991
  "staticMap must be an object mapping client IDs to toolset arrays"
761
992
  );
762
- Z(r.staticMap);
993
+ he(r.staticMap);
763
994
  }
764
995
  if (r.resolver !== void 0 && typeof r.resolver != "function")
765
996
  throw new Error(
@@ -770,48 +1001,61 @@ function X(r) {
770
1001
  if (r.headerName !== void 0 && (typeof r.headerName != "string" || r.headerName.length === 0))
771
1002
  throw new Error("headerName must be a non-empty string");
772
1003
  }
773
- function Z(r) {
774
- for (const [e, t] of Object.entries(r))
775
- if (!Array.isArray(t))
1004
+ function he(r) {
1005
+ for (const [e, s] of Object.entries(r))
1006
+ if (!Array.isArray(s))
776
1007
  throw new Error(
777
1008
  `staticMap value for client "${e}" must be an array of toolset names`
778
1009
  );
779
1010
  }
780
- var m, $, E, L, N;
781
- class ee {
1011
+ var g, F, _, B, H, W;
1012
+ class ue {
782
1013
  /**
783
1014
  * Creates a new PermissionResolver instance.
784
1015
  * @param config - The permission configuration defining how permissions are resolved
785
1016
  */
786
1017
  constructor(e) {
787
- v(this, m);
788
- this.config = e, this.cache = /* @__PURE__ */ new Map();
1018
+ T(this, g);
1019
+ this.config = e, this.cache = /* @__PURE__ */ new Map(), this.normalizedHeaderName = (e.headerName || "mcp-toolset-permissions").toLowerCase();
789
1020
  }
790
1021
  /**
791
1022
  * Resolves permissions for a client based on the configured source.
792
1023
  * Results are cached to improve performance for subsequent requests from the same client.
793
1024
  * Handles all errors gracefully by returning empty permissions on failure.
1025
+ *
1026
+ * Note on caching: For header-based permissions, permissions are cached by clientId.
1027
+ * This means subsequent requests from the same client will use cached permissions,
1028
+ * even if headers change. Use invalidateCache(clientId) to force re-resolution.
1029
+ *
794
1030
  * @param clientId - The unique identifier for the client
795
1031
  * @param headers - Optional request headers (required for header-based permissions)
796
1032
  * @returns Array of toolset names the client is allowed to access
797
1033
  */
798
- resolvePermissions(e, t) {
1034
+ resolvePermissions(e, s) {
799
1035
  if (this.cache.has(e))
800
1036
  return this.cache.get(e);
801
- let s;
1037
+ let t;
802
1038
  try {
803
- this.config.source === "headers" ? s = u(this, m, $).call(this, t) : s = u(this, m, E).call(this, e), Array.isArray(s) || (console.warn(
1039
+ this.config.source === "headers" ? t = f(this, g, F).call(this, s) : t = f(this, g, B).call(this, e), Array.isArray(t) || (console.warn(
804
1040
  `Permission resolution returned non-array for client ${e}, using empty permissions`
805
- ), s = []), s = s.filter(
1041
+ ), t = []), t = t.filter(
806
1042
  (o) => typeof o == "string" && o.trim().length > 0
807
1043
  );
808
1044
  } catch (o) {
809
1045
  console.error(
810
1046
  `Unexpected error resolving permissions for client ${e}:`,
811
1047
  o
812
- ), s = [];
1048
+ ), t = [];
813
1049
  }
814
- return this.cache.set(e, s), s;
1050
+ return this.cache.set(e, t), t;
1051
+ }
1052
+ /**
1053
+ * Invalidates cached permissions for a specific client.
1054
+ * Call this when you know a client's permissions have changed.
1055
+ * @param clientId - The client ID to invalidate
1056
+ */
1057
+ invalidateCache(e) {
1058
+ this.cache.delete(e);
815
1059
  }
816
1060
  /**
817
1061
  * Clears the permission cache.
@@ -821,26 +1065,43 @@ class ee {
821
1065
  this.cache.clear();
822
1066
  }
823
1067
  }
824
- m = new WeakSet(), /**
1068
+ g = new WeakSet(), /**
825
1069
  * Parses permissions from request headers.
826
1070
  * Extracts comma-separated toolset names from the configured header.
827
1071
  * Handles malformed headers gracefully by returning empty permissions.
1072
+ * Uses case-insensitive header lookup per RFC 7230.
828
1073
  * @param headers - Request headers containing permission data
829
1074
  * @returns Array of toolset names from headers, or empty array if header is missing/malformed
830
1075
  * @private
831
1076
  */
832
- $ = function(e) {
833
- const t = this.config.headerName || "mcp-toolset-permissions", s = e?.[t];
1077
+ F = function(e) {
1078
+ if (!e)
1079
+ return [];
1080
+ const s = f(this, g, _).call(this, e, this.normalizedHeaderName);
834
1081
  if (!s)
835
1082
  return [];
836
1083
  try {
837
- return s.split(",").map((o) => o.trim()).filter((o) => o.length > 0);
838
- } catch (o) {
1084
+ return s.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
1085
+ } catch (t) {
839
1086
  return console.warn(
840
- `Failed to parse permission header '${t}':`,
841
- o
1087
+ `Failed to parse permission header '${this.normalizedHeaderName}':`,
1088
+ t
842
1089
  ), [];
843
1090
  }
1091
+ }, /**
1092
+ * Finds a header value using case-insensitive key matching.
1093
+ * HTTP headers are case-insensitive per RFC 7230.
1094
+ * @param headers - The headers object to search
1095
+ * @param normalizedKey - The lowercase key to search for
1096
+ * @returns The header value if found, undefined otherwise
1097
+ * @private
1098
+ */
1099
+ _ = function(e, s) {
1100
+ if (e[s] !== void 0)
1101
+ return e[s];
1102
+ for (const [t, o] of Object.entries(e))
1103
+ if (t.toLowerCase() === s)
1104
+ return o;
844
1105
  }, /**
845
1106
  * Resolves permissions from server-side configuration.
846
1107
  * Tries resolver function first (if provided), then falls back to static map,
@@ -849,16 +1110,16 @@ $ = function(e) {
849
1110
  * @returns Array of toolset names from configuration
850
1111
  * @private
851
1112
  */
852
- E = function(e) {
1113
+ B = function(e) {
853
1114
  if (this.config.resolver) {
854
- const t = u(this, m, L).call(this, e);
855
- if (t !== null)
856
- return t;
1115
+ const s = f(this, g, H).call(this, e);
1116
+ if (s !== null)
1117
+ return s;
857
1118
  }
858
1119
  if (this.config.staticMap) {
859
- const t = u(this, m, N).call(this, e);
860
- if (t !== null)
861
- return t;
1120
+ const s = f(this, g, W).call(this, e);
1121
+ if (s !== null)
1122
+ return s;
862
1123
  }
863
1124
  return this.config.defaultPermissions || [];
864
1125
  }, /**
@@ -868,16 +1129,16 @@ E = function(e) {
868
1129
  * @returns Array of toolset names if successful, null if resolver fails or returns invalid data
869
1130
  * @private
870
1131
  */
871
- L = function(e) {
1132
+ H = function(e) {
872
1133
  try {
873
- const t = this.config.resolver(e);
874
- return Array.isArray(t) ? t : (console.warn(
1134
+ const s = this.config.resolver(e);
1135
+ return Array.isArray(s) ? s : (console.warn(
875
1136
  `Permission resolver returned non-array for client ${e}, using fallback`
876
1137
  ), null);
877
- } catch (t) {
878
- const s = t instanceof Error ? t.message : String(t);
1138
+ } catch (s) {
1139
+ const t = s instanceof Error ? s.message : String(s);
879
1140
  return console.warn(
880
- `Permission resolver declined client ${e} (${s}), trying fallback`
1141
+ `Permission resolver declined client ${e} (${t}), trying fallback`
881
1142
  ), null;
882
1143
  }
883
1144
  }, /**
@@ -887,25 +1148,37 @@ L = function(e) {
887
1148
  * @returns Array of toolset names if found, null if client not in map
888
1149
  * @private
889
1150
  */
890
- N = function(e) {
891
- const t = this.config.staticMap[e];
892
- return t !== void 0 ? Array.isArray(t) ? t : [] : null;
1151
+ W = function(e) {
1152
+ const s = this.config.staticMap[e];
1153
+ return s !== void 0 ? Array.isArray(s) ? s : [] : null;
893
1154
  };
894
- function se(r, e) {
895
- return async (t) => {
896
- const s = e.resolvePermissions(
897
- t.clientId,
898
- t.headers
899
- ), o = r(s), i = o.orchestrator.getManager();
900
- return s.length > 0 && await i.enableToolsets(s), {
1155
+ function fe(r, e) {
1156
+ return async (s) => {
1157
+ const t = e.resolvePermissions(
1158
+ s.clientId,
1159
+ s.headers
1160
+ ), o = r(t), i = o.orchestrator.getManager(), n = [], l = [];
1161
+ if (t.length > 0) {
1162
+ const a = await i.enableToolsets(t);
1163
+ for (const c of a.results)
1164
+ c.success ? n.push(c.name) : (l.push(c.name), console.warn(
1165
+ `Failed to enable toolset '${c.name}' for client '${s.clientId}': ${c.message}`
1166
+ ));
1167
+ if (n.length === 0 && l.length > 0)
1168
+ throw new Error(
1169
+ `All requested toolsets failed to enable for client '${s.clientId}'. Requested: [${t.join(", ")}]. Check that toolset names in permissions match the catalog.`
1170
+ );
1171
+ }
1172
+ return {
901
1173
  server: o.server,
902
1174
  orchestrator: o.orchestrator,
903
- allowedToolsets: s
1175
+ allowedToolsets: n,
1176
+ failedToolsets: l
904
1177
  };
905
1178
  };
906
1179
  }
907
- var d, D, j, z, O, R, k, F, V, _;
908
- class te {
1180
+ var h, Y, U, q, J, G, K, Q, X, E, Z;
1181
+ class me {
909
1182
  /**
910
1183
  * Creates a new PermissionAwareFastifyTransport instance.
911
1184
  * @param defaultManager - Default tool manager for status endpoints
@@ -913,15 +1186,20 @@ class te {
913
1186
  * @param options - Transport configuration options
914
1187
  * @param configSchema - Optional JSON schema for configuration discovery
915
1188
  */
916
- constructor(e, t, s = {}, o) {
917
- v(this, d);
918
- this.app = null, this.clientCache = new P(), this.defaultManager = e, this.createPermissionAwareBundle = t, this.options = {
919
- host: s.host ?? "0.0.0.0",
920
- port: s.port ?? 3e3,
921
- basePath: s.basePath ?? "/",
922
- cors: s.cors ?? !0,
923
- logger: s.logger ?? !1,
924
- app: s.app
1189
+ constructor(e, s, t = {}, o) {
1190
+ T(this, h);
1191
+ this.app = null, this.clientCache = new O({
1192
+ onEvict: (i, n) => {
1193
+ f(this, h, Y).call(this, n);
1194
+ }
1195
+ }), this.defaultManager = e, this.createPermissionAwareBundle = s, this.options = {
1196
+ host: t.host ?? "0.0.0.0",
1197
+ port: t.port ?? 3e3,
1198
+ basePath: t.basePath ?? "/",
1199
+ cors: t.cors ?? !0,
1200
+ logger: t.logger ?? !1,
1201
+ app: t.app,
1202
+ customEndpoints: t.customEndpoints
925
1203
  }, this.configSchema = o;
926
1204
  }
927
1205
  /**
@@ -930,25 +1208,60 @@ class te {
930
1208
  */
931
1209
  async start() {
932
1210
  if (this.app) return;
933
- const e = this.options.app ?? S({ logger: this.options.logger });
934
- this.options.cors && await e.register(M, { origin: !0 });
935
- const t = u(this, d, D).call(this, this.options.basePath);
936
- u(this, d, j).call(this, e, t), u(this, d, z).call(this, e, t), u(this, d, O).call(this, e, t), u(this, d, R).call(this, e, t), u(this, d, k).call(this, e, t), u(this, d, F).call(this, e, t), this.options.app || await e.listen({ host: this.options.host, port: this.options.port }), this.app = e;
1211
+ const e = this.options.app ?? N({ logger: this.options.logger });
1212
+ this.options.cors && await e.register(j, { origin: !0 });
1213
+ const s = f(this, h, U).call(this, this.options.basePath);
1214
+ f(this, h, q).call(this, e, s), f(this, h, J).call(this, e, s), f(this, h, G).call(this, e, s), f(this, h, K).call(this, e, s), f(this, h, Q).call(this, e, s), f(this, h, X).call(this, e, s), this.options.customEndpoints && this.options.customEndpoints.length > 0 && V(e, s, this.options.customEndpoints, {
1215
+ contextExtractor: async (t) => {
1216
+ const o = f(this, h, E).call(this, t);
1217
+ try {
1218
+ const i = await this.createPermissionAwareBundle(o);
1219
+ return {
1220
+ allowedToolsets: i.allowedToolsets,
1221
+ failedToolsets: i.failedToolsets
1222
+ };
1223
+ } catch (i) {
1224
+ return console.warn(
1225
+ `Permission resolution failed for custom endpoint: ${i}`
1226
+ ), {
1227
+ allowedToolsets: [],
1228
+ failedToolsets: []
1229
+ };
1230
+ }
1231
+ }
1232
+ }), this.options.app || await e.listen({ host: this.options.host, port: this.options.port }), this.app = e;
937
1233
  }
938
1234
  /**
939
- * Stops the Fastify server and cleans up resources.
1235
+ * Stops the Fastify server and cleans up all resources.
1236
+ * Closes all client sessions and clears the cache.
940
1237
  */
941
1238
  async stop() {
942
- this.app && (this.options.app || await this.app.close(), this.app = null);
1239
+ this.app && (this.clientCache.stop(!0), this.options.app || await this.app.close(), this.app = null);
943
1240
  }
944
1241
  }
945
- d = new WeakSet(), /**
1242
+ h = new WeakSet(), /**
1243
+ * Cleans up resources associated with a client bundle.
1244
+ * Closes all sessions within the bundle.
1245
+ * @param bundle - The client bundle to clean up
1246
+ * @private
1247
+ */
1248
+ Y = function(e) {
1249
+ for (const [s, t] of e.sessions.entries())
1250
+ try {
1251
+ typeof t.close == "function" && t.close().catch((o) => {
1252
+ console.warn(`Error closing session ${s}:`, o);
1253
+ });
1254
+ } catch (o) {
1255
+ console.warn(`Error closing session ${s}:`, o);
1256
+ }
1257
+ e.sessions.clear();
1258
+ }, /**
946
1259
  * Normalizes the base path by removing trailing slashes.
947
1260
  * @param basePath - The base path to normalize
948
1261
  * @returns Normalized base path without trailing slash
949
1262
  * @private
950
1263
  */
951
- D = function(e) {
1264
+ U = function(e) {
952
1265
  return e.endsWith("/") ? e.slice(0, -1) : e;
953
1266
  }, /**
954
1267
  * Registers the health check endpoint.
@@ -956,24 +1269,24 @@ D = function(e) {
956
1269
  * @param base - Base path for routes
957
1270
  * @private
958
1271
  */
959
- j = function(e, t) {
960
- e.get(`${t}/healthz`, async () => ({ ok: !0 }));
1272
+ q = function(e, s) {
1273
+ e.get(`${s}/healthz`, async () => ({ ok: !0 }));
961
1274
  }, /**
962
1275
  * Registers the tools status endpoint.
963
1276
  * @param app - Fastify instance
964
1277
  * @param base - Base path for routes
965
1278
  * @private
966
1279
  */
967
- z = function(e, t) {
968
- e.get(`${t}/tools`, async () => this.defaultManager.getStatus());
1280
+ J = function(e, s) {
1281
+ e.get(`${s}/tools`, async () => this.defaultManager.getStatus());
969
1282
  }, /**
970
1283
  * Registers the MCP configuration discovery endpoint.
971
1284
  * @param app - Fastify instance
972
1285
  * @param base - Base path for routes
973
1286
  * @private
974
1287
  */
975
- O = function(e, t) {
976
- e.get(`${t}/.well-known/mcp-config`, async (s, o) => (o.header("Content-Type", "application/schema+json; charset=utf-8"), this.configSchema ?? {
1288
+ G = function(e, s) {
1289
+ e.get(`${s}/.well-known/mcp-config`, async (t, o) => (o.header("Content-Type", "application/schema+json; charset=utf-8"), this.configSchema ?? {
977
1290
  $schema: "https://json-schema.org/draft/2020-12/schema",
978
1291
  title: "MCP Session Configuration",
979
1292
  description: "Schema for the /mcp endpoint configuration",
@@ -990,37 +1303,42 @@ O = function(e, t) {
990
1303
  * @param base - Base path for routes
991
1304
  * @private
992
1305
  */
993
- R = function(e, t) {
1306
+ K = function(e, s) {
994
1307
  e.post(
995
- `${t}/mcp`,
996
- async (s, o) => {
997
- const i = u(this, d, V).call(this, s), a = !i.clientId.startsWith("anon-");
998
- let l = a ? this.clientCache.get(i.clientId) : null;
1308
+ `${s}/mcp`,
1309
+ async (t, o) => {
1310
+ const i = f(this, h, E).call(this, t), n = !i.clientId.startsWith("anon-");
1311
+ let l = n ? this.clientCache.get(i.clientId) : null;
999
1312
  if (!l)
1000
1313
  try {
1001
- const h = await this.createPermissionAwareBundle(i), f = h.sessions;
1314
+ const d = await this.createPermissionAwareBundle(i);
1315
+ d.failedToolsets.length > 0 && console.warn(
1316
+ `Client ${i.clientId} had ${d.failedToolsets.length} toolsets fail to enable: [${d.failedToolsets.join(", ")}]. Successfully enabled: [${d.allowedToolsets.join(", ")}]`
1317
+ );
1318
+ const u = d.sessions;
1002
1319
  l = {
1003
- server: h.server,
1004
- orchestrator: h.orchestrator,
1005
- allowedToolsets: h.allowedToolsets,
1006
- sessions: f instanceof Map ? f : /* @__PURE__ */ new Map()
1007
- }, a && this.clientCache.set(i.clientId, l);
1008
- } catch (h) {
1320
+ server: d.server,
1321
+ orchestrator: d.orchestrator,
1322
+ allowedToolsets: d.allowedToolsets,
1323
+ failedToolsets: d.failedToolsets,
1324
+ sessions: u instanceof Map ? u : /* @__PURE__ */ new Map()
1325
+ }, n && this.clientCache.set(i.clientId, l);
1326
+ } catch (d) {
1009
1327
  return console.error(
1010
1328
  `Failed to create permission-aware bundle for client ${i.clientId}:`,
1011
- h
1012
- ), o.code(403), u(this, d, _).call(this, "Access denied");
1329
+ d
1330
+ ), o.code(403), f(this, h, Z).call(this, "Access denied");
1013
1331
  }
1014
- const n = s.headers["mcp-session-id"];
1332
+ const a = t.headers["mcp-session-id"];
1015
1333
  let c;
1016
- if (n && l.sessions.get(n))
1017
- c = l.sessions.get(n);
1018
- else if (!n && x(s.body)) {
1019
- const h = p();
1020
- c = new C({
1021
- sessionIdGenerator: () => h,
1022
- onsessioninitialized: (f) => {
1023
- l.sessions.set(f, c);
1334
+ if (a && l.sessions.get(a))
1335
+ c = l.sessions.get(a);
1336
+ else if (!a && D(t.body)) {
1337
+ const d = y();
1338
+ c = new k({
1339
+ sessionIdGenerator: () => d,
1340
+ onsessioninitialized: (u) => {
1341
+ l.sessions.set(u, c);
1024
1342
  }
1025
1343
  });
1026
1344
  try {
@@ -1042,9 +1360,9 @@ R = function(e, t) {
1042
1360
  id: null
1043
1361
  };
1044
1362
  return await c.handleRequest(
1045
- s.raw,
1363
+ t.raw,
1046
1364
  o.raw,
1047
- s.body
1365
+ t.body
1048
1366
  ), o;
1049
1367
  }
1050
1368
  );
@@ -1054,19 +1372,19 @@ R = function(e, t) {
1054
1372
  * @param base - Base path for routes
1055
1373
  * @private
1056
1374
  */
1057
- k = function(e, t) {
1058
- e.get(`${t}/mcp`, async (s, o) => {
1059
- const i = s.headers["mcp-client-id"]?.trim(), a = i && i.length > 0 ? i : "";
1060
- if (!a)
1375
+ Q = function(e, s) {
1376
+ e.get(`${s}/mcp`, async (t, o) => {
1377
+ const i = t.headers["mcp-client-id"]?.trim(), n = i && i.length > 0 ? i : "";
1378
+ if (!n)
1061
1379
  return o.code(400), "Missing mcp-client-id";
1062
- const l = this.clientCache.get(a);
1380
+ const l = this.clientCache.get(n);
1063
1381
  if (!l)
1064
1382
  return o.code(400), "Invalid or expired client";
1065
- const n = s.headers["mcp-session-id"];
1066
- if (!n)
1383
+ const a = t.headers["mcp-session-id"];
1384
+ if (!a)
1067
1385
  return o.code(400), "Missing mcp-session-id";
1068
- const c = l.sessions.get(n);
1069
- return c ? (await c.handleRequest(s.raw, o.raw), o) : (o.code(400), "Invalid or expired session ID");
1386
+ const c = l.sessions.get(a);
1387
+ return c ? (await c.handleRequest(t.raw, o.raw), o) : (o.code(400), "Invalid or expired session ID");
1070
1388
  });
1071
1389
  }, /**
1072
1390
  * Registers the DELETE /mcp endpoint for session termination.
@@ -1074,12 +1392,12 @@ k = function(e, t) {
1074
1392
  * @param base - Base path for routes
1075
1393
  * @private
1076
1394
  */
1077
- F = function(e, t) {
1395
+ X = function(e, s) {
1078
1396
  e.delete(
1079
- `${t}/mcp`,
1080
- async (s, o) => {
1081
- const i = s.headers["mcp-client-id"]?.trim(), a = i && i.length > 0 ? i : "", l = s.headers["mcp-session-id"];
1082
- if (!a || !l)
1397
+ `${s}/mcp`,
1398
+ async (t, o) => {
1399
+ const i = t.headers["mcp-client-id"]?.trim(), n = i && i.length > 0 ? i : "", l = t.headers["mcp-session-id"];
1400
+ if (!n || !l)
1083
1401
  return o.code(400), {
1084
1402
  jsonrpc: "2.0",
1085
1403
  error: {
@@ -1088,8 +1406,8 @@ F = function(e, t) {
1088
1406
  },
1089
1407
  id: null
1090
1408
  };
1091
- const n = this.clientCache.get(a), c = n?.sessions.get(l);
1092
- if (!n || !c)
1409
+ const a = this.clientCache.get(n), c = a?.sessions.get(l);
1410
+ if (!a || !c)
1093
1411
  return o.code(404), {
1094
1412
  jsonrpc: "2.0",
1095
1413
  error: { code: -32e3, message: "Session not found or expired" },
@@ -1102,7 +1420,7 @@ F = function(e, t) {
1102
1420
  } catch {
1103
1421
  }
1104
1422
  } finally {
1105
- c?.sessionId ? n.sessions.delete(c.sessionId) : n.sessions.delete(l);
1423
+ c?.sessionId ? a.sessions.delete(c.sessionId) : a.sessions.delete(l);
1106
1424
  }
1107
1425
  return o.code(204).send(), o;
1108
1426
  }
@@ -1114,11 +1432,11 @@ F = function(e, t) {
1114
1432
  * @returns Client request context with ID and headers
1115
1433
  * @private
1116
1434
  */
1117
- V = function(e) {
1118
- const t = e.headers["mcp-client-id"]?.trim(), s = t && t.length > 0 ? t : `anon-${p()}`, o = {};
1119
- for (const [i, a] of Object.entries(e.headers))
1120
- typeof a == "string" && (o[i] = a);
1121
- return { clientId: s, headers: o };
1435
+ E = function(e) {
1436
+ const s = e.headers["mcp-client-id"]?.trim(), t = s && s.length > 0 ? s : `anon-${y()}`, o = {};
1437
+ for (const [i, n] of Object.entries(e.headers))
1438
+ typeof n == "string" && (o[i] = n);
1439
+ return { clientId: t, headers: o };
1122
1440
  }, /**
1123
1441
  * Creates a safe error response that doesn't expose unauthorized toolset information.
1124
1442
  * Used for permission-related errors to prevent information leakage.
@@ -1127,22 +1445,37 @@ V = function(e) {
1127
1445
  * @returns JSON-RPC error response object
1128
1446
  * @private
1129
1447
  */
1130
- _ = function(e = "Access denied", t = -32e3) {
1448
+ Z = function(e = "Access denied", s = -32e3) {
1131
1449
  return {
1132
1450
  jsonrpc: "2.0",
1133
1451
  error: {
1134
- code: t,
1452
+ code: s,
1135
1453
  message: e
1136
1454
  },
1137
1455
  id: null
1138
1456
  };
1139
1457
  };
1140
- async function he(r) {
1458
+ function ge(r) {
1459
+ if (!r) return;
1460
+ const e = {
1461
+ namespaceToolsWithSetKey: r.namespaceToolsWithSetKey
1462
+ };
1463
+ return r.allowlist !== void 0 && console.warn(
1464
+ "Permission-based servers: exposurePolicy.allowlist is ignored. Allowed toolsets are determined by client permissions."
1465
+ ), r.denylist !== void 0 && console.warn(
1466
+ "Permission-based servers: exposurePolicy.denylist is ignored. Use permission configuration to control toolset access."
1467
+ ), r.maxActiveToolsets !== void 0 && console.warn(
1468
+ "Permission-based servers: exposurePolicy.maxActiveToolsets is ignored. Toolset count is determined by client permissions."
1469
+ ), r.onLimitExceeded !== void 0 && console.warn(
1470
+ "Permission-based servers: exposurePolicy.onLimitExceeded is ignored. No toolset limits are enforced."
1471
+ ), e;
1472
+ }
1473
+ async function Ee(r) {
1141
1474
  if (!r.permissions)
1142
1475
  throw new Error(
1143
1476
  "Permission configuration is required for createPermissionBasedMcpServer. Please provide a 'permissions' field in the options."
1144
1477
  );
1145
- if (q(r.permissions), r.startup)
1478
+ if (ne(r.permissions), r.startup)
1146
1479
  throw new Error(
1147
1480
  "Permission-based servers determine toolsets from client permissions. The 'startup' option is not allowed. Remove it from your configuration."
1148
1481
  );
@@ -1150,23 +1483,25 @@ async function he(r) {
1150
1483
  throw new Error(
1151
1484
  "createPermissionBasedMcpServer: `createServer` (factory) is required"
1152
1485
  );
1153
- const e = new ee(r.permissions), t = r.createServer(), s = new y({
1486
+ const e = ge(
1487
+ r.exposurePolicy
1488
+ ), s = new ue(r.permissions), t = r.createServer(), o = new w({
1154
1489
  server: t,
1155
1490
  catalog: r.catalog,
1156
1491
  moduleLoaders: r.moduleLoaders,
1157
- exposurePolicy: r.exposurePolicy,
1492
+ exposurePolicy: e,
1158
1493
  context: r.context,
1159
1494
  notifyToolsListChanged: void 0,
1160
1495
  // No notifications in STATIC mode
1161
1496
  startup: { mode: "STATIC", toolsets: [] },
1162
1497
  registerMetaTools: !1
1163
- }), o = se(
1164
- (a) => {
1165
- const l = r.createServer(), n = new y({
1166
- server: l,
1498
+ }), i = fe(
1499
+ (l) => {
1500
+ const a = r.createServer(), c = new w({
1501
+ server: a,
1167
1502
  catalog: r.catalog,
1168
1503
  moduleLoaders: r.moduleLoaders,
1169
- exposurePolicy: r.exposurePolicy,
1504
+ exposurePolicy: e,
1170
1505
  context: r.context,
1171
1506
  notifyToolsListChanged: void 0,
1172
1507
  // No notifications in STATIC mode
@@ -1175,31 +1510,39 @@ async function he(r) {
1175
1510
  registerMetaTools: !1
1176
1511
  // No meta-tools - toolsets are fixed per client
1177
1512
  });
1178
- return { server: l, orchestrator: n };
1513
+ return { server: a, orchestrator: c };
1179
1514
  },
1180
- e
1181
- ), i = new te(
1182
- s.getManager(),
1183
- o,
1515
+ s
1516
+ ), n = new me(
1517
+ o.getManager(),
1518
+ i,
1184
1519
  r.http,
1185
1520
  r.configSchema
1186
1521
  );
1187
1522
  return {
1188
1523
  server: t,
1189
1524
  start: async () => {
1190
- await i.start();
1525
+ await n.start();
1191
1526
  },
1192
1527
  close: async () => {
1193
1528
  try {
1194
- await i.stop();
1529
+ await n.stop();
1195
1530
  } finally {
1196
- e.clearCache();
1531
+ s.clearCache();
1197
1532
  }
1198
1533
  }
1199
1534
  };
1200
1535
  }
1536
+ function Ce(r) {
1537
+ return r;
1538
+ }
1539
+ function xe(r) {
1540
+ return r;
1541
+ }
1201
1542
  export {
1202
- de as createMcpServer,
1203
- he as createPermissionBasedMcpServer
1543
+ Se as createMcpServer,
1544
+ Ee as createPermissionBasedMcpServer,
1545
+ Ce as defineEndpoint,
1546
+ xe as definePermissionAwareEndpoint
1204
1547
  };
1205
1548
  //# sourceMappingURL=index.js.map