toolception 0.5.5 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/http/FastifyTransport.d.ts +40 -4
- package/dist/http/FastifyTransport.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +844 -577
- package/dist/index.js.map +1 -1
- package/dist/meta/registerMetaTools.d.ts +12 -1
- package/dist/meta/registerMetaTools.d.ts.map +1 -1
- package/dist/mode/ModuleResolver.d.ts.map +1 -1
- package/dist/server/createMcpServer.d.ts +20 -1
- package/dist/server/createMcpServer.d.ts.map +1 -1
- package/dist/server/createPermissionBasedMcpServer.d.ts.map +1 -1
- package/dist/session/SessionContextResolver.d.ts +115 -0
- package/dist/session/SessionContextResolver.d.ts.map +1 -0
- package/dist/session/validateSessionContextConfig.d.ts +10 -0
- package/dist/session/validateSessionContextConfig.d.ts.map +1 -0
- package/dist/types/index.d.ts +103 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
throw TypeError(
|
|
1
|
+
var re = Object.defineProperty;
|
|
2
|
+
var j = (o) => {
|
|
3
|
+
throw TypeError(o);
|
|
4
4
|
};
|
|
5
|
-
var
|
|
6
|
-
var d = (
|
|
7
|
-
var
|
|
8
|
-
var m = (
|
|
9
|
-
import { z as
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import { randomUUID as
|
|
13
|
-
import { StreamableHTTPServerTransport as
|
|
14
|
-
import { isInitializeRequest as
|
|
5
|
+
var ie = (o, e, t) => e in o ? re(o, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : o[e] = t;
|
|
6
|
+
var d = (o, e, t) => ie(o, typeof e != "symbol" ? e + "" : e, t), ne = (o, e, t) => e.has(o) || j("Cannot " + t);
|
|
7
|
+
var A = (o, e, t) => e.has(o) ? j("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(o) : e.set(o, t);
|
|
8
|
+
var m = (o, e, t) => (ne(o, e, "access private method"), t);
|
|
9
|
+
import { z as v } from "zod";
|
|
10
|
+
import N from "fastify";
|
|
11
|
+
import D from "@fastify/cors";
|
|
12
|
+
import { randomUUID as S, createHash as ae } from "node:crypto";
|
|
13
|
+
import { StreamableHTTPServerTransport as z } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
14
|
+
import { isInitializeRequest as _ } from "@modelcontextprotocol/sdk/types.js";
|
|
15
15
|
const R = {
|
|
16
16
|
dynamic: [
|
|
17
17
|
"dynamic-tool-discovery",
|
|
@@ -20,7 +20,7 @@ const R = {
|
|
|
20
20
|
],
|
|
21
21
|
toolsets: ["tool-sets", "toolSets", "FMP_TOOL_SETS"]
|
|
22
22
|
};
|
|
23
|
-
class
|
|
23
|
+
class le {
|
|
24
24
|
constructor(e = {}) {
|
|
25
25
|
d(this, "keys");
|
|
26
26
|
this.keys = {
|
|
@@ -28,46 +28,46 @@ class re {
|
|
|
28
28
|
toolsets: e.keys?.toolsets ?? R.toolsets
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
-
resolveMode(e,
|
|
32
|
-
return this.isDynamicEnabled(
|
|
31
|
+
resolveMode(e, t) {
|
|
32
|
+
return this.isDynamicEnabled(t) ? "DYNAMIC" : this.getToolsetsString(t) ? "STATIC" : this.isDynamicEnabled(e) ? "DYNAMIC" : this.getToolsetsString(e) ? "STATIC" : null;
|
|
33
33
|
}
|
|
34
|
-
parseCommaSeparatedToolSets(e,
|
|
34
|
+
parseCommaSeparatedToolSets(e, t) {
|
|
35
35
|
if (!e || typeof e != "string") return [];
|
|
36
|
-
const
|
|
37
|
-
for (const n of
|
|
38
|
-
|
|
36
|
+
const s = e.split(",").map((n) => n.trim()).filter((n) => n.length > 0), r = new Set(Object.keys(t)), i = [];
|
|
37
|
+
for (const n of s)
|
|
38
|
+
r.has(n) ? i.push(n) : console.warn(
|
|
39
39
|
`Invalid toolset '${n}' ignored. Available: ${Array.from(
|
|
40
|
-
|
|
40
|
+
r
|
|
41
41
|
).join(", ")}`
|
|
42
42
|
);
|
|
43
43
|
return i;
|
|
44
44
|
}
|
|
45
|
-
getModulesForToolSets(e,
|
|
46
|
-
const
|
|
47
|
-
for (const
|
|
48
|
-
const i =
|
|
49
|
-
i && (i.modules || []).forEach((n) =>
|
|
45
|
+
getModulesForToolSets(e, t) {
|
|
46
|
+
const s = /* @__PURE__ */ new Set();
|
|
47
|
+
for (const r of e) {
|
|
48
|
+
const i = t[r];
|
|
49
|
+
i && (i.modules || []).forEach((n) => s.add(n));
|
|
50
50
|
}
|
|
51
|
-
return Array.from(
|
|
51
|
+
return Array.from(s);
|
|
52
52
|
}
|
|
53
|
-
validateToolsetName(e,
|
|
53
|
+
validateToolsetName(e, t) {
|
|
54
54
|
if (!e || typeof e != "string")
|
|
55
55
|
return {
|
|
56
56
|
isValid: !1,
|
|
57
57
|
error: `Invalid toolset name provided. Must be a non-empty string. Available toolsets: ${Object.keys(
|
|
58
|
-
|
|
58
|
+
t
|
|
59
59
|
).join(", ")}`
|
|
60
60
|
};
|
|
61
|
-
const
|
|
62
|
-
return
|
|
61
|
+
const s = e.trim();
|
|
62
|
+
return s.length === 0 ? {
|
|
63
63
|
isValid: !1,
|
|
64
64
|
error: `Empty toolset name provided. Available toolsets: ${Object.keys(
|
|
65
|
-
|
|
65
|
+
t
|
|
66
66
|
).join(", ")}`
|
|
67
|
-
} : s
|
|
67
|
+
} : t[s] ? { isValid: !0, sanitized: s } : {
|
|
68
68
|
isValid: !1,
|
|
69
|
-
error: `Toolset '${
|
|
70
|
-
|
|
69
|
+
error: `Toolset '${s}' not found. Available toolsets: ${Object.keys(
|
|
70
|
+
t
|
|
71
71
|
).join(", ")}`
|
|
72
72
|
};
|
|
73
73
|
}
|
|
@@ -78,44 +78,50 @@ class re {
|
|
|
78
78
|
* @param catalog - The toolset catalog to validate against
|
|
79
79
|
* @returns Validation result with modules array if valid
|
|
80
80
|
*/
|
|
81
|
-
validateToolsetModules(e,
|
|
81
|
+
validateToolsetModules(e, t) {
|
|
82
82
|
try {
|
|
83
|
-
for (const
|
|
84
|
-
if (!
|
|
83
|
+
for (const r of e)
|
|
84
|
+
if (!t[r])
|
|
85
85
|
return {
|
|
86
86
|
isValid: !1,
|
|
87
|
-
error: `Toolset '${
|
|
87
|
+
error: `Toolset '${r}' not found in catalog`
|
|
88
88
|
};
|
|
89
|
-
return { isValid: !0, modules: this.getModulesForToolSets(e,
|
|
90
|
-
} catch (
|
|
89
|
+
return { isValid: !0, modules: this.getModulesForToolSets(e, t) };
|
|
90
|
+
} catch (s) {
|
|
91
91
|
return {
|
|
92
92
|
isValid: !1,
|
|
93
|
-
error: `Error resolving modules for ${e.join(", ")}: ${
|
|
93
|
+
error: `Error resolving modules for ${e.join(", ")}: ${s instanceof Error ? s.message : "Unknown error"}`
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
isDynamicEnabled(e) {
|
|
98
98
|
if (!e) return !1;
|
|
99
|
-
for (const
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
99
|
+
for (const t of this.keys.dynamic) {
|
|
100
|
+
const s = e[t];
|
|
101
|
+
if (s === !0 || typeof s == "string" && s.trim().toLowerCase() === "true")
|
|
102
102
|
return !0;
|
|
103
103
|
}
|
|
104
104
|
return !1;
|
|
105
105
|
}
|
|
106
106
|
getToolsetsString(e) {
|
|
107
107
|
if (e)
|
|
108
|
-
for (const
|
|
109
|
-
const
|
|
110
|
-
if (typeof
|
|
111
|
-
return
|
|
108
|
+
for (const t of this.keys.toolsets) {
|
|
109
|
+
const s = e[t];
|
|
110
|
+
if (typeof s == "string" && s.trim().length > 0)
|
|
111
|
+
return s;
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
-
|
|
115
|
+
const k = ["_meta"];
|
|
116
|
+
class ce {
|
|
116
117
|
constructor(e) {
|
|
117
118
|
d(this, "catalog");
|
|
118
119
|
d(this, "moduleLoaders");
|
|
120
|
+
for (const t of k)
|
|
121
|
+
if (t in e.catalog)
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Toolset key '${t}' is reserved for internal use and cannot be used in the catalog`
|
|
124
|
+
);
|
|
119
125
|
this.catalog = e.catalog, this.moduleLoaders = e.moduleLoaders ?? {};
|
|
120
126
|
}
|
|
121
127
|
getAvailableToolsets() {
|
|
@@ -132,50 +138,53 @@ class ie {
|
|
|
132
138
|
", "
|
|
133
139
|
)}`
|
|
134
140
|
};
|
|
135
|
-
const
|
|
136
|
-
return
|
|
141
|
+
const t = e.trim();
|
|
142
|
+
return t.length === 0 ? {
|
|
137
143
|
isValid: !1,
|
|
138
144
|
error: `Empty toolset name provided. Available toolsets: ${this.getAvailableToolsets().join(
|
|
139
145
|
", "
|
|
140
146
|
)}`
|
|
141
|
-
} :
|
|
147
|
+
} : k.includes(t) ? {
|
|
148
|
+
isValid: !1,
|
|
149
|
+
error: `Toolset key '${t}' is reserved for internal use`
|
|
150
|
+
} : this.catalog[t] ? { isValid: !0, sanitized: t } : {
|
|
142
151
|
isValid: !1,
|
|
143
|
-
error: `Toolset '${
|
|
152
|
+
error: `Toolset '${t}' not found. Available toolsets: ${this.getAvailableToolsets().join(
|
|
144
153
|
", "
|
|
145
154
|
)}`
|
|
146
155
|
};
|
|
147
156
|
}
|
|
148
|
-
async resolveToolsForToolsets(e,
|
|
149
|
-
const
|
|
150
|
-
for (const
|
|
151
|
-
const i = this.catalog[
|
|
152
|
-
if (i && (Array.isArray(i.tools) && i.tools.length > 0 &&
|
|
157
|
+
async resolveToolsForToolsets(e, t) {
|
|
158
|
+
const s = [];
|
|
159
|
+
for (const r of e) {
|
|
160
|
+
const i = this.catalog[r];
|
|
161
|
+
if (i && (Array.isArray(i.tools) && i.tools.length > 0 && s.push(...i.tools), Array.isArray(i.modules) && i.modules.length > 0))
|
|
153
162
|
for (const n of i.modules) {
|
|
154
163
|
const l = this.moduleLoaders[n];
|
|
155
164
|
if (l)
|
|
156
165
|
try {
|
|
157
|
-
const
|
|
158
|
-
Array.isArray(
|
|
159
|
-
} catch (
|
|
166
|
+
const c = await l(t);
|
|
167
|
+
Array.isArray(c) && c.length > 0 && s.push(...c);
|
|
168
|
+
} catch (c) {
|
|
160
169
|
console.warn(
|
|
161
|
-
`Module loader '${n}' failed for toolset '${
|
|
162
|
-
|
|
170
|
+
`Module loader '${n}' failed for toolset '${r}':`,
|
|
171
|
+
c
|
|
163
172
|
);
|
|
164
173
|
}
|
|
165
174
|
}
|
|
166
175
|
}
|
|
167
|
-
return
|
|
176
|
+
return s;
|
|
168
177
|
}
|
|
169
178
|
}
|
|
170
|
-
class
|
|
171
|
-
constructor(
|
|
172
|
-
super(
|
|
179
|
+
class O extends Error {
|
|
180
|
+
constructor(t, s, r, i) {
|
|
181
|
+
super(t);
|
|
173
182
|
d(this, "code");
|
|
174
183
|
d(this, "details");
|
|
175
|
-
this.name = "ToolingError", this.code =
|
|
184
|
+
this.name = "ToolingError", this.code = s, this.details = r;
|
|
176
185
|
}
|
|
177
186
|
}
|
|
178
|
-
class
|
|
187
|
+
class q {
|
|
179
188
|
constructor(e = {}) {
|
|
180
189
|
d(this, "options");
|
|
181
190
|
d(this, "names", /* @__PURE__ */ new Set());
|
|
@@ -184,34 +193,34 @@ class z {
|
|
|
184
193
|
namespaceWithToolset: e.namespaceWithToolset ?? !0
|
|
185
194
|
};
|
|
186
195
|
}
|
|
187
|
-
getSafeName(e,
|
|
188
|
-
return !this.options.namespaceWithToolset ||
|
|
196
|
+
getSafeName(e, t) {
|
|
197
|
+
return !this.options.namespaceWithToolset || t.startsWith(`${e}.`) ? t : `${e}.${t}`;
|
|
189
198
|
}
|
|
190
199
|
has(e) {
|
|
191
200
|
return this.names.has(e);
|
|
192
201
|
}
|
|
193
202
|
add(e) {
|
|
194
203
|
if (this.names.has(e))
|
|
195
|
-
throw new
|
|
204
|
+
throw new O(
|
|
196
205
|
`Tool name collision: '${e}' already registered`,
|
|
197
206
|
"E_TOOL_NAME_CONFLICT"
|
|
198
207
|
);
|
|
199
208
|
this.names.add(e);
|
|
200
209
|
}
|
|
201
|
-
addForToolset(e,
|
|
202
|
-
this.add(
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
mapAndValidate(e,
|
|
207
|
-
return
|
|
208
|
-
const
|
|
209
|
-
if (this.has(
|
|
210
|
-
throw new
|
|
211
|
-
`Tool name collision for '${
|
|
210
|
+
addForToolset(e, t) {
|
|
211
|
+
this.add(t);
|
|
212
|
+
const s = this.toolsetToNames.get(e) ?? /* @__PURE__ */ new Set();
|
|
213
|
+
s.add(t), this.toolsetToNames.set(e, s);
|
|
214
|
+
}
|
|
215
|
+
mapAndValidate(e, t) {
|
|
216
|
+
return t.map((s) => {
|
|
217
|
+
const r = this.getSafeName(e, s.name);
|
|
218
|
+
if (this.has(r))
|
|
219
|
+
throw new O(
|
|
220
|
+
`Tool name collision for '${r}'`,
|
|
212
221
|
"E_TOOL_NAME_CONFLICT"
|
|
213
222
|
);
|
|
214
|
-
return { ...
|
|
223
|
+
return { ...s, name: r };
|
|
215
224
|
});
|
|
216
225
|
}
|
|
217
226
|
list() {
|
|
@@ -219,12 +228,12 @@ class z {
|
|
|
219
228
|
}
|
|
220
229
|
listByToolset() {
|
|
221
230
|
const e = {};
|
|
222
|
-
for (const [
|
|
223
|
-
e[
|
|
231
|
+
for (const [t, s] of this.toolsetToNames.entries())
|
|
232
|
+
e[t] = Array.from(s);
|
|
224
233
|
return e;
|
|
225
234
|
}
|
|
226
235
|
}
|
|
227
|
-
class
|
|
236
|
+
class de {
|
|
228
237
|
constructor(e) {
|
|
229
238
|
d(this, "server");
|
|
230
239
|
d(this, "resolver");
|
|
@@ -233,7 +242,7 @@ class ne {
|
|
|
233
242
|
d(this, "exposurePolicy");
|
|
234
243
|
d(this, "toolRegistry");
|
|
235
244
|
d(this, "activeToolsets", /* @__PURE__ */ new Set());
|
|
236
|
-
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
|
|
245
|
+
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 q({ namespaceWithToolset: !0 });
|
|
237
246
|
}
|
|
238
247
|
/**
|
|
239
248
|
* Sends a tool list change notification if configured.
|
|
@@ -268,46 +277,46 @@ class ne {
|
|
|
268
277
|
* @param skipNotification - If true, skips the tool list change notification (for batch operations)
|
|
269
278
|
* @returns Result object with success status and message
|
|
270
279
|
*/
|
|
271
|
-
async enableToolset(e,
|
|
272
|
-
const
|
|
273
|
-
if (!
|
|
280
|
+
async enableToolset(e, t = !1) {
|
|
281
|
+
const s = this.resolver.validateToolsetName(e);
|
|
282
|
+
if (!s.isValid || !s.sanitized)
|
|
274
283
|
return {
|
|
275
284
|
success: !1,
|
|
276
|
-
message:
|
|
285
|
+
message: s.error || "Unknown validation error"
|
|
277
286
|
};
|
|
278
|
-
const
|
|
279
|
-
if (this.activeToolsets.has(
|
|
287
|
+
const r = s.sanitized;
|
|
288
|
+
if (this.activeToolsets.has(r))
|
|
280
289
|
return {
|
|
281
290
|
success: !1,
|
|
282
|
-
message: `Toolset '${
|
|
291
|
+
message: `Toolset '${r}' is already enabled.`
|
|
283
292
|
};
|
|
284
|
-
const i = this.checkExposurePolicy(
|
|
293
|
+
const i = this.checkExposurePolicy(r);
|
|
285
294
|
if (!i.allowed)
|
|
286
295
|
return { success: !1, message: i.message };
|
|
287
296
|
const n = [];
|
|
288
297
|
try {
|
|
289
298
|
const l = await this.resolver.resolveToolsForToolsets(
|
|
290
|
-
[
|
|
299
|
+
[r],
|
|
291
300
|
this.context
|
|
292
301
|
);
|
|
293
302
|
if (l && l.length > 0) {
|
|
294
|
-
const
|
|
295
|
-
|
|
303
|
+
const c = this.toolRegistry.mapAndValidate(
|
|
304
|
+
r,
|
|
296
305
|
l
|
|
297
306
|
);
|
|
298
|
-
for (const
|
|
299
|
-
this.registerSingleTool(
|
|
307
|
+
for (const a of c)
|
|
308
|
+
this.registerSingleTool(a, r), n.push(a.name);
|
|
300
309
|
}
|
|
301
|
-
return this.activeToolsets.add(
|
|
310
|
+
return this.activeToolsets.add(r), t || await this.notifyToolsChanged(), {
|
|
302
311
|
success: !0,
|
|
303
|
-
message: `Toolset '${
|
|
312
|
+
message: `Toolset '${r}' enabled successfully. Registered ${l?.length ?? 0} tools.`
|
|
304
313
|
};
|
|
305
314
|
} catch (l) {
|
|
306
315
|
return n.length > 0 && console.warn(
|
|
307
|
-
`Partial failure enabling toolset '${
|
|
316
|
+
`Partial failure enabling toolset '${r}'. ${n.length} tools were registered but toolset activation failed. Tools remain registered due to MCP limitations: ${n.join(", ")}`
|
|
308
317
|
), {
|
|
309
318
|
success: !1,
|
|
310
|
-
message: `Failed to enable toolset '${
|
|
319
|
+
message: `Failed to enable toolset '${r}': ${l instanceof Error ? l.message : "Unknown error"}`
|
|
311
320
|
};
|
|
312
321
|
}
|
|
313
322
|
}
|
|
@@ -338,19 +347,19 @@ class ne {
|
|
|
338
347
|
* @param toolsetKey - The toolset key for tracking
|
|
339
348
|
* @private
|
|
340
349
|
*/
|
|
341
|
-
registerSingleTool(e,
|
|
350
|
+
registerSingleTool(e, t) {
|
|
342
351
|
e.annotations && Object.keys(e.annotations).length > 0 && e.annotations ? this.server.tool(
|
|
343
352
|
e.name,
|
|
344
353
|
e.description,
|
|
345
354
|
e.inputSchema,
|
|
346
355
|
e.annotations,
|
|
347
|
-
async (
|
|
356
|
+
async (r) => await e.handler(r)
|
|
348
357
|
) : this.server.tool(
|
|
349
358
|
e.name,
|
|
350
359
|
e.description,
|
|
351
360
|
e.inputSchema,
|
|
352
|
-
async (
|
|
353
|
-
), this.toolRegistry.addForToolset(
|
|
361
|
+
async (r) => await e.handler(r)
|
|
362
|
+
), this.toolRegistry.addForToolset(t, e.name);
|
|
354
363
|
}
|
|
355
364
|
/**
|
|
356
365
|
* Disables a toolset by name.
|
|
@@ -359,21 +368,21 @@ class ne {
|
|
|
359
368
|
* @returns Result object with success status and message
|
|
360
369
|
*/
|
|
361
370
|
async disableToolset(e) {
|
|
362
|
-
const
|
|
363
|
-
if (!
|
|
364
|
-
const
|
|
371
|
+
const t = this.resolver.validateToolsetName(e);
|
|
372
|
+
if (!t.isValid || !t.sanitized) {
|
|
373
|
+
const r = Array.from(this.activeToolsets).join(", ") || "none";
|
|
365
374
|
return {
|
|
366
375
|
success: !1,
|
|
367
|
-
message: `${
|
|
376
|
+
message: `${t.error || "Unknown validation error"} Active toolsets: ${r}`
|
|
368
377
|
};
|
|
369
378
|
}
|
|
370
|
-
const
|
|
371
|
-
return this.activeToolsets.has(
|
|
379
|
+
const s = t.sanitized;
|
|
380
|
+
return this.activeToolsets.has(s) ? (this.activeToolsets.delete(s), await this.notifyToolsChanged(), {
|
|
372
381
|
success: !0,
|
|
373
|
-
message: `Toolset '${
|
|
382
|
+
message: `Toolset '${s}' disabled successfully. Individual tools remain registered due to MCP limitations.`
|
|
374
383
|
}) : {
|
|
375
384
|
success: !1,
|
|
376
|
-
message: `Toolset '${
|
|
385
|
+
message: `Toolset '${s}' is not currently active. Active toolsets: ${Array.from(this.activeToolsets).join(", ") || "none"}`
|
|
377
386
|
};
|
|
378
387
|
}
|
|
379
388
|
getStatus() {
|
|
@@ -394,21 +403,21 @@ class ne {
|
|
|
394
403
|
* @returns Result object with overall success status and individual results
|
|
395
404
|
*/
|
|
396
405
|
async enableToolsets(e) {
|
|
397
|
-
const
|
|
406
|
+
const t = [];
|
|
398
407
|
for (const n of e)
|
|
399
408
|
try {
|
|
400
409
|
const l = await this.enableToolset(n, !0);
|
|
401
|
-
|
|
410
|
+
t.push({ name: n, ...l });
|
|
402
411
|
} catch (l) {
|
|
403
|
-
|
|
412
|
+
t.push({
|
|
404
413
|
name: n,
|
|
405
414
|
success: !1,
|
|
406
415
|
message: l instanceof Error ? l.message : "Unknown error",
|
|
407
416
|
code: "E_INTERNAL"
|
|
408
417
|
});
|
|
409
418
|
}
|
|
410
|
-
const
|
|
411
|
-
return
|
|
419
|
+
const s = t.every((n) => n.success), r = t.some((n) => n.success), i = s ? "All toolsets enabled" : r ? "Some toolsets failed to enable" : "All toolsets failed to enable";
|
|
420
|
+
return r && await this.notifyToolsChanged(), { success: s, results: t, message: i };
|
|
412
421
|
}
|
|
413
422
|
/**
|
|
414
423
|
* Enables all available toolsets in a batch operation.
|
|
@@ -419,103 +428,104 @@ class ne {
|
|
|
419
428
|
return this.enableToolsets(e);
|
|
420
429
|
}
|
|
421
430
|
}
|
|
422
|
-
|
|
423
|
-
|
|
431
|
+
const b = "_meta";
|
|
432
|
+
function ue(o, e, t, s) {
|
|
433
|
+
(s?.mode ?? "DYNAMIC") === "DYNAMIC" && (t.addForToolset(b, "enable_toolset"), o.tool(
|
|
424
434
|
"enable_toolset",
|
|
425
435
|
"Enable a toolset by name",
|
|
426
|
-
{ name:
|
|
436
|
+
{ name: v.string().describe("Toolset name") },
|
|
427
437
|
{ destructiveHint: !0, idempotentHint: !0 },
|
|
428
|
-
async (
|
|
429
|
-
const
|
|
438
|
+
async (i) => {
|
|
439
|
+
const n = await e.enableToolset(i.name);
|
|
430
440
|
return {
|
|
431
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
441
|
+
content: [{ type: "text", text: JSON.stringify(n) }]
|
|
432
442
|
};
|
|
433
443
|
}
|
|
434
|
-
),
|
|
444
|
+
), t.addForToolset(b, "disable_toolset"), o.tool(
|
|
435
445
|
"disable_toolset",
|
|
436
446
|
"Disable a toolset by name (state only)",
|
|
437
|
-
{ name:
|
|
447
|
+
{ name: v.string().describe("Toolset name") },
|
|
438
448
|
{ destructiveHint: !0, idempotentHint: !0 },
|
|
439
|
-
async (
|
|
440
|
-
const
|
|
449
|
+
async (i) => {
|
|
450
|
+
const n = await e.disableToolset(i.name);
|
|
441
451
|
return {
|
|
442
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
452
|
+
content: [{ type: "text", text: JSON.stringify(n) }]
|
|
443
453
|
};
|
|
444
454
|
}
|
|
445
|
-
),
|
|
455
|
+
), t.addForToolset(b, "list_toolsets"), o.tool(
|
|
446
456
|
"list_toolsets",
|
|
447
457
|
"List available toolsets with active status and definitions",
|
|
448
458
|
{},
|
|
449
459
|
{ readOnlyHint: !0, idempotentHint: !0 },
|
|
450
460
|
async () => {
|
|
451
|
-
const
|
|
452
|
-
const a = e.getToolsetDefinition(
|
|
461
|
+
const i = e.getAvailableToolsets(), n = e.getStatus().toolsetToTools, l = i.map((c) => {
|
|
462
|
+
const a = e.getToolsetDefinition(c);
|
|
453
463
|
return {
|
|
454
|
-
key:
|
|
455
|
-
active: e.isActive(
|
|
464
|
+
key: c,
|
|
465
|
+
active: e.isActive(c),
|
|
456
466
|
definition: a ? {
|
|
457
467
|
name: a.name,
|
|
458
468
|
description: a.description,
|
|
459
469
|
modules: a.modules ?? [],
|
|
460
470
|
decisionCriteria: a.decisionCriteria ?? void 0
|
|
461
471
|
} : null,
|
|
462
|
-
tools:
|
|
472
|
+
tools: n[c] ?? []
|
|
463
473
|
};
|
|
464
474
|
});
|
|
465
475
|
return {
|
|
466
476
|
content: [
|
|
467
|
-
{ type: "text", text: JSON.stringify({ toolsets:
|
|
477
|
+
{ type: "text", text: JSON.stringify({ toolsets: l }) }
|
|
468
478
|
]
|
|
469
479
|
};
|
|
470
480
|
}
|
|
471
|
-
),
|
|
481
|
+
), t.addForToolset(b, "describe_toolset"), o.tool(
|
|
472
482
|
"describe_toolset",
|
|
473
483
|
"Describe a toolset with definition, active status and tools",
|
|
474
|
-
{ name:
|
|
484
|
+
{ name: v.string().describe("Toolset name") },
|
|
475
485
|
{ readOnlyHint: !0, idempotentHint: !0 },
|
|
476
|
-
async (
|
|
477
|
-
const
|
|
478
|
-
if (!
|
|
486
|
+
async (i) => {
|
|
487
|
+
const n = e.getToolsetDefinition(i.name), l = e.getStatus().toolsetToTools;
|
|
488
|
+
if (!n)
|
|
479
489
|
return {
|
|
480
490
|
content: [
|
|
481
491
|
{
|
|
482
492
|
type: "text",
|
|
483
|
-
text: JSON.stringify({ error: `Unknown toolset '${
|
|
493
|
+
text: JSON.stringify({ error: `Unknown toolset '${i.name}'` })
|
|
484
494
|
}
|
|
485
495
|
]
|
|
486
496
|
};
|
|
487
|
-
const
|
|
488
|
-
key:
|
|
489
|
-
active: e.isActive(
|
|
497
|
+
const c = {
|
|
498
|
+
key: i.name,
|
|
499
|
+
active: e.isActive(i.name),
|
|
490
500
|
definition: {
|
|
491
|
-
name:
|
|
492
|
-
description:
|
|
493
|
-
modules:
|
|
494
|
-
decisionCriteria:
|
|
501
|
+
name: n.name,
|
|
502
|
+
description: n.description,
|
|
503
|
+
modules: n.modules ?? [],
|
|
504
|
+
decisionCriteria: n.decisionCriteria ?? void 0
|
|
495
505
|
},
|
|
496
|
-
tools:
|
|
506
|
+
tools: l[i.name] ?? []
|
|
497
507
|
};
|
|
498
508
|
return {
|
|
499
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
509
|
+
content: [{ type: "text", text: JSON.stringify(c) }]
|
|
500
510
|
};
|
|
501
511
|
}
|
|
502
|
-
)),
|
|
512
|
+
)), t.addForToolset(b, "list_tools"), o.tool(
|
|
503
513
|
"list_tools",
|
|
504
514
|
"List currently registered tool names (best effort)",
|
|
505
515
|
{},
|
|
506
516
|
{ readOnlyHint: !0, idempotentHint: !0 },
|
|
507
517
|
async () => {
|
|
508
|
-
const
|
|
509
|
-
tools:
|
|
510
|
-
toolsetToTools:
|
|
518
|
+
const i = e.getStatus(), n = {
|
|
519
|
+
tools: i.tools,
|
|
520
|
+
toolsetToTools: i.toolsetToTools
|
|
511
521
|
};
|
|
512
522
|
return {
|
|
513
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
523
|
+
content: [{ type: "text", text: JSON.stringify(n) }]
|
|
514
524
|
};
|
|
515
525
|
}
|
|
516
526
|
);
|
|
517
527
|
}
|
|
518
|
-
class
|
|
528
|
+
class C {
|
|
519
529
|
constructor(e) {
|
|
520
530
|
d(this, "mode");
|
|
521
531
|
d(this, "resolver");
|
|
@@ -523,24 +533,24 @@ class A {
|
|
|
523
533
|
d(this, "toolsetValidator");
|
|
524
534
|
d(this, "initPromise");
|
|
525
535
|
d(this, "initError", null);
|
|
526
|
-
this.toolsetValidator = new
|
|
527
|
-
const
|
|
528
|
-
this.mode =
|
|
536
|
+
this.toolsetValidator = new le();
|
|
537
|
+
const t = e.startup ?? {}, s = this.resolveStartupConfig(t, e.catalog);
|
|
538
|
+
this.mode = s.mode, this.resolver = new ce({
|
|
529
539
|
catalog: e.catalog,
|
|
530
540
|
moduleLoaders: e.moduleLoaders
|
|
531
541
|
});
|
|
532
|
-
const
|
|
542
|
+
const r = new q({
|
|
533
543
|
namespaceWithToolset: e.exposurePolicy?.namespaceToolsWithSetKey ?? !0
|
|
534
544
|
});
|
|
535
|
-
this.manager = new
|
|
545
|
+
this.manager = new de({
|
|
536
546
|
server: e.server,
|
|
537
547
|
resolver: this.resolver,
|
|
538
548
|
context: e.context,
|
|
539
549
|
onToolsListChanged: e.notifyToolsListChanged,
|
|
540
550
|
exposurePolicy: e.exposurePolicy,
|
|
541
|
-
toolRegistry:
|
|
542
|
-
}), e.registerMetaTools !== !1 &&
|
|
543
|
-
const i =
|
|
551
|
+
toolRegistry: r
|
|
552
|
+
}), e.registerMetaTools !== !1 && ue(e.server, this.manager, r, { mode: this.mode });
|
|
553
|
+
const i = s.toolsets;
|
|
544
554
|
this.initPromise = this.initializeToolsets(i);
|
|
545
555
|
}
|
|
546
556
|
/**
|
|
@@ -553,8 +563,8 @@ class A {
|
|
|
553
563
|
async initializeToolsets(e) {
|
|
554
564
|
try {
|
|
555
565
|
e === "ALL" ? await this.manager.enableToolsets(this.resolver.getAvailableToolsets()) : Array.isArray(e) && e.length > 0 && await this.manager.enableToolsets(e);
|
|
556
|
-
} catch (
|
|
557
|
-
this.initError =
|
|
566
|
+
} catch (t) {
|
|
567
|
+
this.initError = t instanceof Error ? t : new Error(String(t)), console.error("Failed to initialize toolsets:", this.initError);
|
|
558
568
|
}
|
|
559
569
|
}
|
|
560
570
|
/**
|
|
@@ -574,38 +584,38 @@ class A {
|
|
|
574
584
|
async isReady() {
|
|
575
585
|
return await this.initPromise, this.initError === null;
|
|
576
586
|
}
|
|
577
|
-
resolveStartupConfig(e,
|
|
587
|
+
resolveStartupConfig(e, t) {
|
|
578
588
|
if (e.mode) {
|
|
579
589
|
if (e.mode === "DYNAMIC" && e.toolsets)
|
|
580
590
|
return console.warn("startup.toolsets provided but ignored in DYNAMIC mode"), { mode: "DYNAMIC" };
|
|
581
591
|
if (e.mode === "STATIC") {
|
|
582
592
|
if (e.toolsets === "ALL")
|
|
583
593
|
return { mode: "STATIC", toolsets: "ALL" };
|
|
584
|
-
const
|
|
585
|
-
for (const i of
|
|
586
|
-
const { isValid: n, sanitized: l, error:
|
|
587
|
-
n && l ?
|
|
594
|
+
const s = Array.isArray(e.toolsets) ? e.toolsets : [], r = [];
|
|
595
|
+
for (const i of s) {
|
|
596
|
+
const { isValid: n, sanitized: l, error: c } = this.toolsetValidator.validateToolsetName(i, t);
|
|
597
|
+
n && l ? r.push(l) : c && console.warn(c);
|
|
588
598
|
}
|
|
589
|
-
if (
|
|
599
|
+
if (s.length > 0 && r.length === 0)
|
|
590
600
|
throw new Error(
|
|
591
601
|
"STATIC mode requires valid toolsets or 'ALL'; none were valid"
|
|
592
602
|
);
|
|
593
|
-
return { mode: "STATIC", toolsets:
|
|
603
|
+
return { mode: "STATIC", toolsets: r };
|
|
594
604
|
}
|
|
595
605
|
return { mode: e.mode };
|
|
596
606
|
}
|
|
597
607
|
if (e.toolsets === "ALL") return { mode: "STATIC", toolsets: "ALL" };
|
|
598
608
|
if (Array.isArray(e.toolsets) && e.toolsets.length > 0) {
|
|
599
|
-
const
|
|
600
|
-
for (const
|
|
601
|
-
const { isValid: i, sanitized: n, error: l } = this.toolsetValidator.validateToolsetName(
|
|
602
|
-
i && n ?
|
|
609
|
+
const s = [];
|
|
610
|
+
for (const r of e.toolsets) {
|
|
611
|
+
const { isValid: i, sanitized: n, error: l } = this.toolsetValidator.validateToolsetName(r, t);
|
|
612
|
+
i && n ? s.push(n) : l && console.warn(l);
|
|
603
613
|
}
|
|
604
|
-
if (
|
|
614
|
+
if (s.length === 0)
|
|
605
615
|
throw new Error(
|
|
606
616
|
"STATIC mode requires valid toolsets or 'ALL'; none were valid"
|
|
607
617
|
);
|
|
608
|
-
return { mode: "STATIC", toolsets:
|
|
618
|
+
return { mode: "STATIC", toolsets: s };
|
|
609
619
|
}
|
|
610
620
|
return { mode: "DYNAMIC" };
|
|
611
621
|
}
|
|
@@ -616,10 +626,10 @@ class A {
|
|
|
616
626
|
return this.manager;
|
|
617
627
|
}
|
|
618
628
|
}
|
|
619
|
-
var
|
|
620
|
-
class
|
|
629
|
+
var x, P;
|
|
630
|
+
class F {
|
|
621
631
|
constructor(e = {}) {
|
|
622
|
-
|
|
632
|
+
A(this, x);
|
|
623
633
|
d(this, "storage", /* @__PURE__ */ new Map());
|
|
624
634
|
d(this, "maxSize");
|
|
625
635
|
d(this, "ttlMs");
|
|
@@ -627,8 +637,8 @@ class V {
|
|
|
627
637
|
// Use ReturnType<typeof setInterval> for cross-env typings without NodeJS namespace
|
|
628
638
|
d(this, "pruneInterval");
|
|
629
639
|
this.maxSize = e.maxSize ?? 1e3, this.ttlMs = e.ttlMs ?? 1e3 * 60 * 60, this.onEvict = e.onEvict;
|
|
630
|
-
const
|
|
631
|
-
this.pruneInterval = setInterval(() => this.pruneExpired(),
|
|
640
|
+
const t = e.pruneIntervalMs ?? 1e3 * 60 * 10;
|
|
641
|
+
this.pruneInterval = setInterval(() => this.pruneExpired(), t);
|
|
632
642
|
}
|
|
633
643
|
getEntryCount() {
|
|
634
644
|
return this.storage.size;
|
|
@@ -640,13 +650,13 @@ class V {
|
|
|
640
650
|
return this.ttlMs;
|
|
641
651
|
}
|
|
642
652
|
get(e) {
|
|
643
|
-
const
|
|
644
|
-
return
|
|
653
|
+
const t = this.storage.get(e);
|
|
654
|
+
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;
|
|
645
655
|
}
|
|
646
|
-
set(e,
|
|
656
|
+
set(e, t) {
|
|
647
657
|
this.storage.size >= this.maxSize && this.evictLeastRecentlyUsed();
|
|
648
|
-
const
|
|
649
|
-
this.storage.set(e,
|
|
658
|
+
const s = { resource: t, lastAccessed: Date.now() };
|
|
659
|
+
this.storage.set(e, s);
|
|
650
660
|
}
|
|
651
661
|
/**
|
|
652
662
|
* Removes an entry from the cache.
|
|
@@ -654,8 +664,8 @@ class V {
|
|
|
654
664
|
* @param key - The key to remove
|
|
655
665
|
*/
|
|
656
666
|
delete(e) {
|
|
657
|
-
const
|
|
658
|
-
|
|
667
|
+
const t = this.storage.get(e);
|
|
668
|
+
t && (this.storage.delete(e), m(this, x, P).call(this, e, t.resource));
|
|
659
669
|
}
|
|
660
670
|
/**
|
|
661
671
|
* Stops the background pruning interval and optionally clears all entries.
|
|
@@ -671,8 +681,8 @@ class V {
|
|
|
671
681
|
clear() {
|
|
672
682
|
const e = Array.from(this.storage.entries());
|
|
673
683
|
this.storage.clear();
|
|
674
|
-
for (const [
|
|
675
|
-
m(this,
|
|
684
|
+
for (const [t, s] of e)
|
|
685
|
+
m(this, x, P).call(this, t, s.resource);
|
|
676
686
|
}
|
|
677
687
|
/**
|
|
678
688
|
* Evicts the least recently used entry from the cache.
|
|
@@ -687,84 +697,84 @@ class V {
|
|
|
687
697
|
* @private
|
|
688
698
|
*/
|
|
689
699
|
pruneExpired() {
|
|
690
|
-
const e = Date.now(),
|
|
691
|
-
for (const [
|
|
692
|
-
e -
|
|
693
|
-
for (const
|
|
694
|
-
this.delete(
|
|
700
|
+
const e = Date.now(), t = [];
|
|
701
|
+
for (const [s, r] of this.storage.entries())
|
|
702
|
+
e - r.lastAccessed > this.ttlMs && t.push(s);
|
|
703
|
+
for (const s of t)
|
|
704
|
+
this.delete(s);
|
|
695
705
|
}
|
|
696
706
|
}
|
|
697
|
-
|
|
707
|
+
x = new WeakSet(), /**
|
|
698
708
|
* Safely calls the evict callback, catching and logging any errors.
|
|
699
709
|
* @param key - The key being evicted
|
|
700
710
|
* @param resource - The resource being evicted
|
|
701
711
|
* @private
|
|
702
712
|
*/
|
|
703
|
-
|
|
713
|
+
P = function(e, t) {
|
|
704
714
|
if (this.onEvict)
|
|
705
715
|
try {
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
console.warn(`Error in cache eviction callback for key '${e}':`,
|
|
716
|
+
const s = this.onEvict(e, t);
|
|
717
|
+
s instanceof Promise && s.catch((r) => {
|
|
718
|
+
console.warn(`Error in cache eviction callback for key '${e}':`, r);
|
|
709
719
|
});
|
|
710
|
-
} catch (
|
|
711
|
-
console.warn(`Error in cache eviction callback for key '${e}':`,
|
|
720
|
+
} catch (s) {
|
|
721
|
+
console.warn(`Error in cache eviction callback for key '${e}':`, s);
|
|
712
722
|
}
|
|
713
723
|
};
|
|
714
|
-
function
|
|
715
|
-
const
|
|
716
|
-
for (const i of
|
|
724
|
+
function V(o, e, t, s) {
|
|
725
|
+
const r = ["/mcp", "/healthz", "/tools", "/.well-known/mcp-config"];
|
|
726
|
+
for (const i of t) {
|
|
717
727
|
const n = `${e}${i.path}`;
|
|
718
|
-
if (
|
|
719
|
-
(
|
|
728
|
+
if (r.some(
|
|
729
|
+
(a) => n.startsWith(`${e}${a}`)
|
|
720
730
|
)) {
|
|
721
731
|
console.warn(
|
|
722
732
|
`Custom endpoint ${i.method} ${i.path} conflicts with built-in MCP endpoint. Skipping registration.`
|
|
723
733
|
);
|
|
724
734
|
continue;
|
|
725
735
|
}
|
|
726
|
-
const
|
|
727
|
-
|
|
736
|
+
const c = i.method.toLowerCase();
|
|
737
|
+
o[c](n, async (a, u) => {
|
|
728
738
|
try {
|
|
729
|
-
const
|
|
730
|
-
let
|
|
739
|
+
const h = a.headers["mcp-client-id"]?.trim(), g = h && h.length > 0 ? h : `anon-${S()}`;
|
|
740
|
+
let w;
|
|
731
741
|
if (i.bodySchema) {
|
|
732
|
-
const
|
|
733
|
-
if (!
|
|
734
|
-
return
|
|
735
|
-
|
|
742
|
+
const y = i.bodySchema.safeParse(a.body);
|
|
743
|
+
if (!y.success)
|
|
744
|
+
return E(u, "body", y.error);
|
|
745
|
+
w = y.data;
|
|
736
746
|
}
|
|
737
|
-
let
|
|
747
|
+
let T = {};
|
|
738
748
|
if (i.querySchema) {
|
|
739
|
-
const
|
|
740
|
-
if (!
|
|
741
|
-
return
|
|
742
|
-
|
|
749
|
+
const y = i.querySchema.safeParse(a.query);
|
|
750
|
+
if (!y.success)
|
|
751
|
+
return E(u, "query", y.error);
|
|
752
|
+
T = y.data;
|
|
743
753
|
}
|
|
744
|
-
let
|
|
754
|
+
let I = {};
|
|
745
755
|
if (i.paramsSchema) {
|
|
746
|
-
const
|
|
747
|
-
if (!
|
|
748
|
-
return
|
|
749
|
-
|
|
756
|
+
const y = i.paramsSchema.safeParse(a.params);
|
|
757
|
+
if (!y.success)
|
|
758
|
+
return E(u, "params", y.error);
|
|
759
|
+
I = y.data;
|
|
750
760
|
}
|
|
751
|
-
const
|
|
752
|
-
body:
|
|
753
|
-
query:
|
|
754
|
-
params:
|
|
755
|
-
headers:
|
|
756
|
-
clientId:
|
|
761
|
+
const $ = {
|
|
762
|
+
body: w,
|
|
763
|
+
query: T,
|
|
764
|
+
params: I,
|
|
765
|
+
headers: a.headers,
|
|
766
|
+
clientId: g
|
|
757
767
|
};
|
|
758
|
-
if (
|
|
759
|
-
const
|
|
760
|
-
Object.assign(
|
|
768
|
+
if (s?.contextExtractor) {
|
|
769
|
+
const y = await s.contextExtractor(a);
|
|
770
|
+
Object.assign($, y);
|
|
761
771
|
}
|
|
762
|
-
const
|
|
772
|
+
const L = await i.handler($);
|
|
763
773
|
if (i.responseSchema) {
|
|
764
|
-
const
|
|
765
|
-
return
|
|
774
|
+
const y = i.responseSchema.safeParse(L);
|
|
775
|
+
return y.success ? y.data : (console.error(
|
|
766
776
|
`Response validation failed for ${i.method} ${i.path}:`,
|
|
767
|
-
|
|
777
|
+
y.error
|
|
768
778
|
), u.code(500), {
|
|
769
779
|
error: {
|
|
770
780
|
code: "RESPONSE_VALIDATION_ERROR",
|
|
@@ -772,59 +782,61 @@ function F(r, e, s, t) {
|
|
|
772
782
|
}
|
|
773
783
|
});
|
|
774
784
|
}
|
|
775
|
-
return
|
|
776
|
-
} catch (
|
|
785
|
+
return L;
|
|
786
|
+
} catch (h) {
|
|
777
787
|
return console.error(
|
|
778
788
|
`Error in custom endpoint ${i.method} ${i.path}:`,
|
|
779
|
-
|
|
789
|
+
h
|
|
780
790
|
), u.code(500), {
|
|
781
791
|
error: {
|
|
782
792
|
code: "INTERNAL_ERROR",
|
|
783
|
-
message:
|
|
793
|
+
message: h instanceof Error ? h.message : "Internal server error"
|
|
784
794
|
}
|
|
785
795
|
};
|
|
786
796
|
}
|
|
787
797
|
});
|
|
788
798
|
}
|
|
789
799
|
}
|
|
790
|
-
function
|
|
791
|
-
return
|
|
800
|
+
function E(o, e, t) {
|
|
801
|
+
return o.code(400), {
|
|
792
802
|
error: {
|
|
793
803
|
code: "VALIDATION_ERROR",
|
|
794
804
|
message: `Validation failed for ${e}`,
|
|
795
|
-
details:
|
|
805
|
+
details: t.errors
|
|
796
806
|
}
|
|
797
807
|
};
|
|
798
808
|
}
|
|
799
|
-
class
|
|
800
|
-
constructor(e,
|
|
809
|
+
class he {
|
|
810
|
+
constructor(e, t, s = {}, r, i, n) {
|
|
801
811
|
d(this, "options");
|
|
802
812
|
d(this, "defaultManager");
|
|
803
813
|
d(this, "createBundle");
|
|
814
|
+
d(this, "sessionContextResolver");
|
|
815
|
+
d(this, "baseContext");
|
|
804
816
|
d(this, "app", null);
|
|
805
817
|
d(this, "configSchema");
|
|
806
818
|
// Per-client server bundles and per-client session transports
|
|
807
|
-
d(this, "clientCache", new
|
|
808
|
-
onEvict: (e,
|
|
809
|
-
this.cleanupBundle(
|
|
819
|
+
d(this, "clientCache", new F({
|
|
820
|
+
onEvict: (e, t) => {
|
|
821
|
+
this.cleanupBundle(t);
|
|
810
822
|
}
|
|
811
823
|
}));
|
|
812
|
-
this.defaultManager = e, this.createBundle =
|
|
813
|
-
host:
|
|
814
|
-
port:
|
|
815
|
-
basePath:
|
|
816
|
-
cors:
|
|
817
|
-
logger:
|
|
818
|
-
app:
|
|
819
|
-
customEndpoints:
|
|
820
|
-
}, this.configSchema =
|
|
824
|
+
this.defaultManager = e, this.createBundle = t, this.sessionContextResolver = i, this.baseContext = n, this.options = {
|
|
825
|
+
host: s.host ?? "0.0.0.0",
|
|
826
|
+
port: s.port ?? 3e3,
|
|
827
|
+
basePath: s.basePath ?? "/",
|
|
828
|
+
cors: s.cors ?? !0,
|
|
829
|
+
logger: s.logger ?? !1,
|
|
830
|
+
app: s.app,
|
|
831
|
+
customEndpoints: s.customEndpoints
|
|
832
|
+
}, this.configSchema = r;
|
|
821
833
|
}
|
|
822
834
|
async start() {
|
|
823
835
|
if (this.app) return;
|
|
824
|
-
const e = this.options.app ??
|
|
825
|
-
this.options.cors && await e.register(
|
|
826
|
-
const
|
|
827
|
-
e.get(`${
|
|
836
|
+
const e = this.options.app ?? N({ logger: this.options.logger });
|
|
837
|
+
this.options.cors && await e.register(D, { origin: !0 });
|
|
838
|
+
const t = this.options.basePath.endsWith("/") ? this.options.basePath.slice(0, -1) : this.options.basePath;
|
|
839
|
+
e.get(`${t}/healthz`, async () => ({ ok: !0 })), e.get(`${t}/tools`, async () => this.defaultManager.getStatus()), e.get(`${t}/.well-known/mcp-config`, async (s, r) => (r.header("Content-Type", "application/schema+json; charset=utf-8"), this.configSchema ?? {
|
|
828
840
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
829
841
|
title: "MCP Session Configuration",
|
|
830
842
|
description: "Schema for the /mcp endpoint configuration",
|
|
@@ -834,72 +846,75 @@ class le {
|
|
|
834
846
|
"x-mcp-version": "1.0",
|
|
835
847
|
"x-query-style": "dot+bracket"
|
|
836
848
|
})), e.post(
|
|
837
|
-
`${
|
|
838
|
-
async (
|
|
839
|
-
const i =
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
849
|
+
`${t}/mcp`,
|
|
850
|
+
async (s, r) => {
|
|
851
|
+
const i = s.headers["mcp-client-id"]?.trim(), n = i && i.length > 0 ? i : `anon-${S()}`, l = !n.startsWith("anon-"), { cacheKey: c, mergedContext: a } = this.resolveSessionContext(
|
|
852
|
+
s,
|
|
853
|
+
n
|
|
854
|
+
);
|
|
855
|
+
let u = l ? this.clientCache.get(c) : null;
|
|
856
|
+
if (!u) {
|
|
857
|
+
const w = this.createBundle(a);
|
|
858
|
+
u = {
|
|
859
|
+
server: w.server,
|
|
860
|
+
orchestrator: w.orchestrator,
|
|
861
|
+
sessions: /* @__PURE__ */ new Map()
|
|
862
|
+
}, l && this.clientCache.set(c, u);
|
|
848
863
|
}
|
|
849
|
-
const
|
|
850
|
-
let
|
|
851
|
-
if (
|
|
852
|
-
|
|
853
|
-
else if (!
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
sessionIdGenerator: () =>
|
|
857
|
-
onsessioninitialized: (
|
|
858
|
-
|
|
864
|
+
const h = s.headers["mcp-session-id"];
|
|
865
|
+
let g;
|
|
866
|
+
if (h && u.sessions.get(h))
|
|
867
|
+
g = u.sessions.get(h);
|
|
868
|
+
else if (!h && _(s.body)) {
|
|
869
|
+
const w = S();
|
|
870
|
+
g = new z({
|
|
871
|
+
sessionIdGenerator: () => w,
|
|
872
|
+
onsessioninitialized: (T) => {
|
|
873
|
+
u.sessions.set(T, g);
|
|
859
874
|
}
|
|
860
875
|
});
|
|
861
876
|
try {
|
|
862
|
-
await
|
|
877
|
+
await u.server.connect(g);
|
|
863
878
|
} catch {
|
|
864
|
-
return
|
|
879
|
+
return r.code(500), {
|
|
865
880
|
jsonrpc: "2.0",
|
|
866
881
|
error: { code: -32603, message: "Error initializing server." },
|
|
867
882
|
id: null
|
|
868
883
|
};
|
|
869
884
|
}
|
|
870
|
-
|
|
871
|
-
|
|
885
|
+
g.onclose = () => {
|
|
886
|
+
g?.sessionId && u.sessions.delete(g.sessionId);
|
|
872
887
|
};
|
|
873
888
|
} else
|
|
874
|
-
return
|
|
889
|
+
return r.code(400), {
|
|
875
890
|
jsonrpc: "2.0",
|
|
876
891
|
error: { code: -32e3, message: "Session not found or expired" },
|
|
877
892
|
id: null
|
|
878
893
|
};
|
|
879
|
-
return await
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
),
|
|
894
|
+
return await g.handleRequest(
|
|
895
|
+
s.raw,
|
|
896
|
+
r.raw,
|
|
897
|
+
s.body
|
|
898
|
+
), r;
|
|
884
899
|
}
|
|
885
|
-
), e.get(`${
|
|
886
|
-
const i =
|
|
900
|
+
), e.get(`${t}/mcp`, async (s, r) => {
|
|
901
|
+
const i = s.headers["mcp-client-id"]?.trim(), n = i && i.length > 0 ? i : "";
|
|
887
902
|
if (!n)
|
|
888
|
-
return
|
|
903
|
+
return r.code(400), "Missing mcp-client-id";
|
|
889
904
|
const l = this.clientCache.get(n);
|
|
890
905
|
if (!l)
|
|
891
|
-
return
|
|
892
|
-
const
|
|
893
|
-
if (!
|
|
894
|
-
return
|
|
895
|
-
const
|
|
896
|
-
return
|
|
906
|
+
return r.code(400), "Invalid or expired client";
|
|
907
|
+
const c = s.headers["mcp-session-id"];
|
|
908
|
+
if (!c)
|
|
909
|
+
return r.code(400), "Missing mcp-session-id";
|
|
910
|
+
const a = l.sessions.get(c);
|
|
911
|
+
return a ? (await a.handleRequest(s.raw, r.raw), r) : (r.code(400), "Invalid or expired session ID");
|
|
897
912
|
}), e.delete(
|
|
898
|
-
`${
|
|
899
|
-
async (
|
|
900
|
-
const i =
|
|
913
|
+
`${t}/mcp`,
|
|
914
|
+
async (s, r) => {
|
|
915
|
+
const i = s.headers["mcp-client-id"]?.trim(), n = i && i.length > 0 ? i : "", l = s.headers["mcp-session-id"];
|
|
901
916
|
if (!n || !l)
|
|
902
|
-
return
|
|
917
|
+
return r.code(400), {
|
|
903
918
|
jsonrpc: "2.0",
|
|
904
919
|
error: {
|
|
905
920
|
code: -32600,
|
|
@@ -907,25 +922,25 @@ class le {
|
|
|
907
922
|
},
|
|
908
923
|
id: null
|
|
909
924
|
};
|
|
910
|
-
const
|
|
911
|
-
if (!
|
|
912
|
-
return
|
|
925
|
+
const c = this.clientCache.get(n), a = c?.sessions.get(l);
|
|
926
|
+
if (!c || !a)
|
|
927
|
+
return r.code(404), {
|
|
913
928
|
jsonrpc: "2.0",
|
|
914
929
|
error: { code: -32e3, message: "Session not found or expired" },
|
|
915
930
|
id: null
|
|
916
931
|
};
|
|
917
932
|
try {
|
|
918
|
-
if (typeof
|
|
933
|
+
if (typeof a.close == "function")
|
|
919
934
|
try {
|
|
920
|
-
await
|
|
935
|
+
await a.close();
|
|
921
936
|
} catch {
|
|
922
937
|
}
|
|
923
938
|
} finally {
|
|
924
|
-
|
|
939
|
+
a?.sessionId ? c.sessions.delete(a.sessionId) : c.sessions.delete(l);
|
|
925
940
|
}
|
|
926
|
-
return
|
|
941
|
+
return r.code(204).send(), r;
|
|
927
942
|
}
|
|
928
|
-
), this.options.customEndpoints && this.options.customEndpoints.length > 0 &&
|
|
943
|
+
), this.options.customEndpoints && this.options.customEndpoints.length > 0 && V(e, t, this.options.customEndpoints), this.options.app || await e.listen({ host: this.options.host, port: this.options.port }), this.app = e;
|
|
929
944
|
}
|
|
930
945
|
/**
|
|
931
946
|
* Stops the Fastify server and cleans up all resources.
|
|
@@ -941,150 +956,399 @@ class le {
|
|
|
941
956
|
* @private
|
|
942
957
|
*/
|
|
943
958
|
cleanupBundle(e) {
|
|
944
|
-
for (const [
|
|
959
|
+
for (const [t, s] of e.sessions.entries())
|
|
945
960
|
try {
|
|
946
|
-
typeof
|
|
947
|
-
console.warn(`Error closing session ${
|
|
961
|
+
typeof s.close == "function" && s.close().catch((r) => {
|
|
962
|
+
console.warn(`Error closing session ${t}:`, r);
|
|
948
963
|
});
|
|
949
|
-
} catch (
|
|
950
|
-
console.warn(`Error closing session ${
|
|
964
|
+
} catch (r) {
|
|
965
|
+
console.warn(`Error closing session ${t}:`, r);
|
|
951
966
|
}
|
|
952
967
|
e.sessions.clear();
|
|
953
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* Resolves the session context and generates a cache key for the request.
|
|
971
|
+
* If a session context resolver is configured, it extracts query parameters
|
|
972
|
+
* and merges session-specific context with the base context.
|
|
973
|
+
*
|
|
974
|
+
* @param req - The Fastify request
|
|
975
|
+
* @param clientId - The client identifier
|
|
976
|
+
* @returns Object with cache key and merged context
|
|
977
|
+
* @private
|
|
978
|
+
*/
|
|
979
|
+
resolveSessionContext(e, t) {
|
|
980
|
+
if (!this.sessionContextResolver)
|
|
981
|
+
return {
|
|
982
|
+
cacheKey: t,
|
|
983
|
+
mergedContext: this.baseContext
|
|
984
|
+
};
|
|
985
|
+
const s = {
|
|
986
|
+
clientId: t,
|
|
987
|
+
headers: this.extractHeaders(e),
|
|
988
|
+
query: this.extractQuery(e)
|
|
989
|
+
}, r = this.sessionContextResolver.resolve(
|
|
990
|
+
s,
|
|
991
|
+
this.baseContext
|
|
992
|
+
);
|
|
993
|
+
return {
|
|
994
|
+
cacheKey: r.cacheKeySuffix === "default" ? t : `${t}:${r.cacheKeySuffix}`,
|
|
995
|
+
mergedContext: r.context
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Extracts headers from a Fastify request as a Record.
|
|
1000
|
+
* Normalizes header names to lowercase.
|
|
1001
|
+
*
|
|
1002
|
+
* @param req - The Fastify request
|
|
1003
|
+
* @returns Headers as a string record
|
|
1004
|
+
* @private
|
|
1005
|
+
*/
|
|
1006
|
+
extractHeaders(e) {
|
|
1007
|
+
const t = {};
|
|
1008
|
+
for (const [s, r] of Object.entries(e.headers))
|
|
1009
|
+
typeof r == "string" ? t[s.toLowerCase()] = r : Array.isArray(r) && r.length > 0 && (t[s.toLowerCase()] = r[0]);
|
|
1010
|
+
return t;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Extracts query parameters from a Fastify request as a Record.
|
|
1014
|
+
*
|
|
1015
|
+
* @param req - The Fastify request
|
|
1016
|
+
* @returns Query parameters as a string record
|
|
1017
|
+
* @private
|
|
1018
|
+
*/
|
|
1019
|
+
extractQuery(e) {
|
|
1020
|
+
const t = {}, s = e.query;
|
|
1021
|
+
if (s && typeof s == "object")
|
|
1022
|
+
for (const [r, i] of Object.entries(s))
|
|
1023
|
+
typeof i == "string" && (t[r] = i);
|
|
1024
|
+
return t;
|
|
1025
|
+
}
|
|
954
1026
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1027
|
+
class fe {
|
|
1028
|
+
constructor(e) {
|
|
1029
|
+
d(this, "config");
|
|
1030
|
+
d(this, "queryParamName");
|
|
1031
|
+
d(this, "encoding");
|
|
1032
|
+
d(this, "allowedKeys");
|
|
1033
|
+
d(this, "mergeStrategy");
|
|
1034
|
+
this.config = e, this.queryParamName = e.queryParam?.name ?? "config", this.encoding = e.queryParam?.encoding ?? "base64", this.allowedKeys = e.queryParam?.allowedKeys ? new Set(e.queryParam.allowedKeys) : null, this.mergeStrategy = e.merge ?? "shallow";
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Resolves the session context for a request.
|
|
1038
|
+
*
|
|
1039
|
+
* @param request - The request context (clientId, headers, query)
|
|
1040
|
+
* @param baseContext - The base context from server configuration
|
|
1041
|
+
* @returns The resolved context and cache key suffix
|
|
1042
|
+
*/
|
|
1043
|
+
resolve(e, t) {
|
|
1044
|
+
if (this.config.enabled === !1)
|
|
1045
|
+
return {
|
|
1046
|
+
context: t,
|
|
1047
|
+
cacheKeySuffix: "default"
|
|
1048
|
+
};
|
|
1049
|
+
const s = this.parseQueryConfig(e.query);
|
|
1050
|
+
if (this.config.contextResolver)
|
|
1051
|
+
try {
|
|
1052
|
+
return {
|
|
1053
|
+
context: this.config.contextResolver(
|
|
1054
|
+
e,
|
|
1055
|
+
t,
|
|
1056
|
+
s
|
|
1057
|
+
),
|
|
1058
|
+
cacheKeySuffix: this.generateCacheKeySuffix(s)
|
|
1059
|
+
};
|
|
1060
|
+
} catch {
|
|
1061
|
+
return {
|
|
1062
|
+
context: t,
|
|
1063
|
+
cacheKeySuffix: "default"
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
return {
|
|
1067
|
+
context: this.mergeContexts(t, s),
|
|
1068
|
+
cacheKeySuffix: this.generateCacheKeySuffix(s)
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Parses the session config from query parameters.
|
|
1073
|
+
* Returns empty object on parse failure (fail secure).
|
|
1074
|
+
*
|
|
1075
|
+
* @param query - Query parameters from the request
|
|
1076
|
+
* @returns Parsed and filtered config object
|
|
1077
|
+
* @private
|
|
1078
|
+
*/
|
|
1079
|
+
parseQueryConfig(e) {
|
|
1080
|
+
const t = e[this.queryParamName];
|
|
1081
|
+
if (!t)
|
|
1082
|
+
return {};
|
|
1083
|
+
try {
|
|
1084
|
+
let s;
|
|
1085
|
+
this.encoding === "base64" ? s = Buffer.from(t, "base64").toString("utf-8") : s = t;
|
|
1086
|
+
const r = JSON.parse(s);
|
|
1087
|
+
return typeof r != "object" || r === null || Array.isArray(r) ? {} : this.filterAllowedKeys(r);
|
|
1088
|
+
} catch {
|
|
1089
|
+
return {};
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Filters the parsed config to only include allowed keys.
|
|
1094
|
+
* If no allowedKeys whitelist is configured, returns the full object.
|
|
1095
|
+
*
|
|
1096
|
+
* @param parsed - The parsed config object
|
|
1097
|
+
* @returns Filtered config with only allowed keys
|
|
1098
|
+
* @private
|
|
1099
|
+
*/
|
|
1100
|
+
filterAllowedKeys(e) {
|
|
1101
|
+
if (!this.allowedKeys)
|
|
1102
|
+
return e;
|
|
1103
|
+
const t = {};
|
|
1104
|
+
for (const s of this.allowedKeys)
|
|
1105
|
+
s in e && (t[s] = e[s]);
|
|
1106
|
+
return t;
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Merges the base context with the session config.
|
|
1110
|
+
*
|
|
1111
|
+
* @param baseContext - The base context from server configuration
|
|
1112
|
+
* @param sessionConfig - The parsed session config
|
|
1113
|
+
* @returns Merged context
|
|
1114
|
+
* @private
|
|
1115
|
+
*/
|
|
1116
|
+
mergeContexts(e, t) {
|
|
1117
|
+
return Object.keys(t).length === 0 ? e : typeof e != "object" || e === null || Array.isArray(e) ? t : this.mergeStrategy === "deep" ? this.deepMerge(
|
|
1118
|
+
e,
|
|
1119
|
+
t
|
|
1120
|
+
) : {
|
|
1121
|
+
...e,
|
|
1122
|
+
...t
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Performs a deep merge of two objects.
|
|
1127
|
+
* Session config values override base context values.
|
|
1128
|
+
*
|
|
1129
|
+
* @param base - The base object
|
|
1130
|
+
* @param override - The override object
|
|
1131
|
+
* @returns Deep merged object
|
|
1132
|
+
* @private
|
|
1133
|
+
*/
|
|
1134
|
+
deepMerge(e, t) {
|
|
1135
|
+
const s = { ...e };
|
|
1136
|
+
for (const [r, i] of Object.entries(t)) {
|
|
1137
|
+
const n = s[r];
|
|
1138
|
+
typeof i == "object" && i !== null && !Array.isArray(i) && typeof n == "object" && n !== null && !Array.isArray(n) ? s[r] = this.deepMerge(
|
|
1139
|
+
n,
|
|
1140
|
+
i
|
|
1141
|
+
) : s[r] = i;
|
|
1142
|
+
}
|
|
1143
|
+
return s;
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Generates a deterministic cache key suffix based on the session config.
|
|
1147
|
+
* Returns 'default' when no session config is present.
|
|
1148
|
+
*
|
|
1149
|
+
* @param sessionConfig - The parsed session config
|
|
1150
|
+
* @returns Hash string or 'default'
|
|
1151
|
+
* @private
|
|
1152
|
+
*/
|
|
1153
|
+
generateCacheKeySuffix(e) {
|
|
1154
|
+
if (Object.keys(e).length === 0)
|
|
1155
|
+
return "default";
|
|
1156
|
+
const t = Object.keys(e).sort(), s = {};
|
|
1157
|
+
for (const i of t)
|
|
1158
|
+
s[i] = e[i];
|
|
1159
|
+
const r = JSON.stringify(s);
|
|
1160
|
+
return ae("sha256").update(r).digest("hex").slice(0, 16);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
function K(o) {
|
|
1164
|
+
me(o), ye(o), ge(o), pe(o), ve(o);
|
|
1165
|
+
}
|
|
1166
|
+
function me(o) {
|
|
1167
|
+
if (!o || typeof o != "object")
|
|
1168
|
+
throw new Error(
|
|
1169
|
+
"Session context configuration must be an object"
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
function ye(o) {
|
|
1173
|
+
if (o.enabled !== void 0 && typeof o.enabled != "boolean")
|
|
1174
|
+
throw new Error(
|
|
1175
|
+
`enabled must be a boolean, got ${typeof o.enabled}`
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
function ge(o) {
|
|
1179
|
+
if (o.queryParam !== void 0) {
|
|
1180
|
+
if (typeof o.queryParam != "object" || o.queryParam === null)
|
|
1181
|
+
throw new Error("queryParam must be an object");
|
|
1182
|
+
if (o.queryParam.name !== void 0 && (typeof o.queryParam.name != "string" || o.queryParam.name.length === 0))
|
|
1183
|
+
throw new Error("queryParam.name must be a non-empty string");
|
|
1184
|
+
if (o.queryParam.encoding !== void 0 && o.queryParam.encoding !== "base64" && o.queryParam.encoding !== "json")
|
|
1185
|
+
throw new Error(
|
|
1186
|
+
`Invalid queryParam.encoding: "${o.queryParam.encoding}". Must be "base64" or "json"`
|
|
1187
|
+
);
|
|
1188
|
+
if (o.queryParam.allowedKeys !== void 0) {
|
|
1189
|
+
if (!Array.isArray(o.queryParam.allowedKeys))
|
|
1190
|
+
throw new Error("queryParam.allowedKeys must be an array of strings");
|
|
1191
|
+
for (let e = 0; e < o.queryParam.allowedKeys.length; e++) {
|
|
1192
|
+
const t = o.queryParam.allowedKeys[e];
|
|
1193
|
+
if (typeof t != "string" || t.length === 0)
|
|
1194
|
+
throw new Error(
|
|
1195
|
+
`queryParam.allowedKeys[${e}] must be a non-empty string`
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
function pe(o) {
|
|
1202
|
+
if (o.contextResolver !== void 0 && typeof o.contextResolver != "function")
|
|
1203
|
+
throw new Error(
|
|
1204
|
+
"contextResolver must be a function: (request, baseContext, parsedQueryConfig?) => unknown"
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
function ve(o) {
|
|
1208
|
+
if (o.merge !== void 0 && o.merge !== "shallow" && o.merge !== "deep")
|
|
1209
|
+
throw new Error(
|
|
1210
|
+
`Invalid merge strategy: "${o.merge}". Must be "shallow" or "deep"`
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
const we = v.object({
|
|
1214
|
+
mode: v.enum(["DYNAMIC", "STATIC"]).optional(),
|
|
1215
|
+
toolsets: v.union([v.array(v.string()), v.literal("ALL")]).optional()
|
|
958
1216
|
}).strict();
|
|
959
|
-
async function
|
|
960
|
-
if (
|
|
1217
|
+
async function De(o) {
|
|
1218
|
+
if (o.startup)
|
|
961
1219
|
try {
|
|
962
|
-
|
|
1220
|
+
we.parse(o.startup);
|
|
963
1221
|
} catch (a) {
|
|
964
|
-
if (a instanceof
|
|
965
|
-
const
|
|
1222
|
+
if (a instanceof v.ZodError) {
|
|
1223
|
+
const u = a.format();
|
|
966
1224
|
throw new Error(
|
|
967
1225
|
`Invalid startup configuration:
|
|
968
|
-
${JSON.stringify(
|
|
1226
|
+
${JSON.stringify(u, null, 2)}
|
|
969
1227
|
|
|
970
1228
|
Hint: Common mistake - use "toolsets" not "initialToolsets"`
|
|
971
1229
|
);
|
|
972
1230
|
}
|
|
973
1231
|
throw a;
|
|
974
1232
|
}
|
|
975
|
-
const e =
|
|
976
|
-
|
|
1233
|
+
const e = o.startup?.mode ?? "DYNAMIC";
|
|
1234
|
+
let t;
|
|
1235
|
+
if (o.sessionContext && (K(o.sessionContext), t = new fe(o.sessionContext), e === "STATIC" && o.sessionContext.enabled !== !1 && console.warn(
|
|
1236
|
+
"sessionContext has limited effect in STATIC mode: all clients share the same server instance with base context. Use DYNAMIC mode for per-session context isolation."
|
|
1237
|
+
)), typeof o.createServer != "function")
|
|
977
1238
|
throw new Error("createMcpServer: `createServer` (factory) is required");
|
|
978
|
-
const s =
|
|
1239
|
+
const s = o.createServer(), r = (a) => typeof a?.server?.notification == "function", i = (a) => typeof a?.notifyToolsListChanged == "function", n = async (a) => {
|
|
979
1240
|
try {
|
|
980
|
-
if (
|
|
1241
|
+
if (r(a)) {
|
|
981
1242
|
await a.server.notification({
|
|
982
1243
|
method: "notifications/tools/list_changed"
|
|
983
1244
|
});
|
|
984
1245
|
return;
|
|
985
1246
|
}
|
|
986
|
-
|
|
987
|
-
} catch (
|
|
988
|
-
if ((
|
|
1247
|
+
i(a) && await a.notifyToolsListChanged();
|
|
1248
|
+
} catch (u) {
|
|
1249
|
+
if ((u instanceof Error ? u.message : String(u)) === "Not connected")
|
|
989
1250
|
return;
|
|
990
|
-
console.warn("Failed to send tools list changed notification:",
|
|
1251
|
+
console.warn("Failed to send tools list changed notification:", u);
|
|
991
1252
|
}
|
|
992
|
-
},
|
|
1253
|
+
}, l = new C({
|
|
993
1254
|
server: s,
|
|
994
|
-
catalog:
|
|
995
|
-
moduleLoaders:
|
|
996
|
-
exposurePolicy:
|
|
997
|
-
context:
|
|
998
|
-
notifyToolsListChanged: async () =>
|
|
999
|
-
startup:
|
|
1000
|
-
registerMetaTools:
|
|
1255
|
+
catalog: o.catalog,
|
|
1256
|
+
moduleLoaders: o.moduleLoaders,
|
|
1257
|
+
exposurePolicy: o.exposurePolicy,
|
|
1258
|
+
context: o.context,
|
|
1259
|
+
notifyToolsListChanged: async () => n(s),
|
|
1260
|
+
startup: o.startup,
|
|
1261
|
+
registerMetaTools: o.registerMetaTools !== void 0 ? o.registerMetaTools : e === "DYNAMIC"
|
|
1001
1262
|
});
|
|
1002
|
-
e === "STATIC" && await
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1005
|
-
() => {
|
|
1263
|
+
e === "STATIC" && await l.ensureReady();
|
|
1264
|
+
const c = new he(
|
|
1265
|
+
l.getManager(),
|
|
1266
|
+
(a) => {
|
|
1267
|
+
const u = a ?? o.context;
|
|
1006
1268
|
if (e === "STATIC")
|
|
1007
|
-
return { server: s, orchestrator:
|
|
1008
|
-
const
|
|
1009
|
-
server:
|
|
1010
|
-
catalog:
|
|
1011
|
-
moduleLoaders:
|
|
1012
|
-
exposurePolicy:
|
|
1013
|
-
context:
|
|
1014
|
-
notifyToolsListChanged: async () =>
|
|
1015
|
-
startup:
|
|
1016
|
-
registerMetaTools:
|
|
1269
|
+
return { server: s, orchestrator: l };
|
|
1270
|
+
const h = o.createServer(), g = new C({
|
|
1271
|
+
server: h,
|
|
1272
|
+
catalog: o.catalog,
|
|
1273
|
+
moduleLoaders: o.moduleLoaders,
|
|
1274
|
+
exposurePolicy: o.exposurePolicy,
|
|
1275
|
+
context: u,
|
|
1276
|
+
notifyToolsListChanged: async () => n(h),
|
|
1277
|
+
startup: o.startup,
|
|
1278
|
+
registerMetaTools: o.registerMetaTools !== void 0 ? o.registerMetaTools : e === "DYNAMIC"
|
|
1017
1279
|
});
|
|
1018
|
-
return { server:
|
|
1280
|
+
return { server: h, orchestrator: g };
|
|
1019
1281
|
},
|
|
1020
|
-
|
|
1021
|
-
|
|
1282
|
+
o.http,
|
|
1283
|
+
o.configSchema,
|
|
1284
|
+
t,
|
|
1285
|
+
o.context
|
|
1022
1286
|
);
|
|
1023
1287
|
return {
|
|
1024
1288
|
server: s,
|
|
1025
1289
|
start: async () => {
|
|
1026
|
-
await
|
|
1290
|
+
await c.start();
|
|
1027
1291
|
},
|
|
1028
1292
|
close: async () => {
|
|
1029
|
-
await
|
|
1293
|
+
await c.stop();
|
|
1030
1294
|
}
|
|
1031
1295
|
};
|
|
1032
1296
|
}
|
|
1033
|
-
function
|
|
1034
|
-
|
|
1297
|
+
function Te(o) {
|
|
1298
|
+
be(o), Se(o), xe(o), Ae(o);
|
|
1035
1299
|
}
|
|
1036
|
-
function
|
|
1037
|
-
if (!
|
|
1300
|
+
function be(o) {
|
|
1301
|
+
if (!o || typeof o != "object")
|
|
1038
1302
|
throw new Error(
|
|
1039
1303
|
"Permission configuration is required for createPermissionBasedMcpServer"
|
|
1040
1304
|
);
|
|
1041
1305
|
}
|
|
1042
|
-
function
|
|
1043
|
-
if (!
|
|
1306
|
+
function Se(o) {
|
|
1307
|
+
if (!o.source)
|
|
1044
1308
|
throw new Error('Permission source must be either "headers" or "config"');
|
|
1045
|
-
if (
|
|
1309
|
+
if (o.source !== "headers" && o.source !== "config")
|
|
1046
1310
|
throw new Error(
|
|
1047
|
-
`Invalid permission source: "${
|
|
1311
|
+
`Invalid permission source: "${o.source}". Must be either "headers" or "config"`
|
|
1048
1312
|
);
|
|
1049
1313
|
}
|
|
1050
|
-
function
|
|
1051
|
-
if (
|
|
1314
|
+
function xe(o) {
|
|
1315
|
+
if (o.source === "config" && !o.staticMap && !o.resolver)
|
|
1052
1316
|
throw new Error(
|
|
1053
1317
|
"Config-based permissions require at least one of: staticMap or resolver function"
|
|
1054
1318
|
);
|
|
1055
1319
|
}
|
|
1056
|
-
function
|
|
1057
|
-
if (
|
|
1058
|
-
if (typeof
|
|
1320
|
+
function Ae(o) {
|
|
1321
|
+
if (o.staticMap !== void 0) {
|
|
1322
|
+
if (typeof o.staticMap != "object" || o.staticMap === null)
|
|
1059
1323
|
throw new Error(
|
|
1060
1324
|
"staticMap must be an object mapping client IDs to toolset arrays"
|
|
1061
1325
|
);
|
|
1062
|
-
|
|
1326
|
+
Ce(o.staticMap);
|
|
1063
1327
|
}
|
|
1064
|
-
if (
|
|
1328
|
+
if (o.resolver !== void 0 && typeof o.resolver != "function")
|
|
1065
1329
|
throw new Error(
|
|
1066
1330
|
"resolver must be a synchronous function: (clientId: string) => string[]"
|
|
1067
1331
|
);
|
|
1068
|
-
if (
|
|
1332
|
+
if (o.defaultPermissions !== void 0 && !Array.isArray(o.defaultPermissions))
|
|
1069
1333
|
throw new Error("defaultPermissions must be an array of toolset names");
|
|
1070
|
-
if (
|
|
1334
|
+
if (o.headerName !== void 0 && (typeof o.headerName != "string" || o.headerName.length === 0))
|
|
1071
1335
|
throw new Error("headerName must be a non-empty string");
|
|
1072
1336
|
}
|
|
1073
|
-
function
|
|
1074
|
-
for (const [e,
|
|
1075
|
-
if (!Array.isArray(
|
|
1337
|
+
function Ce(o) {
|
|
1338
|
+
for (const [e, t] of Object.entries(o))
|
|
1339
|
+
if (!Array.isArray(t))
|
|
1076
1340
|
throw new Error(
|
|
1077
1341
|
`staticMap value for client "${e}" must be an array of toolset names`
|
|
1078
1342
|
);
|
|
1079
1343
|
}
|
|
1080
|
-
var p,
|
|
1081
|
-
class
|
|
1344
|
+
var p, H, B, Y, U, W;
|
|
1345
|
+
class Ee {
|
|
1082
1346
|
/**
|
|
1083
1347
|
* Creates a new PermissionResolver instance.
|
|
1084
1348
|
* @param config - The permission configuration defining how permissions are resolved
|
|
1085
1349
|
*/
|
|
1086
1350
|
constructor(e) {
|
|
1087
|
-
|
|
1351
|
+
A(this, p);
|
|
1088
1352
|
d(this, "cache", /* @__PURE__ */ new Map());
|
|
1089
1353
|
d(this, "normalizedHeaderName");
|
|
1090
1354
|
this.config = e, this.normalizedHeaderName = (e.headerName || "mcp-toolset-permissions").toLowerCase();
|
|
@@ -1102,23 +1366,23 @@ class pe {
|
|
|
1102
1366
|
* @param headers - Optional request headers (required for header-based permissions)
|
|
1103
1367
|
* @returns Array of toolset names the client is allowed to access
|
|
1104
1368
|
*/
|
|
1105
|
-
resolvePermissions(e,
|
|
1369
|
+
resolvePermissions(e, t) {
|
|
1106
1370
|
if (this.cache.has(e))
|
|
1107
1371
|
return this.cache.get(e);
|
|
1108
|
-
let
|
|
1372
|
+
let s;
|
|
1109
1373
|
try {
|
|
1110
|
-
this.config.source === "headers" ?
|
|
1374
|
+
this.config.source === "headers" ? s = m(this, p, H).call(this, t) : s = m(this, p, Y).call(this, e), Array.isArray(s) || (console.warn(
|
|
1111
1375
|
`Permission resolution returned non-array for client ${e}, using empty permissions`
|
|
1112
|
-
),
|
|
1113
|
-
(
|
|
1376
|
+
), s = []), s = s.filter(
|
|
1377
|
+
(r) => typeof r == "string" && r.trim().length > 0
|
|
1114
1378
|
);
|
|
1115
|
-
} catch (
|
|
1379
|
+
} catch (r) {
|
|
1116
1380
|
console.error(
|
|
1117
1381
|
`Unexpected error resolving permissions for client ${e}:`,
|
|
1118
|
-
|
|
1119
|
-
),
|
|
1382
|
+
r
|
|
1383
|
+
), s = [];
|
|
1120
1384
|
}
|
|
1121
|
-
return this.cache.set(e,
|
|
1385
|
+
return this.cache.set(e, s), s;
|
|
1122
1386
|
}
|
|
1123
1387
|
/**
|
|
1124
1388
|
* Invalidates cached permissions for a specific client.
|
|
@@ -1145,18 +1409,18 @@ p = new WeakSet(), /**
|
|
|
1145
1409
|
* @returns Array of toolset names from headers, or empty array if header is missing/malformed
|
|
1146
1410
|
* @private
|
|
1147
1411
|
*/
|
|
1148
|
-
|
|
1412
|
+
H = function(e) {
|
|
1149
1413
|
if (!e)
|
|
1150
1414
|
return [];
|
|
1151
|
-
const
|
|
1152
|
-
if (!
|
|
1415
|
+
const t = m(this, p, B).call(this, e, this.normalizedHeaderName);
|
|
1416
|
+
if (!t)
|
|
1153
1417
|
return [];
|
|
1154
1418
|
try {
|
|
1155
|
-
return
|
|
1156
|
-
} catch (
|
|
1419
|
+
return t.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1420
|
+
} catch (s) {
|
|
1157
1421
|
return console.warn(
|
|
1158
1422
|
`Failed to parse permission header '${this.normalizedHeaderName}':`,
|
|
1159
|
-
|
|
1423
|
+
s
|
|
1160
1424
|
), [];
|
|
1161
1425
|
}
|
|
1162
1426
|
}, /**
|
|
@@ -1167,12 +1431,12 @@ _ = function(e) {
|
|
|
1167
1431
|
* @returns The header value if found, undefined otherwise
|
|
1168
1432
|
* @private
|
|
1169
1433
|
*/
|
|
1170
|
-
|
|
1171
|
-
if (e[
|
|
1172
|
-
return e[
|
|
1173
|
-
for (const [
|
|
1174
|
-
if (
|
|
1175
|
-
return
|
|
1434
|
+
B = function(e, t) {
|
|
1435
|
+
if (e[t] !== void 0)
|
|
1436
|
+
return e[t];
|
|
1437
|
+
for (const [s, r] of Object.entries(e))
|
|
1438
|
+
if (s.toLowerCase() === t)
|
|
1439
|
+
return r;
|
|
1176
1440
|
}, /**
|
|
1177
1441
|
* Resolves permissions from server-side configuration.
|
|
1178
1442
|
* Tries resolver function first (if provided), then falls back to static map,
|
|
@@ -1181,16 +1445,16 @@ H = function(e, s) {
|
|
|
1181
1445
|
* @returns Array of toolset names from configuration
|
|
1182
1446
|
* @private
|
|
1183
1447
|
*/
|
|
1184
|
-
|
|
1448
|
+
Y = function(e) {
|
|
1185
1449
|
if (this.config.resolver) {
|
|
1186
|
-
const
|
|
1187
|
-
if (
|
|
1188
|
-
return
|
|
1450
|
+
const t = m(this, p, U).call(this, e);
|
|
1451
|
+
if (t !== null)
|
|
1452
|
+
return t;
|
|
1189
1453
|
}
|
|
1190
1454
|
if (this.config.staticMap) {
|
|
1191
|
-
const
|
|
1192
|
-
if (
|
|
1193
|
-
return
|
|
1455
|
+
const t = m(this, p, W).call(this, e);
|
|
1456
|
+
if (t !== null)
|
|
1457
|
+
return t;
|
|
1194
1458
|
}
|
|
1195
1459
|
return this.config.defaultPermissions || [];
|
|
1196
1460
|
}, /**
|
|
@@ -1200,16 +1464,16 @@ B = function(e) {
|
|
|
1200
1464
|
* @returns Array of toolset names if successful, null if resolver fails or returns invalid data
|
|
1201
1465
|
* @private
|
|
1202
1466
|
*/
|
|
1203
|
-
|
|
1467
|
+
U = function(e) {
|
|
1204
1468
|
try {
|
|
1205
|
-
const
|
|
1206
|
-
return Array.isArray(
|
|
1469
|
+
const t = this.config.resolver(e);
|
|
1470
|
+
return Array.isArray(t) ? t : (console.warn(
|
|
1207
1471
|
`Permission resolver returned non-array for client ${e}, using fallback`
|
|
1208
1472
|
), null);
|
|
1209
|
-
} catch (
|
|
1210
|
-
const
|
|
1473
|
+
} catch (t) {
|
|
1474
|
+
const s = t instanceof Error ? t.message : String(t);
|
|
1211
1475
|
return console.warn(
|
|
1212
|
-
`Permission resolver declined client ${e} (${
|
|
1476
|
+
`Permission resolver declined client ${e} (${s}), trying fallback`
|
|
1213
1477
|
), null;
|
|
1214
1478
|
}
|
|
1215
1479
|
}, /**
|
|
@@ -1220,36 +1484,36 @@ Y = function(e) {
|
|
|
1220
1484
|
* @private
|
|
1221
1485
|
*/
|
|
1222
1486
|
W = function(e) {
|
|
1223
|
-
const
|
|
1224
|
-
return
|
|
1487
|
+
const t = this.config.staticMap[e];
|
|
1488
|
+
return t !== void 0 ? Array.isArray(t) ? t : [] : null;
|
|
1225
1489
|
};
|
|
1226
|
-
function
|
|
1227
|
-
return async (
|
|
1228
|
-
const
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
),
|
|
1232
|
-
if (
|
|
1233
|
-
const
|
|
1234
|
-
for (const
|
|
1235
|
-
|
|
1236
|
-
`Failed to enable toolset '${
|
|
1490
|
+
function Pe(o, e) {
|
|
1491
|
+
return async (t) => {
|
|
1492
|
+
const s = e.resolvePermissions(
|
|
1493
|
+
t.clientId,
|
|
1494
|
+
t.headers
|
|
1495
|
+
), r = o(s), i = r.orchestrator.getManager(), n = [], l = [];
|
|
1496
|
+
if (s.length > 0) {
|
|
1497
|
+
const c = await i.enableToolsets(s);
|
|
1498
|
+
for (const a of c.results)
|
|
1499
|
+
a.success ? n.push(a.name) : (l.push(a.name), console.warn(
|
|
1500
|
+
`Failed to enable toolset '${a.name}' for client '${t.clientId}': ${a.message}`
|
|
1237
1501
|
));
|
|
1238
1502
|
if (n.length === 0 && l.length > 0)
|
|
1239
1503
|
throw new Error(
|
|
1240
|
-
`All requested toolsets failed to enable for client '${
|
|
1504
|
+
`All requested toolsets failed to enable for client '${t.clientId}'. Requested: [${s.join(", ")}]. Check that toolset names in permissions match the catalog.`
|
|
1241
1505
|
);
|
|
1242
1506
|
}
|
|
1243
1507
|
return {
|
|
1244
|
-
server:
|
|
1245
|
-
orchestrator:
|
|
1508
|
+
server: r.server,
|
|
1509
|
+
orchestrator: r.orchestrator,
|
|
1246
1510
|
allowedToolsets: n,
|
|
1247
1511
|
failedToolsets: l
|
|
1248
1512
|
};
|
|
1249
1513
|
};
|
|
1250
1514
|
}
|
|
1251
|
-
var
|
|
1252
|
-
class
|
|
1515
|
+
var f, J, Q, G, Z, X, ee, te, se, M, oe;
|
|
1516
|
+
class Me {
|
|
1253
1517
|
/**
|
|
1254
1518
|
* Creates a new PermissionAwareFastifyTransport instance.
|
|
1255
1519
|
* @param defaultManager - Default tool manager for status endpoints
|
|
@@ -1257,28 +1521,28 @@ class ve {
|
|
|
1257
1521
|
* @param options - Transport configuration options
|
|
1258
1522
|
* @param configSchema - Optional JSON schema for configuration discovery
|
|
1259
1523
|
*/
|
|
1260
|
-
constructor(e,
|
|
1261
|
-
|
|
1524
|
+
constructor(e, t, s = {}, r) {
|
|
1525
|
+
A(this, f);
|
|
1262
1526
|
d(this, "options");
|
|
1263
1527
|
d(this, "defaultManager");
|
|
1264
1528
|
d(this, "createPermissionAwareBundle");
|
|
1265
1529
|
d(this, "app", null);
|
|
1266
1530
|
d(this, "configSchema");
|
|
1267
1531
|
// Per-client server bundles and per-client session transports
|
|
1268
|
-
d(this, "clientCache", new
|
|
1269
|
-
onEvict: (e,
|
|
1270
|
-
m(this,
|
|
1532
|
+
d(this, "clientCache", new F({
|
|
1533
|
+
onEvict: (e, t) => {
|
|
1534
|
+
m(this, f, J).call(this, t);
|
|
1271
1535
|
}
|
|
1272
1536
|
}));
|
|
1273
|
-
this.defaultManager = e, this.createPermissionAwareBundle =
|
|
1274
|
-
host:
|
|
1275
|
-
port:
|
|
1276
|
-
basePath:
|
|
1277
|
-
cors:
|
|
1278
|
-
logger:
|
|
1279
|
-
app:
|
|
1280
|
-
customEndpoints:
|
|
1281
|
-
}, this.configSchema =
|
|
1537
|
+
this.defaultManager = e, this.createPermissionAwareBundle = t, this.options = {
|
|
1538
|
+
host: s.host ?? "0.0.0.0",
|
|
1539
|
+
port: s.port ?? 3e3,
|
|
1540
|
+
basePath: s.basePath ?? "/",
|
|
1541
|
+
cors: s.cors ?? !0,
|
|
1542
|
+
logger: s.logger ?? !1,
|
|
1543
|
+
app: s.app,
|
|
1544
|
+
customEndpoints: s.customEndpoints
|
|
1545
|
+
}, this.configSchema = r;
|
|
1282
1546
|
}
|
|
1283
1547
|
/**
|
|
1284
1548
|
* Starts the Fastify server and registers all MCP endpoints.
|
|
@@ -1286,14 +1550,14 @@ class ve {
|
|
|
1286
1550
|
*/
|
|
1287
1551
|
async start() {
|
|
1288
1552
|
if (this.app) return;
|
|
1289
|
-
const e = this.options.app ??
|
|
1290
|
-
this.options.cors && await e.register(
|
|
1291
|
-
const
|
|
1292
|
-
m(this,
|
|
1293
|
-
contextExtractor: async (
|
|
1294
|
-
const
|
|
1553
|
+
const e = this.options.app ?? N({ logger: this.options.logger });
|
|
1554
|
+
this.options.cors && await e.register(D, { origin: !0 });
|
|
1555
|
+
const t = m(this, f, Q).call(this, this.options.basePath);
|
|
1556
|
+
m(this, f, G).call(this, e, t), m(this, f, Z).call(this, e, t), m(this, f, X).call(this, e, t), m(this, f, ee).call(this, e, t), m(this, f, te).call(this, e, t), m(this, f, se).call(this, e, t), this.options.customEndpoints && this.options.customEndpoints.length > 0 && V(e, t, this.options.customEndpoints, {
|
|
1557
|
+
contextExtractor: async (s) => {
|
|
1558
|
+
const r = m(this, f, M).call(this, s);
|
|
1295
1559
|
try {
|
|
1296
|
-
const i = await this.createPermissionAwareBundle(
|
|
1560
|
+
const i = await this.createPermissionAwareBundle(r);
|
|
1297
1561
|
return {
|
|
1298
1562
|
allowedToolsets: i.allowedToolsets,
|
|
1299
1563
|
failedToolsets: i.failedToolsets
|
|
@@ -1317,20 +1581,20 @@ class ve {
|
|
|
1317
1581
|
this.app && (this.clientCache.stop(!0), this.options.app || await this.app.close(), this.app = null);
|
|
1318
1582
|
}
|
|
1319
1583
|
}
|
|
1320
|
-
|
|
1584
|
+
f = new WeakSet(), /**
|
|
1321
1585
|
* Cleans up resources associated with a client bundle.
|
|
1322
1586
|
* Closes all sessions within the bundle.
|
|
1323
1587
|
* @param bundle - The client bundle to clean up
|
|
1324
1588
|
* @private
|
|
1325
1589
|
*/
|
|
1326
|
-
|
|
1327
|
-
for (const [
|
|
1590
|
+
J = function(e) {
|
|
1591
|
+
for (const [t, s] of e.sessions.entries())
|
|
1328
1592
|
try {
|
|
1329
|
-
typeof
|
|
1330
|
-
console.warn(`Error closing session ${
|
|
1593
|
+
typeof s.close == "function" && s.close().catch((r) => {
|
|
1594
|
+
console.warn(`Error closing session ${t}:`, r);
|
|
1331
1595
|
});
|
|
1332
|
-
} catch (
|
|
1333
|
-
console.warn(`Error closing session ${
|
|
1596
|
+
} catch (r) {
|
|
1597
|
+
console.warn(`Error closing session ${t}:`, r);
|
|
1334
1598
|
}
|
|
1335
1599
|
e.sessions.clear();
|
|
1336
1600
|
}, /**
|
|
@@ -1339,7 +1603,7 @@ U = function(e) {
|
|
|
1339
1603
|
* @returns Normalized base path without trailing slash
|
|
1340
1604
|
* @private
|
|
1341
1605
|
*/
|
|
1342
|
-
|
|
1606
|
+
Q = function(e) {
|
|
1343
1607
|
return e.endsWith("/") ? e.slice(0, -1) : e;
|
|
1344
1608
|
}, /**
|
|
1345
1609
|
* Registers the health check endpoint.
|
|
@@ -1347,24 +1611,24 @@ q = function(e) {
|
|
|
1347
1611
|
* @param base - Base path for routes
|
|
1348
1612
|
* @private
|
|
1349
1613
|
*/
|
|
1350
|
-
|
|
1351
|
-
e.get(`${
|
|
1614
|
+
G = function(e, t) {
|
|
1615
|
+
e.get(`${t}/healthz`, async () => ({ ok: !0 }));
|
|
1352
1616
|
}, /**
|
|
1353
1617
|
* Registers the tools status endpoint.
|
|
1354
1618
|
* @param app - Fastify instance
|
|
1355
1619
|
* @param base - Base path for routes
|
|
1356
1620
|
* @private
|
|
1357
1621
|
*/
|
|
1358
|
-
|
|
1359
|
-
e.get(`${
|
|
1622
|
+
Z = function(e, t) {
|
|
1623
|
+
e.get(`${t}/tools`, async () => this.defaultManager.getStatus());
|
|
1360
1624
|
}, /**
|
|
1361
1625
|
* Registers the MCP configuration discovery endpoint.
|
|
1362
1626
|
* @param app - Fastify instance
|
|
1363
1627
|
* @param base - Base path for routes
|
|
1364
1628
|
* @private
|
|
1365
1629
|
*/
|
|
1366
|
-
|
|
1367
|
-
e.get(`${
|
|
1630
|
+
X = function(e, t) {
|
|
1631
|
+
e.get(`${t}/.well-known/mcp-config`, async (s, r) => (r.header("Content-Type", "application/schema+json; charset=utf-8"), this.configSchema ?? {
|
|
1368
1632
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1369
1633
|
title: "MCP Session Configuration",
|
|
1370
1634
|
description: "Schema for the /mcp endpoint configuration",
|
|
@@ -1381,11 +1645,11 @@ K = function(e, s) {
|
|
|
1381
1645
|
* @param base - Base path for routes
|
|
1382
1646
|
* @private
|
|
1383
1647
|
*/
|
|
1384
|
-
|
|
1648
|
+
ee = function(e, t) {
|
|
1385
1649
|
e.post(
|
|
1386
|
-
`${
|
|
1387
|
-
async (
|
|
1388
|
-
const i = m(this,
|
|
1650
|
+
`${t}/mcp`,
|
|
1651
|
+
async (s, r) => {
|
|
1652
|
+
const i = m(this, f, M).call(this, s), n = !i.clientId.startsWith("anon-");
|
|
1389
1653
|
let l = n ? this.clientCache.get(i.clientId) : null;
|
|
1390
1654
|
if (!l)
|
|
1391
1655
|
try {
|
|
@@ -1393,55 +1657,55 @@ Z = function(e, s) {
|
|
|
1393
1657
|
u.failedToolsets.length > 0 && console.warn(
|
|
1394
1658
|
`Client ${i.clientId} had ${u.failedToolsets.length} toolsets fail to enable: [${u.failedToolsets.join(", ")}]. Successfully enabled: [${u.allowedToolsets.join(", ")}]`
|
|
1395
1659
|
);
|
|
1396
|
-
const
|
|
1660
|
+
const h = u.sessions;
|
|
1397
1661
|
l = {
|
|
1398
1662
|
server: u.server,
|
|
1399
1663
|
orchestrator: u.orchestrator,
|
|
1400
1664
|
allowedToolsets: u.allowedToolsets,
|
|
1401
1665
|
failedToolsets: u.failedToolsets,
|
|
1402
|
-
sessions:
|
|
1666
|
+
sessions: h instanceof Map ? h : /* @__PURE__ */ new Map()
|
|
1403
1667
|
}, n && this.clientCache.set(i.clientId, l);
|
|
1404
1668
|
} catch (u) {
|
|
1405
1669
|
return console.error(
|
|
1406
1670
|
`Failed to create permission-aware bundle for client ${i.clientId}:`,
|
|
1407
1671
|
u
|
|
1408
|
-
),
|
|
1672
|
+
), r.code(403), m(this, f, oe).call(this, "Access denied");
|
|
1409
1673
|
}
|
|
1410
|
-
const
|
|
1411
|
-
let
|
|
1412
|
-
if (
|
|
1413
|
-
|
|
1414
|
-
else if (!
|
|
1415
|
-
const u =
|
|
1416
|
-
|
|
1674
|
+
const c = s.headers["mcp-session-id"];
|
|
1675
|
+
let a;
|
|
1676
|
+
if (c && l.sessions.get(c))
|
|
1677
|
+
a = l.sessions.get(c);
|
|
1678
|
+
else if (!c && _(s.body)) {
|
|
1679
|
+
const u = S();
|
|
1680
|
+
a = new z({
|
|
1417
1681
|
sessionIdGenerator: () => u,
|
|
1418
|
-
onsessioninitialized: (
|
|
1419
|
-
l.sessions.set(
|
|
1682
|
+
onsessioninitialized: (h) => {
|
|
1683
|
+
l.sessions.set(h, a);
|
|
1420
1684
|
}
|
|
1421
1685
|
});
|
|
1422
1686
|
try {
|
|
1423
|
-
await l.server.connect(
|
|
1687
|
+
await l.server.connect(a);
|
|
1424
1688
|
} catch {
|
|
1425
|
-
return
|
|
1689
|
+
return r.code(500), {
|
|
1426
1690
|
jsonrpc: "2.0",
|
|
1427
1691
|
error: { code: -32603, message: "Error initializing server." },
|
|
1428
1692
|
id: null
|
|
1429
1693
|
};
|
|
1430
1694
|
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1695
|
+
a.onclose = () => {
|
|
1696
|
+
a?.sessionId && l.sessions.delete(a.sessionId);
|
|
1433
1697
|
};
|
|
1434
1698
|
} else
|
|
1435
|
-
return
|
|
1699
|
+
return r.code(400), {
|
|
1436
1700
|
jsonrpc: "2.0",
|
|
1437
1701
|
error: { code: -32e3, message: "Session not found or expired" },
|
|
1438
1702
|
id: null
|
|
1439
1703
|
};
|
|
1440
|
-
return await
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
),
|
|
1704
|
+
return await a.handleRequest(
|
|
1705
|
+
s.raw,
|
|
1706
|
+
r.raw,
|
|
1707
|
+
s.body
|
|
1708
|
+
), r;
|
|
1445
1709
|
}
|
|
1446
1710
|
);
|
|
1447
1711
|
}, /**
|
|
@@ -1450,19 +1714,19 @@ Z = function(e, s) {
|
|
|
1450
1714
|
* @param base - Base path for routes
|
|
1451
1715
|
* @private
|
|
1452
1716
|
*/
|
|
1453
|
-
|
|
1454
|
-
e.get(`${
|
|
1455
|
-
const i =
|
|
1717
|
+
te = function(e, t) {
|
|
1718
|
+
e.get(`${t}/mcp`, async (s, r) => {
|
|
1719
|
+
const i = s.headers["mcp-client-id"]?.trim(), n = i && i.length > 0 ? i : "";
|
|
1456
1720
|
if (!n)
|
|
1457
|
-
return
|
|
1721
|
+
return r.code(400), "Missing mcp-client-id";
|
|
1458
1722
|
const l = this.clientCache.get(n);
|
|
1459
1723
|
if (!l)
|
|
1460
|
-
return
|
|
1461
|
-
const
|
|
1462
|
-
if (!
|
|
1463
|
-
return
|
|
1464
|
-
const
|
|
1465
|
-
return
|
|
1724
|
+
return r.code(400), "Invalid or expired client";
|
|
1725
|
+
const c = s.headers["mcp-session-id"];
|
|
1726
|
+
if (!c)
|
|
1727
|
+
return r.code(400), "Missing mcp-session-id";
|
|
1728
|
+
const a = l.sessions.get(c);
|
|
1729
|
+
return a ? (await a.handleRequest(s.raw, r.raw), r) : (r.code(400), "Invalid or expired session ID");
|
|
1466
1730
|
});
|
|
1467
1731
|
}, /**
|
|
1468
1732
|
* Registers the DELETE /mcp endpoint for session termination.
|
|
@@ -1470,13 +1734,13 @@ Q = function(e, s) {
|
|
|
1470
1734
|
* @param base - Base path for routes
|
|
1471
1735
|
* @private
|
|
1472
1736
|
*/
|
|
1473
|
-
|
|
1737
|
+
se = function(e, t) {
|
|
1474
1738
|
e.delete(
|
|
1475
|
-
`${
|
|
1476
|
-
async (
|
|
1477
|
-
const i =
|
|
1739
|
+
`${t}/mcp`,
|
|
1740
|
+
async (s, r) => {
|
|
1741
|
+
const i = s.headers["mcp-client-id"]?.trim(), n = i && i.length > 0 ? i : "", l = s.headers["mcp-session-id"];
|
|
1478
1742
|
if (!n || !l)
|
|
1479
|
-
return
|
|
1743
|
+
return r.code(400), {
|
|
1480
1744
|
jsonrpc: "2.0",
|
|
1481
1745
|
error: {
|
|
1482
1746
|
code: -32600,
|
|
@@ -1484,23 +1748,23 @@ X = function(e, s) {
|
|
|
1484
1748
|
},
|
|
1485
1749
|
id: null
|
|
1486
1750
|
};
|
|
1487
|
-
const
|
|
1488
|
-
if (!
|
|
1489
|
-
return
|
|
1751
|
+
const c = this.clientCache.get(n), a = c?.sessions.get(l);
|
|
1752
|
+
if (!c || !a)
|
|
1753
|
+
return r.code(404), {
|
|
1490
1754
|
jsonrpc: "2.0",
|
|
1491
1755
|
error: { code: -32e3, message: "Session not found or expired" },
|
|
1492
1756
|
id: null
|
|
1493
1757
|
};
|
|
1494
1758
|
try {
|
|
1495
|
-
if (typeof
|
|
1759
|
+
if (typeof a.close == "function")
|
|
1496
1760
|
try {
|
|
1497
|
-
await
|
|
1761
|
+
await a.close();
|
|
1498
1762
|
} catch {
|
|
1499
1763
|
}
|
|
1500
1764
|
} finally {
|
|
1501
|
-
|
|
1765
|
+
a?.sessionId ? c.sessions.delete(a.sessionId) : c.sessions.delete(l);
|
|
1502
1766
|
}
|
|
1503
|
-
return
|
|
1767
|
+
return r.code(204).send(), r;
|
|
1504
1768
|
}
|
|
1505
1769
|
);
|
|
1506
1770
|
}, /**
|
|
@@ -1510,11 +1774,11 @@ X = function(e, s) {
|
|
|
1510
1774
|
* @returns Client request context with ID and headers
|
|
1511
1775
|
* @private
|
|
1512
1776
|
*/
|
|
1513
|
-
|
|
1514
|
-
const
|
|
1777
|
+
M = function(e) {
|
|
1778
|
+
const t = e.headers["mcp-client-id"]?.trim(), s = t && t.length > 0 ? t : `anon-${S()}`, r = {};
|
|
1515
1779
|
for (const [i, n] of Object.entries(e.headers))
|
|
1516
|
-
typeof n == "string" && (
|
|
1517
|
-
return { clientId:
|
|
1780
|
+
typeof n == "string" && (r[i] = n);
|
|
1781
|
+
return { clientId: s, headers: r };
|
|
1518
1782
|
}, /**
|
|
1519
1783
|
* Creates a safe error response that doesn't expose unauthorized toolset information.
|
|
1520
1784
|
* Used for permission-related errors to prevent information leakage.
|
|
@@ -1523,64 +1787,66 @@ C = function(e) {
|
|
|
1523
1787
|
* @returns JSON-RPC error response object
|
|
1524
1788
|
* @private
|
|
1525
1789
|
*/
|
|
1526
|
-
|
|
1790
|
+
oe = function(e = "Access denied", t = -32e3) {
|
|
1527
1791
|
return {
|
|
1528
1792
|
jsonrpc: "2.0",
|
|
1529
1793
|
error: {
|
|
1530
|
-
code:
|
|
1794
|
+
code: t,
|
|
1531
1795
|
message: e
|
|
1532
1796
|
},
|
|
1533
1797
|
id: null
|
|
1534
1798
|
};
|
|
1535
1799
|
};
|
|
1536
|
-
function
|
|
1537
|
-
if (!
|
|
1800
|
+
function Ie(o) {
|
|
1801
|
+
if (!o) return;
|
|
1538
1802
|
const e = {
|
|
1539
|
-
namespaceToolsWithSetKey:
|
|
1803
|
+
namespaceToolsWithSetKey: o.namespaceToolsWithSetKey
|
|
1540
1804
|
};
|
|
1541
|
-
return
|
|
1805
|
+
return o.allowlist !== void 0 && console.warn(
|
|
1542
1806
|
"Permission-based servers: exposurePolicy.allowlist is ignored. Allowed toolsets are determined by client permissions."
|
|
1543
|
-
),
|
|
1807
|
+
), o.denylist !== void 0 && console.warn(
|
|
1544
1808
|
"Permission-based servers: exposurePolicy.denylist is ignored. Use permission configuration to control toolset access."
|
|
1545
|
-
),
|
|
1809
|
+
), o.maxActiveToolsets !== void 0 && console.warn(
|
|
1546
1810
|
"Permission-based servers: exposurePolicy.maxActiveToolsets is ignored. Toolset count is determined by client permissions."
|
|
1547
|
-
),
|
|
1811
|
+
), o.onLimitExceeded !== void 0 && console.warn(
|
|
1548
1812
|
"Permission-based servers: exposurePolicy.onLimitExceeded is ignored. No toolset limits are enforced."
|
|
1549
1813
|
), e;
|
|
1550
1814
|
}
|
|
1551
|
-
async function
|
|
1552
|
-
if (!
|
|
1815
|
+
async function ze(o) {
|
|
1816
|
+
if (!o.permissions)
|
|
1553
1817
|
throw new Error(
|
|
1554
1818
|
"Permission configuration is required for createPermissionBasedMcpServer. Please provide a 'permissions' field in the options."
|
|
1555
1819
|
);
|
|
1556
|
-
if (
|
|
1820
|
+
if (Te(o.permissions), o.sessionContext && (K(o.sessionContext), console.warn(
|
|
1821
|
+
"Session context support for permission-based servers is limited. The base context will be used for module loaders."
|
|
1822
|
+
)), o.startup)
|
|
1557
1823
|
throw new Error(
|
|
1558
1824
|
"Permission-based servers determine toolsets from client permissions. The 'startup' option is not allowed. Remove it from your configuration."
|
|
1559
1825
|
);
|
|
1560
|
-
if (typeof
|
|
1826
|
+
if (typeof o.createServer != "function")
|
|
1561
1827
|
throw new Error(
|
|
1562
1828
|
"createPermissionBasedMcpServer: `createServer` (factory) is required"
|
|
1563
1829
|
);
|
|
1564
|
-
const e =
|
|
1565
|
-
|
|
1566
|
-
),
|
|
1567
|
-
server:
|
|
1568
|
-
catalog:
|
|
1569
|
-
moduleLoaders:
|
|
1830
|
+
const e = Ie(
|
|
1831
|
+
o.exposurePolicy
|
|
1832
|
+
), t = new Ee(o.permissions), s = o.createServer(), r = new C({
|
|
1833
|
+
server: s,
|
|
1834
|
+
catalog: o.catalog,
|
|
1835
|
+
moduleLoaders: o.moduleLoaders,
|
|
1570
1836
|
exposurePolicy: e,
|
|
1571
|
-
context:
|
|
1837
|
+
context: o.context,
|
|
1572
1838
|
notifyToolsListChanged: void 0,
|
|
1573
1839
|
// No notifications in STATIC mode
|
|
1574
1840
|
startup: { mode: "STATIC", toolsets: [] },
|
|
1575
1841
|
registerMetaTools: !1
|
|
1576
|
-
}), i =
|
|
1842
|
+
}), i = Pe(
|
|
1577
1843
|
(l) => {
|
|
1578
|
-
const
|
|
1579
|
-
server:
|
|
1580
|
-
catalog:
|
|
1581
|
-
moduleLoaders:
|
|
1844
|
+
const c = o.createServer(), a = new C({
|
|
1845
|
+
server: c,
|
|
1846
|
+
catalog: o.catalog,
|
|
1847
|
+
moduleLoaders: o.moduleLoaders,
|
|
1582
1848
|
exposurePolicy: e,
|
|
1583
|
-
context:
|
|
1849
|
+
context: o.context,
|
|
1584
1850
|
notifyToolsListChanged: void 0,
|
|
1585
1851
|
// No notifications in STATIC mode
|
|
1586
1852
|
startup: { mode: "STATIC", toolsets: [] },
|
|
@@ -1588,17 +1854,17 @@ async function Me(r) {
|
|
|
1588
1854
|
registerMetaTools: !1
|
|
1589
1855
|
// No meta-tools - toolsets are fixed per client
|
|
1590
1856
|
});
|
|
1591
|
-
return { server:
|
|
1857
|
+
return { server: c, orchestrator: a };
|
|
1592
1858
|
},
|
|
1593
|
-
|
|
1594
|
-
), n = new
|
|
1595
|
-
|
|
1859
|
+
t
|
|
1860
|
+
), n = new Me(
|
|
1861
|
+
r.getManager(),
|
|
1596
1862
|
i,
|
|
1597
|
-
|
|
1598
|
-
|
|
1863
|
+
o.http,
|
|
1864
|
+
o.configSchema
|
|
1599
1865
|
);
|
|
1600
1866
|
return {
|
|
1601
|
-
server:
|
|
1867
|
+
server: s,
|
|
1602
1868
|
start: async () => {
|
|
1603
1869
|
await n.start();
|
|
1604
1870
|
},
|
|
@@ -1606,21 +1872,22 @@ async function Me(r) {
|
|
|
1606
1872
|
try {
|
|
1607
1873
|
await n.stop();
|
|
1608
1874
|
} finally {
|
|
1609
|
-
|
|
1875
|
+
t.clearCache();
|
|
1610
1876
|
}
|
|
1611
1877
|
}
|
|
1612
1878
|
};
|
|
1613
1879
|
}
|
|
1614
|
-
function
|
|
1615
|
-
return
|
|
1880
|
+
function _e(o) {
|
|
1881
|
+
return o;
|
|
1616
1882
|
}
|
|
1617
|
-
function
|
|
1618
|
-
return
|
|
1883
|
+
function qe(o) {
|
|
1884
|
+
return o;
|
|
1619
1885
|
}
|
|
1620
1886
|
export {
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1887
|
+
fe as SessionContextResolver,
|
|
1888
|
+
De as createMcpServer,
|
|
1889
|
+
ze as createPermissionBasedMcpServer,
|
|
1890
|
+
_e as defineEndpoint,
|
|
1891
|
+
qe as definePermissionAwareEndpoint
|
|
1625
1892
|
};
|
|
1626
1893
|
//# sourceMappingURL=index.js.map
|