toolception 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -1
- package/dist/http/FastifyTransport.d.ts +6 -0
- package/dist/http/FastifyTransport.d.ts.map +1 -1
- package/dist/http/customEndpoints.d.ts +247 -0
- package/dist/http/customEndpoints.d.ts.map +1 -0
- package/dist/http/endpointRegistration.d.ts +35 -0
- package/dist/http/endpointRegistration.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +238 -121
- package/dist/index.js.map +1 -1
- package/dist/permissions/PermissionAwareFastifyTransport.d.ts +7 -0
- package/dist/permissions/PermissionAwareFastifyTransport.d.ts.map +1 -1
- package/dist/server/createMcpServer.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
var
|
|
1
|
+
var P = (r) => {
|
|
2
2
|
throw TypeError(r);
|
|
3
3
|
};
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
import { z as
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import { randomUUID as
|
|
11
|
-
import { StreamableHTTPServerTransport as
|
|
12
|
-
import { isInitializeRequest as
|
|
13
|
-
const
|
|
4
|
+
var ee = (r, e, s) => e.has(r) || P("Cannot " + s);
|
|
5
|
+
var T = (r, e, s) => e.has(r) ? P("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(r) : e.set(r, s);
|
|
6
|
+
var f = (r, e, s) => (ee(r, e, "access private method"), s);
|
|
7
|
+
import { z as b } from "zod";
|
|
8
|
+
import N from "fastify";
|
|
9
|
+
import j from "@fastify/cors";
|
|
10
|
+
import { randomUUID as y } from "node:crypto";
|
|
11
|
+
import { StreamableHTTPServerTransport as k } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
12
|
+
import { isInitializeRequest as D } from "@modelcontextprotocol/sdk/types.js";
|
|
13
|
+
const L = {
|
|
14
14
|
dynamic: [
|
|
15
15
|
"dynamic-tool-discovery",
|
|
16
16
|
"dynamicToolDiscovery",
|
|
@@ -18,11 +18,11 @@ const S = {
|
|
|
18
18
|
],
|
|
19
19
|
toolsets: ["tool-sets", "toolSets", "FMP_TOOL_SETS"]
|
|
20
20
|
};
|
|
21
|
-
class
|
|
21
|
+
class se {
|
|
22
22
|
constructor(e = {}) {
|
|
23
23
|
this.keys = {
|
|
24
|
-
dynamic: e.keys?.dynamic ??
|
|
25
|
-
toolsets: e.keys?.toolsets ??
|
|
24
|
+
dynamic: e.keys?.dynamic ?? L.dynamic,
|
|
25
|
+
toolsets: e.keys?.toolsets ?? L.toolsets
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
resolveMode(e, s) {
|
|
@@ -109,7 +109,7 @@ class q {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
class
|
|
112
|
+
class te {
|
|
113
113
|
constructor(e) {
|
|
114
114
|
this.catalog = e.catalog, this.moduleLoaders = e.moduleLoaders ?? {};
|
|
115
115
|
}
|
|
@@ -162,12 +162,12 @@ class J {
|
|
|
162
162
|
return t;
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
-
class
|
|
165
|
+
class R extends Error {
|
|
166
166
|
constructor(e, s, t, o) {
|
|
167
167
|
super(e), this.name = "ToolingError", this.code = s, this.details = t;
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
|
-
class
|
|
170
|
+
class z {
|
|
171
171
|
constructor(e = {}) {
|
|
172
172
|
this.names = /* @__PURE__ */ new Set(), this.toolsetToNames = /* @__PURE__ */ new Map(), this.options = {
|
|
173
173
|
namespaceWithToolset: e.namespaceWithToolset ?? !0
|
|
@@ -181,7 +181,7 @@ class P {
|
|
|
181
181
|
}
|
|
182
182
|
add(e) {
|
|
183
183
|
if (this.names.has(e))
|
|
184
|
-
throw new
|
|
184
|
+
throw new R(
|
|
185
185
|
`Tool name collision: '${e}' already registered`,
|
|
186
186
|
"E_TOOL_NAME_CONFLICT"
|
|
187
187
|
);
|
|
@@ -196,7 +196,7 @@ class P {
|
|
|
196
196
|
return s.map((t) => {
|
|
197
197
|
const o = this.getSafeName(e, t.name);
|
|
198
198
|
if (this.has(o))
|
|
199
|
-
throw new
|
|
199
|
+
throw new R(
|
|
200
200
|
`Tool name collision for '${o}'`,
|
|
201
201
|
"E_TOOL_NAME_CONFLICT"
|
|
202
202
|
);
|
|
@@ -213,9 +213,9 @@ class P {
|
|
|
213
213
|
return e;
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
|
-
class
|
|
216
|
+
class oe {
|
|
217
217
|
constructor(e) {
|
|
218
|
-
this.activeToolsets = /* @__PURE__ */ new Set(), this.server = e.server, this.resolver = e.resolver, this.context = e.context, this.onToolsListChanged = e.onToolsListChanged, this.exposurePolicy = e.exposurePolicy, this.toolRegistry = e.toolRegistry ?? new
|
|
218
|
+
this.activeToolsets = /* @__PURE__ */ new Set(), this.server = e.server, this.resolver = e.resolver, this.context = e.context, this.onToolsListChanged = e.onToolsListChanged, this.exposurePolicy = e.exposurePolicy, this.toolRegistry = e.toolRegistry ?? new z({ namespaceWithToolset: !0 });
|
|
219
219
|
}
|
|
220
220
|
/**
|
|
221
221
|
* Sends a tool list change notification if configured.
|
|
@@ -395,11 +395,11 @@ class G {
|
|
|
395
395
|
return this.enableToolsets(e);
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
|
-
function
|
|
398
|
+
function re(r, e, s) {
|
|
399
399
|
(s?.mode ?? "DYNAMIC") === "DYNAMIC" && (r.tool(
|
|
400
400
|
"enable_toolset",
|
|
401
401
|
"Enable a toolset by name",
|
|
402
|
-
{ name:
|
|
402
|
+
{ name: b.string().describe("Toolset name") },
|
|
403
403
|
async (o) => {
|
|
404
404
|
const i = await e.enableToolset(o.name);
|
|
405
405
|
return {
|
|
@@ -409,7 +409,7 @@ function K(r, e, s) {
|
|
|
409
409
|
), r.tool(
|
|
410
410
|
"disable_toolset",
|
|
411
411
|
"Disable a toolset by name (state only)",
|
|
412
|
-
{ name:
|
|
412
|
+
{ name: b.string().describe("Toolset name") },
|
|
413
413
|
async (o) => {
|
|
414
414
|
const i = await e.disableToolset(o.name);
|
|
415
415
|
return {
|
|
@@ -444,7 +444,7 @@ function K(r, e, s) {
|
|
|
444
444
|
), r.tool(
|
|
445
445
|
"describe_toolset",
|
|
446
446
|
"Describe a toolset with definition, active status and tools",
|
|
447
|
-
{ name:
|
|
447
|
+
{ name: b.string().describe("Toolset name") },
|
|
448
448
|
async (o) => {
|
|
449
449
|
const i = e.getToolsetDefinition(o.name), n = e.getStatus().toolsetToTools;
|
|
450
450
|
if (!i)
|
|
@@ -486,25 +486,25 @@ function K(r, e, s) {
|
|
|
486
486
|
}
|
|
487
487
|
);
|
|
488
488
|
}
|
|
489
|
-
class
|
|
489
|
+
class w {
|
|
490
490
|
constructor(e) {
|
|
491
|
-
this.initError = null, this.toolsetValidator = new
|
|
491
|
+
this.initError = null, this.toolsetValidator = new se();
|
|
492
492
|
const s = e.startup ?? {}, t = this.resolveStartupConfig(s, e.catalog);
|
|
493
|
-
this.mode = t.mode, this.resolver = new
|
|
493
|
+
this.mode = t.mode, this.resolver = new te({
|
|
494
494
|
catalog: e.catalog,
|
|
495
495
|
moduleLoaders: e.moduleLoaders
|
|
496
496
|
});
|
|
497
|
-
const o = new
|
|
497
|
+
const o = new z({
|
|
498
498
|
namespaceWithToolset: e.exposurePolicy?.namespaceToolsWithSetKey ?? !0
|
|
499
499
|
});
|
|
500
|
-
this.manager = new
|
|
500
|
+
this.manager = new oe({
|
|
501
501
|
server: e.server,
|
|
502
502
|
resolver: this.resolver,
|
|
503
503
|
context: e.context,
|
|
504
504
|
onToolsListChanged: e.notifyToolsListChanged,
|
|
505
505
|
exposurePolicy: e.exposurePolicy,
|
|
506
506
|
toolRegistry: o
|
|
507
|
-
}), e.registerMetaTools !== !1 &&
|
|
507
|
+
}), e.registerMetaTools !== !1 && re(e.server, this.manager, { mode: this.mode });
|
|
508
508
|
const i = t.toolsets;
|
|
509
509
|
this.initPromise = this.initializeToolsets(i);
|
|
510
510
|
}
|
|
@@ -581,10 +581,10 @@ class T {
|
|
|
581
581
|
return this.manager;
|
|
582
582
|
}
|
|
583
583
|
}
|
|
584
|
-
var
|
|
585
|
-
class
|
|
584
|
+
var v, S;
|
|
585
|
+
class O {
|
|
586
586
|
constructor(e = {}) {
|
|
587
|
-
|
|
587
|
+
T(this, v);
|
|
588
588
|
this.storage = /* @__PURE__ */ new Map(), this.maxSize = e.maxSize ?? 1e3, this.ttlMs = e.ttlMs ?? 1e3 * 60 * 60, this.onEvict = e.onEvict;
|
|
589
589
|
const s = e.pruneIntervalMs ?? 1e3 * 60 * 10;
|
|
590
590
|
this.pruneInterval = setInterval(() => this.pruneExpired(), s);
|
|
@@ -614,7 +614,7 @@ class $ {
|
|
|
614
614
|
*/
|
|
615
615
|
delete(e) {
|
|
616
616
|
const s = this.storage.get(e);
|
|
617
|
-
s && (this.storage.delete(e),
|
|
617
|
+
s && (this.storage.delete(e), f(this, v, S).call(this, e, s.resource));
|
|
618
618
|
}
|
|
619
619
|
/**
|
|
620
620
|
* Stops the background pruning interval and optionally clears all entries.
|
|
@@ -631,7 +631,7 @@ class $ {
|
|
|
631
631
|
const e = Array.from(this.storage.entries());
|
|
632
632
|
this.storage.clear();
|
|
633
633
|
for (const [s, t] of e)
|
|
634
|
-
|
|
634
|
+
f(this, v, S).call(this, s, t.resource);
|
|
635
635
|
}
|
|
636
636
|
/**
|
|
637
637
|
* Evicts the least recently used entry from the cache.
|
|
@@ -653,13 +653,13 @@ class $ {
|
|
|
653
653
|
this.delete(t);
|
|
654
654
|
}
|
|
655
655
|
}
|
|
656
|
-
|
|
656
|
+
v = new WeakSet(), /**
|
|
657
657
|
* Safely calls the evict callback, catching and logging any errors.
|
|
658
658
|
* @param key - The key being evicted
|
|
659
659
|
* @param resource - The resource being evicted
|
|
660
660
|
* @private
|
|
661
661
|
*/
|
|
662
|
-
|
|
662
|
+
S = function(e, s) {
|
|
663
663
|
if (this.onEvict)
|
|
664
664
|
try {
|
|
665
665
|
const t = this.onEvict(e, s);
|
|
@@ -670,9 +670,94 @@ b = function(e, s) {
|
|
|
670
670
|
console.warn(`Error in cache eviction callback for key '${e}':`, t);
|
|
671
671
|
}
|
|
672
672
|
};
|
|
673
|
-
|
|
673
|
+
function V(r, e, s, t) {
|
|
674
|
+
const o = ["/mcp", "/healthz", "/tools", "/.well-known/mcp-config"];
|
|
675
|
+
for (const i of s) {
|
|
676
|
+
const n = `${e}${i.path}`;
|
|
677
|
+
if (o.some(
|
|
678
|
+
(c) => n.startsWith(`${e}${c}`)
|
|
679
|
+
)) {
|
|
680
|
+
console.warn(
|
|
681
|
+
`Custom endpoint ${i.method} ${i.path} conflicts with built-in MCP endpoint. Skipping registration.`
|
|
682
|
+
);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
const a = i.method.toLowerCase();
|
|
686
|
+
r[a](n, async (c, d) => {
|
|
687
|
+
try {
|
|
688
|
+
const u = c.headers["mcp-client-id"]?.trim(), p = u && u.length > 0 ? u : `anon-${y()}`;
|
|
689
|
+
let C;
|
|
690
|
+
if (i.bodySchema) {
|
|
691
|
+
const m = i.bodySchema.safeParse(c.body);
|
|
692
|
+
if (!m.success)
|
|
693
|
+
return A(d, "body", m.error);
|
|
694
|
+
C = m.data;
|
|
695
|
+
}
|
|
696
|
+
let x = {};
|
|
697
|
+
if (i.querySchema) {
|
|
698
|
+
const m = i.querySchema.safeParse(c.query);
|
|
699
|
+
if (!m.success)
|
|
700
|
+
return A(d, "query", m.error);
|
|
701
|
+
x = m.data;
|
|
702
|
+
}
|
|
703
|
+
let I = {};
|
|
704
|
+
if (i.paramsSchema) {
|
|
705
|
+
const m = i.paramsSchema.safeParse(c.params);
|
|
706
|
+
if (!m.success)
|
|
707
|
+
return A(d, "params", m.error);
|
|
708
|
+
I = m.data;
|
|
709
|
+
}
|
|
710
|
+
const M = {
|
|
711
|
+
body: C,
|
|
712
|
+
query: x,
|
|
713
|
+
params: I,
|
|
714
|
+
headers: c.headers,
|
|
715
|
+
clientId: p
|
|
716
|
+
};
|
|
717
|
+
if (t?.contextExtractor) {
|
|
718
|
+
const m = await t.contextExtractor(c);
|
|
719
|
+
Object.assign(M, m);
|
|
720
|
+
}
|
|
721
|
+
const $ = await i.handler(M);
|
|
722
|
+
if (i.responseSchema) {
|
|
723
|
+
const m = i.responseSchema.safeParse($);
|
|
724
|
+
return m.success ? m.data : (console.error(
|
|
725
|
+
`Response validation failed for ${i.method} ${i.path}:`,
|
|
726
|
+
m.error
|
|
727
|
+
), d.code(500), {
|
|
728
|
+
error: {
|
|
729
|
+
code: "RESPONSE_VALIDATION_ERROR",
|
|
730
|
+
message: "Internal server error: invalid response format"
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
return $;
|
|
735
|
+
} catch (u) {
|
|
736
|
+
return console.error(
|
|
737
|
+
`Error in custom endpoint ${i.method} ${i.path}:`,
|
|
738
|
+
u
|
|
739
|
+
), d.code(500), {
|
|
740
|
+
error: {
|
|
741
|
+
code: "INTERNAL_ERROR",
|
|
742
|
+
message: u instanceof Error ? u.message : "Internal server error"
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
function A(r, e, s) {
|
|
750
|
+
return r.code(400), {
|
|
751
|
+
error: {
|
|
752
|
+
code: "VALIDATION_ERROR",
|
|
753
|
+
message: `Validation failed for ${e}`,
|
|
754
|
+
details: s.errors
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
class ie {
|
|
674
759
|
constructor(e, s, t = {}, o) {
|
|
675
|
-
this.app = null, this.clientCache = new
|
|
760
|
+
this.app = null, this.clientCache = new O({
|
|
676
761
|
onEvict: (i, n) => {
|
|
677
762
|
this.cleanupBundle(n);
|
|
678
763
|
}
|
|
@@ -682,13 +767,14 @@ class Q {
|
|
|
682
767
|
basePath: t.basePath ?? "/",
|
|
683
768
|
cors: t.cors ?? !0,
|
|
684
769
|
logger: t.logger ?? !1,
|
|
685
|
-
app: t.app
|
|
770
|
+
app: t.app,
|
|
771
|
+
customEndpoints: t.customEndpoints
|
|
686
772
|
}, this.configSchema = o;
|
|
687
773
|
}
|
|
688
774
|
async start() {
|
|
689
775
|
if (this.app) return;
|
|
690
|
-
const e = this.options.app ??
|
|
691
|
-
this.options.cors && await e.register(
|
|
776
|
+
const e = this.options.app ?? N({ logger: this.options.logger });
|
|
777
|
+
this.options.cors && await e.register(j, { origin: !0 });
|
|
692
778
|
const s = this.options.basePath.endsWith("/") ? this.options.basePath.slice(0, -1) : this.options.basePath;
|
|
693
779
|
e.get(`${s}/healthz`, async () => ({ ok: !0 })), e.get(`${s}/tools`, async () => this.defaultManager.getStatus()), e.get(`${s}/.well-known/mcp-config`, async (t, o) => (o.header("Content-Type", "application/schema+json; charset=utf-8"), this.configSchema ?? {
|
|
694
780
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -702,26 +788,26 @@ class Q {
|
|
|
702
788
|
})), e.post(
|
|
703
789
|
`${s}/mcp`,
|
|
704
790
|
async (t, o) => {
|
|
705
|
-
const i = t.headers["mcp-client-id"]?.trim(), n = i && i.length > 0 ? i : `anon-${
|
|
791
|
+
const i = t.headers["mcp-client-id"]?.trim(), n = i && i.length > 0 ? i : `anon-${y()}`, l = !n.startsWith("anon-");
|
|
706
792
|
let a = l ? this.clientCache.get(n) : null;
|
|
707
793
|
if (!a) {
|
|
708
|
-
const
|
|
794
|
+
const u = this.createBundle(), p = u.sessions;
|
|
709
795
|
a = {
|
|
710
|
-
server:
|
|
711
|
-
orchestrator:
|
|
712
|
-
sessions:
|
|
796
|
+
server: u.server,
|
|
797
|
+
orchestrator: u.orchestrator,
|
|
798
|
+
sessions: p instanceof Map ? p : /* @__PURE__ */ new Map()
|
|
713
799
|
}, l && this.clientCache.set(n, a);
|
|
714
800
|
}
|
|
715
801
|
const c = t.headers["mcp-session-id"];
|
|
716
802
|
let d;
|
|
717
803
|
if (c && a.sessions.get(c))
|
|
718
804
|
d = a.sessions.get(c);
|
|
719
|
-
else if (!c &&
|
|
720
|
-
const
|
|
721
|
-
d = new
|
|
722
|
-
sessionIdGenerator: () =>
|
|
723
|
-
onsessioninitialized: (
|
|
724
|
-
a.sessions.set(
|
|
805
|
+
else if (!c && D(t.body)) {
|
|
806
|
+
const u = y();
|
|
807
|
+
d = new k({
|
|
808
|
+
sessionIdGenerator: () => u,
|
|
809
|
+
onsessioninitialized: (p) => {
|
|
810
|
+
a.sessions.set(p, d);
|
|
725
811
|
}
|
|
726
812
|
});
|
|
727
813
|
try {
|
|
@@ -791,7 +877,7 @@ class Q {
|
|
|
791
877
|
}
|
|
792
878
|
return o.code(204).send(), o;
|
|
793
879
|
}
|
|
794
|
-
), this.options.app || await e.listen({ host: this.options.host, port: this.options.port }), this.app = e;
|
|
880
|
+
), this.options.customEndpoints && this.options.customEndpoints.length > 0 && V(e, s, this.options.customEndpoints), this.options.app || await e.listen({ host: this.options.host, port: this.options.port }), this.app = e;
|
|
795
881
|
}
|
|
796
882
|
/**
|
|
797
883
|
* Stops the Fastify server and cleans up all resources.
|
|
@@ -818,7 +904,7 @@ class Q {
|
|
|
818
904
|
e.sessions.clear();
|
|
819
905
|
}
|
|
820
906
|
}
|
|
821
|
-
async function
|
|
907
|
+
async function Se(r) {
|
|
822
908
|
const e = r.startup?.mode ?? "DYNAMIC";
|
|
823
909
|
if (typeof r.createServer != "function")
|
|
824
910
|
throw new Error("createMcpServer: `createServer` (factory) is required");
|
|
@@ -832,9 +918,11 @@ async function ge(r) {
|
|
|
832
918
|
}
|
|
833
919
|
o(a) && await a.notifyToolsListChanged();
|
|
834
920
|
} catch (c) {
|
|
921
|
+
if ((c instanceof Error ? c.message : String(c)) === "Not connected")
|
|
922
|
+
return;
|
|
835
923
|
console.warn("Failed to send tools list changed notification:", c);
|
|
836
924
|
}
|
|
837
|
-
}, n = new
|
|
925
|
+
}, n = new w({
|
|
838
926
|
server: s,
|
|
839
927
|
catalog: r.catalog,
|
|
840
928
|
moduleLoaders: r.moduleLoaders,
|
|
@@ -843,12 +931,14 @@ async function ge(r) {
|
|
|
843
931
|
notifyToolsListChanged: async () => i(s),
|
|
844
932
|
startup: r.startup,
|
|
845
933
|
registerMetaTools: r.registerMetaTools !== void 0 ? r.registerMetaTools : e === "DYNAMIC"
|
|
846
|
-
})
|
|
934
|
+
});
|
|
935
|
+
e === "STATIC" && await n.ensureReady();
|
|
936
|
+
const l = new ie(
|
|
847
937
|
n.getManager(),
|
|
848
938
|
() => {
|
|
849
939
|
if (e === "STATIC")
|
|
850
940
|
return { server: s, orchestrator: n };
|
|
851
|
-
const a = r.createServer(), c = new
|
|
941
|
+
const a = r.createServer(), c = new w({
|
|
852
942
|
server: a,
|
|
853
943
|
catalog: r.catalog,
|
|
854
944
|
moduleLoaders: r.moduleLoaders,
|
|
@@ -873,16 +963,16 @@ async function ge(r) {
|
|
|
873
963
|
}
|
|
874
964
|
};
|
|
875
965
|
}
|
|
876
|
-
function
|
|
877
|
-
|
|
966
|
+
function ne(r) {
|
|
967
|
+
ae(r), le(r), ce(r), de(r);
|
|
878
968
|
}
|
|
879
|
-
function
|
|
969
|
+
function ae(r) {
|
|
880
970
|
if (!r || typeof r != "object")
|
|
881
971
|
throw new Error(
|
|
882
972
|
"Permission configuration is required for createPermissionBasedMcpServer"
|
|
883
973
|
);
|
|
884
974
|
}
|
|
885
|
-
function
|
|
975
|
+
function le(r) {
|
|
886
976
|
if (!r.source)
|
|
887
977
|
throw new Error('Permission source must be either "headers" or "config"');
|
|
888
978
|
if (r.source !== "headers" && r.source !== "config")
|
|
@@ -890,19 +980,19 @@ function ee(r) {
|
|
|
890
980
|
`Invalid permission source: "${r.source}". Must be either "headers" or "config"`
|
|
891
981
|
);
|
|
892
982
|
}
|
|
893
|
-
function
|
|
983
|
+
function ce(r) {
|
|
894
984
|
if (r.source === "config" && !r.staticMap && !r.resolver)
|
|
895
985
|
throw new Error(
|
|
896
986
|
"Config-based permissions require at least one of: staticMap or resolver function"
|
|
897
987
|
);
|
|
898
988
|
}
|
|
899
|
-
function
|
|
989
|
+
function de(r) {
|
|
900
990
|
if (r.staticMap !== void 0) {
|
|
901
991
|
if (typeof r.staticMap != "object" || r.staticMap === null)
|
|
902
992
|
throw new Error(
|
|
903
993
|
"staticMap must be an object mapping client IDs to toolset arrays"
|
|
904
994
|
);
|
|
905
|
-
|
|
995
|
+
he(r.staticMap);
|
|
906
996
|
}
|
|
907
997
|
if (r.resolver !== void 0 && typeof r.resolver != "function")
|
|
908
998
|
throw new Error(
|
|
@@ -913,21 +1003,21 @@ function te(r) {
|
|
|
913
1003
|
if (r.headerName !== void 0 && (typeof r.headerName != "string" || r.headerName.length === 0))
|
|
914
1004
|
throw new Error("headerName must be a non-empty string");
|
|
915
1005
|
}
|
|
916
|
-
function
|
|
1006
|
+
function he(r) {
|
|
917
1007
|
for (const [e, s] of Object.entries(r))
|
|
918
1008
|
if (!Array.isArray(s))
|
|
919
1009
|
throw new Error(
|
|
920
1010
|
`staticMap value for client "${e}" must be an array of toolset names`
|
|
921
1011
|
);
|
|
922
1012
|
}
|
|
923
|
-
var
|
|
924
|
-
class
|
|
1013
|
+
var g, F, _, B, H, W;
|
|
1014
|
+
class ue {
|
|
925
1015
|
/**
|
|
926
1016
|
* Creates a new PermissionResolver instance.
|
|
927
1017
|
* @param config - The permission configuration defining how permissions are resolved
|
|
928
1018
|
*/
|
|
929
1019
|
constructor(e) {
|
|
930
|
-
|
|
1020
|
+
T(this, g);
|
|
931
1021
|
this.config = e, this.cache = /* @__PURE__ */ new Map(), this.normalizedHeaderName = (e.headerName || "mcp-toolset-permissions").toLowerCase();
|
|
932
1022
|
}
|
|
933
1023
|
/**
|
|
@@ -948,7 +1038,7 @@ class re {
|
|
|
948
1038
|
return this.cache.get(e);
|
|
949
1039
|
let t;
|
|
950
1040
|
try {
|
|
951
|
-
this.config.source === "headers" ? t =
|
|
1041
|
+
this.config.source === "headers" ? t = f(this, g, F).call(this, s) : t = f(this, g, B).call(this, e), Array.isArray(t) || (console.warn(
|
|
952
1042
|
`Permission resolution returned non-array for client ${e}, using empty permissions`
|
|
953
1043
|
), t = []), t = t.filter(
|
|
954
1044
|
(o) => typeof o == "string" && o.trim().length > 0
|
|
@@ -977,7 +1067,7 @@ class re {
|
|
|
977
1067
|
this.cache.clear();
|
|
978
1068
|
}
|
|
979
1069
|
}
|
|
980
|
-
|
|
1070
|
+
g = new WeakSet(), /**
|
|
981
1071
|
* Parses permissions from request headers.
|
|
982
1072
|
* Extracts comma-separated toolset names from the configured header.
|
|
983
1073
|
* Handles malformed headers gracefully by returning empty permissions.
|
|
@@ -986,10 +1076,10 @@ f = new WeakSet(), /**
|
|
|
986
1076
|
* @returns Array of toolset names from headers, or empty array if header is missing/malformed
|
|
987
1077
|
* @private
|
|
988
1078
|
*/
|
|
989
|
-
|
|
1079
|
+
F = function(e) {
|
|
990
1080
|
if (!e)
|
|
991
1081
|
return [];
|
|
992
|
-
const s =
|
|
1082
|
+
const s = f(this, g, _).call(this, e, this.normalizedHeaderName);
|
|
993
1083
|
if (!s)
|
|
994
1084
|
return [];
|
|
995
1085
|
try {
|
|
@@ -1008,7 +1098,7 @@ L = function(e) {
|
|
|
1008
1098
|
* @returns The header value if found, undefined otherwise
|
|
1009
1099
|
* @private
|
|
1010
1100
|
*/
|
|
1011
|
-
|
|
1101
|
+
_ = function(e, s) {
|
|
1012
1102
|
if (e[s] !== void 0)
|
|
1013
1103
|
return e[s];
|
|
1014
1104
|
for (const [t, o] of Object.entries(e))
|
|
@@ -1022,14 +1112,14 @@ j = function(e, s) {
|
|
|
1022
1112
|
* @returns Array of toolset names from configuration
|
|
1023
1113
|
* @private
|
|
1024
1114
|
*/
|
|
1025
|
-
|
|
1115
|
+
B = function(e) {
|
|
1026
1116
|
if (this.config.resolver) {
|
|
1027
|
-
const s =
|
|
1117
|
+
const s = f(this, g, H).call(this, e);
|
|
1028
1118
|
if (s !== null)
|
|
1029
1119
|
return s;
|
|
1030
1120
|
}
|
|
1031
1121
|
if (this.config.staticMap) {
|
|
1032
|
-
const s =
|
|
1122
|
+
const s = f(this, g, W).call(this, e);
|
|
1033
1123
|
if (s !== null)
|
|
1034
1124
|
return s;
|
|
1035
1125
|
}
|
|
@@ -1041,7 +1131,7 @@ N = function(e) {
|
|
|
1041
1131
|
* @returns Array of toolset names if successful, null if resolver fails or returns invalid data
|
|
1042
1132
|
* @private
|
|
1043
1133
|
*/
|
|
1044
|
-
|
|
1134
|
+
H = function(e) {
|
|
1045
1135
|
try {
|
|
1046
1136
|
const s = this.config.resolver(e);
|
|
1047
1137
|
return Array.isArray(s) ? s : (console.warn(
|
|
@@ -1060,11 +1150,11 @@ k = function(e) {
|
|
|
1060
1150
|
* @returns Array of toolset names if found, null if client not in map
|
|
1061
1151
|
* @private
|
|
1062
1152
|
*/
|
|
1063
|
-
|
|
1153
|
+
W = function(e) {
|
|
1064
1154
|
const s = this.config.staticMap[e];
|
|
1065
1155
|
return s !== void 0 ? Array.isArray(s) ? s : [] : null;
|
|
1066
1156
|
};
|
|
1067
|
-
function
|
|
1157
|
+
function fe(r, e) {
|
|
1068
1158
|
return async (s) => {
|
|
1069
1159
|
const t = e.resolvePermissions(
|
|
1070
1160
|
s.clientId,
|
|
@@ -1089,8 +1179,8 @@ function ie(r, e) {
|
|
|
1089
1179
|
};
|
|
1090
1180
|
};
|
|
1091
1181
|
}
|
|
1092
|
-
var h,
|
|
1093
|
-
class
|
|
1182
|
+
var h, Y, U, q, J, G, K, Q, X, E, Z;
|
|
1183
|
+
class me {
|
|
1094
1184
|
/**
|
|
1095
1185
|
* Creates a new PermissionAwareFastifyTransport instance.
|
|
1096
1186
|
* @param defaultManager - Default tool manager for status endpoints
|
|
@@ -1099,10 +1189,10 @@ class ne {
|
|
|
1099
1189
|
* @param configSchema - Optional JSON schema for configuration discovery
|
|
1100
1190
|
*/
|
|
1101
1191
|
constructor(e, s, t = {}, o) {
|
|
1102
|
-
|
|
1103
|
-
this.app = null, this.clientCache = new
|
|
1192
|
+
T(this, h);
|
|
1193
|
+
this.app = null, this.clientCache = new O({
|
|
1104
1194
|
onEvict: (i, n) => {
|
|
1105
|
-
|
|
1195
|
+
f(this, h, Y).call(this, n);
|
|
1106
1196
|
}
|
|
1107
1197
|
}), this.defaultManager = e, this.createPermissionAwareBundle = s, this.options = {
|
|
1108
1198
|
host: t.host ?? "0.0.0.0",
|
|
@@ -1110,7 +1200,8 @@ class ne {
|
|
|
1110
1200
|
basePath: t.basePath ?? "/",
|
|
1111
1201
|
cors: t.cors ?? !0,
|
|
1112
1202
|
logger: t.logger ?? !1,
|
|
1113
|
-
app: t.app
|
|
1203
|
+
app: t.app,
|
|
1204
|
+
customEndpoints: t.customEndpoints
|
|
1114
1205
|
}, this.configSchema = o;
|
|
1115
1206
|
}
|
|
1116
1207
|
/**
|
|
@@ -1119,10 +1210,28 @@ class ne {
|
|
|
1119
1210
|
*/
|
|
1120
1211
|
async start() {
|
|
1121
1212
|
if (this.app) return;
|
|
1122
|
-
const e = this.options.app ??
|
|
1123
|
-
this.options.cors && await e.register(
|
|
1124
|
-
const s =
|
|
1125
|
-
|
|
1213
|
+
const e = this.options.app ?? N({ logger: this.options.logger });
|
|
1214
|
+
this.options.cors && await e.register(j, { origin: !0 });
|
|
1215
|
+
const s = f(this, h, U).call(this, this.options.basePath);
|
|
1216
|
+
f(this, h, q).call(this, e, s), f(this, h, J).call(this, e, s), f(this, h, G).call(this, e, s), f(this, h, K).call(this, e, s), f(this, h, Q).call(this, e, s), f(this, h, X).call(this, e, s), this.options.customEndpoints && this.options.customEndpoints.length > 0 && V(e, s, this.options.customEndpoints, {
|
|
1217
|
+
contextExtractor: async (t) => {
|
|
1218
|
+
const o = f(this, h, E).call(this, t);
|
|
1219
|
+
try {
|
|
1220
|
+
const i = await this.createPermissionAwareBundle(o);
|
|
1221
|
+
return {
|
|
1222
|
+
allowedToolsets: i.allowedToolsets,
|
|
1223
|
+
failedToolsets: i.failedToolsets
|
|
1224
|
+
};
|
|
1225
|
+
} catch (i) {
|
|
1226
|
+
return console.warn(
|
|
1227
|
+
`Permission resolution failed for custom endpoint: ${i}`
|
|
1228
|
+
), {
|
|
1229
|
+
allowedToolsets: [],
|
|
1230
|
+
failedToolsets: []
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
}), this.options.app || await e.listen({ host: this.options.host, port: this.options.port }), this.app = e;
|
|
1126
1235
|
}
|
|
1127
1236
|
/**
|
|
1128
1237
|
* Stops the Fastify server and cleans up all resources.
|
|
@@ -1138,7 +1247,7 @@ h = new WeakSet(), /**
|
|
|
1138
1247
|
* @param bundle - The client bundle to clean up
|
|
1139
1248
|
* @private
|
|
1140
1249
|
*/
|
|
1141
|
-
|
|
1250
|
+
Y = function(e) {
|
|
1142
1251
|
for (const [s, t] of e.sessions.entries())
|
|
1143
1252
|
try {
|
|
1144
1253
|
typeof t.close == "function" && t.close().catch((o) => {
|
|
@@ -1154,7 +1263,7 @@ z = function(e) {
|
|
|
1154
1263
|
* @returns Normalized base path without trailing slash
|
|
1155
1264
|
* @private
|
|
1156
1265
|
*/
|
|
1157
|
-
|
|
1266
|
+
U = function(e) {
|
|
1158
1267
|
return e.endsWith("/") ? e.slice(0, -1) : e;
|
|
1159
1268
|
}, /**
|
|
1160
1269
|
* Registers the health check endpoint.
|
|
@@ -1162,7 +1271,7 @@ R = function(e) {
|
|
|
1162
1271
|
* @param base - Base path for routes
|
|
1163
1272
|
* @private
|
|
1164
1273
|
*/
|
|
1165
|
-
|
|
1274
|
+
q = function(e, s) {
|
|
1166
1275
|
e.get(`${s}/healthz`, async () => ({ ok: !0 }));
|
|
1167
1276
|
}, /**
|
|
1168
1277
|
* Registers the tools status endpoint.
|
|
@@ -1170,7 +1279,7 @@ O = function(e, s) {
|
|
|
1170
1279
|
* @param base - Base path for routes
|
|
1171
1280
|
* @private
|
|
1172
1281
|
*/
|
|
1173
|
-
|
|
1282
|
+
J = function(e, s) {
|
|
1174
1283
|
e.get(`${s}/tools`, async () => this.defaultManager.getStatus());
|
|
1175
1284
|
}, /**
|
|
1176
1285
|
* Registers the MCP configuration discovery endpoint.
|
|
@@ -1178,7 +1287,7 @@ F = function(e, s) {
|
|
|
1178
1287
|
* @param base - Base path for routes
|
|
1179
1288
|
* @private
|
|
1180
1289
|
*/
|
|
1181
|
-
|
|
1290
|
+
G = function(e, s) {
|
|
1182
1291
|
e.get(`${s}/.well-known/mcp-config`, async (t, o) => (o.header("Content-Type", "application/schema+json; charset=utf-8"), this.configSchema ?? {
|
|
1183
1292
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1184
1293
|
title: "MCP Session Configuration",
|
|
@@ -1196,11 +1305,11 @@ V = function(e, s) {
|
|
|
1196
1305
|
* @param base - Base path for routes
|
|
1197
1306
|
* @private
|
|
1198
1307
|
*/
|
|
1199
|
-
|
|
1308
|
+
K = function(e, s) {
|
|
1200
1309
|
e.post(
|
|
1201
1310
|
`${s}/mcp`,
|
|
1202
1311
|
async (t, o) => {
|
|
1203
|
-
const i =
|
|
1312
|
+
const i = f(this, h, E).call(this, t), n = !i.clientId.startsWith("anon-");
|
|
1204
1313
|
let l = n ? this.clientCache.get(i.clientId) : null;
|
|
1205
1314
|
if (!l)
|
|
1206
1315
|
try {
|
|
@@ -1208,30 +1317,30 @@ _ = function(e, s) {
|
|
|
1208
1317
|
d.failedToolsets.length > 0 && console.warn(
|
|
1209
1318
|
`Client ${i.clientId} had ${d.failedToolsets.length} toolsets fail to enable: [${d.failedToolsets.join(", ")}]. Successfully enabled: [${d.allowedToolsets.join(", ")}]`
|
|
1210
1319
|
);
|
|
1211
|
-
const
|
|
1320
|
+
const u = d.sessions;
|
|
1212
1321
|
l = {
|
|
1213
1322
|
server: d.server,
|
|
1214
1323
|
orchestrator: d.orchestrator,
|
|
1215
1324
|
allowedToolsets: d.allowedToolsets,
|
|
1216
1325
|
failedToolsets: d.failedToolsets,
|
|
1217
|
-
sessions:
|
|
1326
|
+
sessions: u instanceof Map ? u : /* @__PURE__ */ new Map()
|
|
1218
1327
|
}, n && this.clientCache.set(i.clientId, l);
|
|
1219
1328
|
} catch (d) {
|
|
1220
1329
|
return console.error(
|
|
1221
1330
|
`Failed to create permission-aware bundle for client ${i.clientId}:`,
|
|
1222
1331
|
d
|
|
1223
|
-
), o.code(403),
|
|
1332
|
+
), o.code(403), f(this, h, Z).call(this, "Access denied");
|
|
1224
1333
|
}
|
|
1225
1334
|
const a = t.headers["mcp-session-id"];
|
|
1226
1335
|
let c;
|
|
1227
1336
|
if (a && l.sessions.get(a))
|
|
1228
1337
|
c = l.sessions.get(a);
|
|
1229
|
-
else if (!a &&
|
|
1230
|
-
const d =
|
|
1231
|
-
c = new
|
|
1338
|
+
else if (!a && D(t.body)) {
|
|
1339
|
+
const d = y();
|
|
1340
|
+
c = new k({
|
|
1232
1341
|
sessionIdGenerator: () => d,
|
|
1233
|
-
onsessioninitialized: (
|
|
1234
|
-
l.sessions.set(
|
|
1342
|
+
onsessioninitialized: (u) => {
|
|
1343
|
+
l.sessions.set(u, c);
|
|
1235
1344
|
}
|
|
1236
1345
|
});
|
|
1237
1346
|
try {
|
|
@@ -1265,7 +1374,7 @@ _ = function(e, s) {
|
|
|
1265
1374
|
* @param base - Base path for routes
|
|
1266
1375
|
* @private
|
|
1267
1376
|
*/
|
|
1268
|
-
|
|
1377
|
+
Q = function(e, s) {
|
|
1269
1378
|
e.get(`${s}/mcp`, async (t, o) => {
|
|
1270
1379
|
const i = t.headers["mcp-client-id"]?.trim(), n = i && i.length > 0 ? i : "";
|
|
1271
1380
|
if (!n)
|
|
@@ -1285,7 +1394,7 @@ B = function(e, s) {
|
|
|
1285
1394
|
* @param base - Base path for routes
|
|
1286
1395
|
* @private
|
|
1287
1396
|
*/
|
|
1288
|
-
|
|
1397
|
+
X = function(e, s) {
|
|
1289
1398
|
e.delete(
|
|
1290
1399
|
`${s}/mcp`,
|
|
1291
1400
|
async (t, o) => {
|
|
@@ -1325,8 +1434,8 @@ Y = function(e, s) {
|
|
|
1325
1434
|
* @returns Client request context with ID and headers
|
|
1326
1435
|
* @private
|
|
1327
1436
|
*/
|
|
1328
|
-
|
|
1329
|
-
const s = e.headers["mcp-client-id"]?.trim(), t = s && s.length > 0 ? s : `anon-${
|
|
1437
|
+
E = function(e) {
|
|
1438
|
+
const s = e.headers["mcp-client-id"]?.trim(), t = s && s.length > 0 ? s : `anon-${y()}`, o = {};
|
|
1330
1439
|
for (const [i, n] of Object.entries(e.headers))
|
|
1331
1440
|
typeof n == "string" && (o[i] = n);
|
|
1332
1441
|
return { clientId: t, headers: o };
|
|
@@ -1338,7 +1447,7 @@ H = function(e) {
|
|
|
1338
1447
|
* @returns JSON-RPC error response object
|
|
1339
1448
|
* @private
|
|
1340
1449
|
*/
|
|
1341
|
-
|
|
1450
|
+
Z = function(e = "Access denied", s = -32e3) {
|
|
1342
1451
|
return {
|
|
1343
1452
|
jsonrpc: "2.0",
|
|
1344
1453
|
error: {
|
|
@@ -1348,7 +1457,7 @@ U = function(e = "Access denied", s = -32e3) {
|
|
|
1348
1457
|
id: null
|
|
1349
1458
|
};
|
|
1350
1459
|
};
|
|
1351
|
-
function
|
|
1460
|
+
function ge(r) {
|
|
1352
1461
|
if (!r) return;
|
|
1353
1462
|
const e = {
|
|
1354
1463
|
namespaceToolsWithSetKey: r.namespaceToolsWithSetKey
|
|
@@ -1363,12 +1472,12 @@ function ae(r) {
|
|
|
1363
1472
|
"Permission-based servers: exposurePolicy.onLimitExceeded is ignored. No toolset limits are enforced."
|
|
1364
1473
|
), e;
|
|
1365
1474
|
}
|
|
1366
|
-
async function
|
|
1475
|
+
async function Ee(r) {
|
|
1367
1476
|
if (!r.permissions)
|
|
1368
1477
|
throw new Error(
|
|
1369
1478
|
"Permission configuration is required for createPermissionBasedMcpServer. Please provide a 'permissions' field in the options."
|
|
1370
1479
|
);
|
|
1371
|
-
if (
|
|
1480
|
+
if (ne(r.permissions), r.startup)
|
|
1372
1481
|
throw new Error(
|
|
1373
1482
|
"Permission-based servers determine toolsets from client permissions. The 'startup' option is not allowed. Remove it from your configuration."
|
|
1374
1483
|
);
|
|
@@ -1376,9 +1485,9 @@ async function pe(r) {
|
|
|
1376
1485
|
throw new Error(
|
|
1377
1486
|
"createPermissionBasedMcpServer: `createServer` (factory) is required"
|
|
1378
1487
|
);
|
|
1379
|
-
const e =
|
|
1488
|
+
const e = ge(
|
|
1380
1489
|
r.exposurePolicy
|
|
1381
|
-
), s = new
|
|
1490
|
+
), s = new ue(r.permissions), t = r.createServer(), o = new w({
|
|
1382
1491
|
server: t,
|
|
1383
1492
|
catalog: r.catalog,
|
|
1384
1493
|
moduleLoaders: r.moduleLoaders,
|
|
@@ -1388,9 +1497,9 @@ async function pe(r) {
|
|
|
1388
1497
|
// No notifications in STATIC mode
|
|
1389
1498
|
startup: { mode: "STATIC", toolsets: [] },
|
|
1390
1499
|
registerMetaTools: !1
|
|
1391
|
-
}), i =
|
|
1500
|
+
}), i = fe(
|
|
1392
1501
|
(l) => {
|
|
1393
|
-
const a = r.createServer(), c = new
|
|
1502
|
+
const a = r.createServer(), c = new w({
|
|
1394
1503
|
server: a,
|
|
1395
1504
|
catalog: r.catalog,
|
|
1396
1505
|
moduleLoaders: r.moduleLoaders,
|
|
@@ -1406,7 +1515,7 @@ async function pe(r) {
|
|
|
1406
1515
|
return { server: a, orchestrator: c };
|
|
1407
1516
|
},
|
|
1408
1517
|
s
|
|
1409
|
-
), n = new
|
|
1518
|
+
), n = new me(
|
|
1410
1519
|
o.getManager(),
|
|
1411
1520
|
i,
|
|
1412
1521
|
r.http,
|
|
@@ -1426,8 +1535,16 @@ async function pe(r) {
|
|
|
1426
1535
|
}
|
|
1427
1536
|
};
|
|
1428
1537
|
}
|
|
1538
|
+
function Ce(r) {
|
|
1539
|
+
return r;
|
|
1540
|
+
}
|
|
1541
|
+
function xe(r) {
|
|
1542
|
+
return r;
|
|
1543
|
+
}
|
|
1429
1544
|
export {
|
|
1430
|
-
|
|
1431
|
-
|
|
1545
|
+
Se as createMcpServer,
|
|
1546
|
+
Ee as createPermissionBasedMcpServer,
|
|
1547
|
+
Ce as defineEndpoint,
|
|
1548
|
+
xe as definePermissionAwareEndpoint
|
|
1432
1549
|
};
|
|
1433
1550
|
//# sourceMappingURL=index.js.map
|