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