toolception 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +236 -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,12 @@ 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
|
-
}), l = new
|
|
934
|
+
}), l = new ie(
|
|
847
935
|
n.getManager(),
|
|
848
936
|
() => {
|
|
849
937
|
if (e === "STATIC")
|
|
850
938
|
return { server: s, orchestrator: n };
|
|
851
|
-
const a = r.createServer(), c = new
|
|
939
|
+
const a = r.createServer(), c = new w({
|
|
852
940
|
server: a,
|
|
853
941
|
catalog: r.catalog,
|
|
854
942
|
moduleLoaders: r.moduleLoaders,
|
|
@@ -873,16 +961,16 @@ async function ge(r) {
|
|
|
873
961
|
}
|
|
874
962
|
};
|
|
875
963
|
}
|
|
876
|
-
function
|
|
877
|
-
|
|
964
|
+
function ne(r) {
|
|
965
|
+
ae(r), le(r), ce(r), de(r);
|
|
878
966
|
}
|
|
879
|
-
function
|
|
967
|
+
function ae(r) {
|
|
880
968
|
if (!r || typeof r != "object")
|
|
881
969
|
throw new Error(
|
|
882
970
|
"Permission configuration is required for createPermissionBasedMcpServer"
|
|
883
971
|
);
|
|
884
972
|
}
|
|
885
|
-
function
|
|
973
|
+
function le(r) {
|
|
886
974
|
if (!r.source)
|
|
887
975
|
throw new Error('Permission source must be either "headers" or "config"');
|
|
888
976
|
if (r.source !== "headers" && r.source !== "config")
|
|
@@ -890,19 +978,19 @@ function ee(r) {
|
|
|
890
978
|
`Invalid permission source: "${r.source}". Must be either "headers" or "config"`
|
|
891
979
|
);
|
|
892
980
|
}
|
|
893
|
-
function
|
|
981
|
+
function ce(r) {
|
|
894
982
|
if (r.source === "config" && !r.staticMap && !r.resolver)
|
|
895
983
|
throw new Error(
|
|
896
984
|
"Config-based permissions require at least one of: staticMap or resolver function"
|
|
897
985
|
);
|
|
898
986
|
}
|
|
899
|
-
function
|
|
987
|
+
function de(r) {
|
|
900
988
|
if (r.staticMap !== void 0) {
|
|
901
989
|
if (typeof r.staticMap != "object" || r.staticMap === null)
|
|
902
990
|
throw new Error(
|
|
903
991
|
"staticMap must be an object mapping client IDs to toolset arrays"
|
|
904
992
|
);
|
|
905
|
-
|
|
993
|
+
he(r.staticMap);
|
|
906
994
|
}
|
|
907
995
|
if (r.resolver !== void 0 && typeof r.resolver != "function")
|
|
908
996
|
throw new Error(
|
|
@@ -913,21 +1001,21 @@ function te(r) {
|
|
|
913
1001
|
if (r.headerName !== void 0 && (typeof r.headerName != "string" || r.headerName.length === 0))
|
|
914
1002
|
throw new Error("headerName must be a non-empty string");
|
|
915
1003
|
}
|
|
916
|
-
function
|
|
1004
|
+
function he(r) {
|
|
917
1005
|
for (const [e, s] of Object.entries(r))
|
|
918
1006
|
if (!Array.isArray(s))
|
|
919
1007
|
throw new Error(
|
|
920
1008
|
`staticMap value for client "${e}" must be an array of toolset names`
|
|
921
1009
|
);
|
|
922
1010
|
}
|
|
923
|
-
var
|
|
924
|
-
class
|
|
1011
|
+
var g, F, _, B, H, W;
|
|
1012
|
+
class ue {
|
|
925
1013
|
/**
|
|
926
1014
|
* Creates a new PermissionResolver instance.
|
|
927
1015
|
* @param config - The permission configuration defining how permissions are resolved
|
|
928
1016
|
*/
|
|
929
1017
|
constructor(e) {
|
|
930
|
-
|
|
1018
|
+
T(this, g);
|
|
931
1019
|
this.config = e, this.cache = /* @__PURE__ */ new Map(), this.normalizedHeaderName = (e.headerName || "mcp-toolset-permissions").toLowerCase();
|
|
932
1020
|
}
|
|
933
1021
|
/**
|
|
@@ -948,7 +1036,7 @@ class re {
|
|
|
948
1036
|
return this.cache.get(e);
|
|
949
1037
|
let t;
|
|
950
1038
|
try {
|
|
951
|
-
this.config.source === "headers" ? t =
|
|
1039
|
+
this.config.source === "headers" ? t = f(this, g, F).call(this, s) : t = f(this, g, B).call(this, e), Array.isArray(t) || (console.warn(
|
|
952
1040
|
`Permission resolution returned non-array for client ${e}, using empty permissions`
|
|
953
1041
|
), t = []), t = t.filter(
|
|
954
1042
|
(o) => typeof o == "string" && o.trim().length > 0
|
|
@@ -977,7 +1065,7 @@ class re {
|
|
|
977
1065
|
this.cache.clear();
|
|
978
1066
|
}
|
|
979
1067
|
}
|
|
980
|
-
|
|
1068
|
+
g = new WeakSet(), /**
|
|
981
1069
|
* Parses permissions from request headers.
|
|
982
1070
|
* Extracts comma-separated toolset names from the configured header.
|
|
983
1071
|
* Handles malformed headers gracefully by returning empty permissions.
|
|
@@ -986,10 +1074,10 @@ f = new WeakSet(), /**
|
|
|
986
1074
|
* @returns Array of toolset names from headers, or empty array if header is missing/malformed
|
|
987
1075
|
* @private
|
|
988
1076
|
*/
|
|
989
|
-
|
|
1077
|
+
F = function(e) {
|
|
990
1078
|
if (!e)
|
|
991
1079
|
return [];
|
|
992
|
-
const s =
|
|
1080
|
+
const s = f(this, g, _).call(this, e, this.normalizedHeaderName);
|
|
993
1081
|
if (!s)
|
|
994
1082
|
return [];
|
|
995
1083
|
try {
|
|
@@ -1008,7 +1096,7 @@ L = function(e) {
|
|
|
1008
1096
|
* @returns The header value if found, undefined otherwise
|
|
1009
1097
|
* @private
|
|
1010
1098
|
*/
|
|
1011
|
-
|
|
1099
|
+
_ = function(e, s) {
|
|
1012
1100
|
if (e[s] !== void 0)
|
|
1013
1101
|
return e[s];
|
|
1014
1102
|
for (const [t, o] of Object.entries(e))
|
|
@@ -1022,14 +1110,14 @@ j = function(e, s) {
|
|
|
1022
1110
|
* @returns Array of toolset names from configuration
|
|
1023
1111
|
* @private
|
|
1024
1112
|
*/
|
|
1025
|
-
|
|
1113
|
+
B = function(e) {
|
|
1026
1114
|
if (this.config.resolver) {
|
|
1027
|
-
const s =
|
|
1115
|
+
const s = f(this, g, H).call(this, e);
|
|
1028
1116
|
if (s !== null)
|
|
1029
1117
|
return s;
|
|
1030
1118
|
}
|
|
1031
1119
|
if (this.config.staticMap) {
|
|
1032
|
-
const s =
|
|
1120
|
+
const s = f(this, g, W).call(this, e);
|
|
1033
1121
|
if (s !== null)
|
|
1034
1122
|
return s;
|
|
1035
1123
|
}
|
|
@@ -1041,7 +1129,7 @@ N = function(e) {
|
|
|
1041
1129
|
* @returns Array of toolset names if successful, null if resolver fails or returns invalid data
|
|
1042
1130
|
* @private
|
|
1043
1131
|
*/
|
|
1044
|
-
|
|
1132
|
+
H = function(e) {
|
|
1045
1133
|
try {
|
|
1046
1134
|
const s = this.config.resolver(e);
|
|
1047
1135
|
return Array.isArray(s) ? s : (console.warn(
|
|
@@ -1060,11 +1148,11 @@ k = function(e) {
|
|
|
1060
1148
|
* @returns Array of toolset names if found, null if client not in map
|
|
1061
1149
|
* @private
|
|
1062
1150
|
*/
|
|
1063
|
-
|
|
1151
|
+
W = function(e) {
|
|
1064
1152
|
const s = this.config.staticMap[e];
|
|
1065
1153
|
return s !== void 0 ? Array.isArray(s) ? s : [] : null;
|
|
1066
1154
|
};
|
|
1067
|
-
function
|
|
1155
|
+
function fe(r, e) {
|
|
1068
1156
|
return async (s) => {
|
|
1069
1157
|
const t = e.resolvePermissions(
|
|
1070
1158
|
s.clientId,
|
|
@@ -1089,8 +1177,8 @@ function ie(r, e) {
|
|
|
1089
1177
|
};
|
|
1090
1178
|
};
|
|
1091
1179
|
}
|
|
1092
|
-
var h,
|
|
1093
|
-
class
|
|
1180
|
+
var h, Y, U, q, J, G, K, Q, X, E, Z;
|
|
1181
|
+
class me {
|
|
1094
1182
|
/**
|
|
1095
1183
|
* Creates a new PermissionAwareFastifyTransport instance.
|
|
1096
1184
|
* @param defaultManager - Default tool manager for status endpoints
|
|
@@ -1099,10 +1187,10 @@ class ne {
|
|
|
1099
1187
|
* @param configSchema - Optional JSON schema for configuration discovery
|
|
1100
1188
|
*/
|
|
1101
1189
|
constructor(e, s, t = {}, o) {
|
|
1102
|
-
|
|
1103
|
-
this.app = null, this.clientCache = new
|
|
1190
|
+
T(this, h);
|
|
1191
|
+
this.app = null, this.clientCache = new O({
|
|
1104
1192
|
onEvict: (i, n) => {
|
|
1105
|
-
|
|
1193
|
+
f(this, h, Y).call(this, n);
|
|
1106
1194
|
}
|
|
1107
1195
|
}), this.defaultManager = e, this.createPermissionAwareBundle = s, this.options = {
|
|
1108
1196
|
host: t.host ?? "0.0.0.0",
|
|
@@ -1110,7 +1198,8 @@ class ne {
|
|
|
1110
1198
|
basePath: t.basePath ?? "/",
|
|
1111
1199
|
cors: t.cors ?? !0,
|
|
1112
1200
|
logger: t.logger ?? !1,
|
|
1113
|
-
app: t.app
|
|
1201
|
+
app: t.app,
|
|
1202
|
+
customEndpoints: t.customEndpoints
|
|
1114
1203
|
}, this.configSchema = o;
|
|
1115
1204
|
}
|
|
1116
1205
|
/**
|
|
@@ -1119,10 +1208,28 @@ class ne {
|
|
|
1119
1208
|
*/
|
|
1120
1209
|
async start() {
|
|
1121
1210
|
if (this.app) return;
|
|
1122
|
-
const e = this.options.app ??
|
|
1123
|
-
this.options.cors && await e.register(
|
|
1124
|
-
const s =
|
|
1125
|
-
|
|
1211
|
+
const e = this.options.app ?? N({ logger: this.options.logger });
|
|
1212
|
+
this.options.cors && await e.register(j, { origin: !0 });
|
|
1213
|
+
const s = f(this, h, U).call(this, this.options.basePath);
|
|
1214
|
+
f(this, h, q).call(this, e, s), f(this, h, J).call(this, e, s), f(this, h, G).call(this, e, s), f(this, h, K).call(this, e, s), f(this, h, Q).call(this, e, s), f(this, h, X).call(this, e, s), this.options.customEndpoints && this.options.customEndpoints.length > 0 && V(e, s, this.options.customEndpoints, {
|
|
1215
|
+
contextExtractor: async (t) => {
|
|
1216
|
+
const o = f(this, h, E).call(this, t);
|
|
1217
|
+
try {
|
|
1218
|
+
const i = await this.createPermissionAwareBundle(o);
|
|
1219
|
+
return {
|
|
1220
|
+
allowedToolsets: i.allowedToolsets,
|
|
1221
|
+
failedToolsets: i.failedToolsets
|
|
1222
|
+
};
|
|
1223
|
+
} catch (i) {
|
|
1224
|
+
return console.warn(
|
|
1225
|
+
`Permission resolution failed for custom endpoint: ${i}`
|
|
1226
|
+
), {
|
|
1227
|
+
allowedToolsets: [],
|
|
1228
|
+
failedToolsets: []
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}), this.options.app || await e.listen({ host: this.options.host, port: this.options.port }), this.app = e;
|
|
1126
1233
|
}
|
|
1127
1234
|
/**
|
|
1128
1235
|
* Stops the Fastify server and cleans up all resources.
|
|
@@ -1138,7 +1245,7 @@ h = new WeakSet(), /**
|
|
|
1138
1245
|
* @param bundle - The client bundle to clean up
|
|
1139
1246
|
* @private
|
|
1140
1247
|
*/
|
|
1141
|
-
|
|
1248
|
+
Y = function(e) {
|
|
1142
1249
|
for (const [s, t] of e.sessions.entries())
|
|
1143
1250
|
try {
|
|
1144
1251
|
typeof t.close == "function" && t.close().catch((o) => {
|
|
@@ -1154,7 +1261,7 @@ z = function(e) {
|
|
|
1154
1261
|
* @returns Normalized base path without trailing slash
|
|
1155
1262
|
* @private
|
|
1156
1263
|
*/
|
|
1157
|
-
|
|
1264
|
+
U = function(e) {
|
|
1158
1265
|
return e.endsWith("/") ? e.slice(0, -1) : e;
|
|
1159
1266
|
}, /**
|
|
1160
1267
|
* Registers the health check endpoint.
|
|
@@ -1162,7 +1269,7 @@ R = function(e) {
|
|
|
1162
1269
|
* @param base - Base path for routes
|
|
1163
1270
|
* @private
|
|
1164
1271
|
*/
|
|
1165
|
-
|
|
1272
|
+
q = function(e, s) {
|
|
1166
1273
|
e.get(`${s}/healthz`, async () => ({ ok: !0 }));
|
|
1167
1274
|
}, /**
|
|
1168
1275
|
* Registers the tools status endpoint.
|
|
@@ -1170,7 +1277,7 @@ O = function(e, s) {
|
|
|
1170
1277
|
* @param base - Base path for routes
|
|
1171
1278
|
* @private
|
|
1172
1279
|
*/
|
|
1173
|
-
|
|
1280
|
+
J = function(e, s) {
|
|
1174
1281
|
e.get(`${s}/tools`, async () => this.defaultManager.getStatus());
|
|
1175
1282
|
}, /**
|
|
1176
1283
|
* Registers the MCP configuration discovery endpoint.
|
|
@@ -1178,7 +1285,7 @@ F = function(e, s) {
|
|
|
1178
1285
|
* @param base - Base path for routes
|
|
1179
1286
|
* @private
|
|
1180
1287
|
*/
|
|
1181
|
-
|
|
1288
|
+
G = function(e, s) {
|
|
1182
1289
|
e.get(`${s}/.well-known/mcp-config`, async (t, o) => (o.header("Content-Type", "application/schema+json; charset=utf-8"), this.configSchema ?? {
|
|
1183
1290
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1184
1291
|
title: "MCP Session Configuration",
|
|
@@ -1196,11 +1303,11 @@ V = function(e, s) {
|
|
|
1196
1303
|
* @param base - Base path for routes
|
|
1197
1304
|
* @private
|
|
1198
1305
|
*/
|
|
1199
|
-
|
|
1306
|
+
K = function(e, s) {
|
|
1200
1307
|
e.post(
|
|
1201
1308
|
`${s}/mcp`,
|
|
1202
1309
|
async (t, o) => {
|
|
1203
|
-
const i =
|
|
1310
|
+
const i = f(this, h, E).call(this, t), n = !i.clientId.startsWith("anon-");
|
|
1204
1311
|
let l = n ? this.clientCache.get(i.clientId) : null;
|
|
1205
1312
|
if (!l)
|
|
1206
1313
|
try {
|
|
@@ -1208,30 +1315,30 @@ _ = function(e, s) {
|
|
|
1208
1315
|
d.failedToolsets.length > 0 && console.warn(
|
|
1209
1316
|
`Client ${i.clientId} had ${d.failedToolsets.length} toolsets fail to enable: [${d.failedToolsets.join(", ")}]. Successfully enabled: [${d.allowedToolsets.join(", ")}]`
|
|
1210
1317
|
);
|
|
1211
|
-
const
|
|
1318
|
+
const u = d.sessions;
|
|
1212
1319
|
l = {
|
|
1213
1320
|
server: d.server,
|
|
1214
1321
|
orchestrator: d.orchestrator,
|
|
1215
1322
|
allowedToolsets: d.allowedToolsets,
|
|
1216
1323
|
failedToolsets: d.failedToolsets,
|
|
1217
|
-
sessions:
|
|
1324
|
+
sessions: u instanceof Map ? u : /* @__PURE__ */ new Map()
|
|
1218
1325
|
}, n && this.clientCache.set(i.clientId, l);
|
|
1219
1326
|
} catch (d) {
|
|
1220
1327
|
return console.error(
|
|
1221
1328
|
`Failed to create permission-aware bundle for client ${i.clientId}:`,
|
|
1222
1329
|
d
|
|
1223
|
-
), o.code(403),
|
|
1330
|
+
), o.code(403), f(this, h, Z).call(this, "Access denied");
|
|
1224
1331
|
}
|
|
1225
1332
|
const a = t.headers["mcp-session-id"];
|
|
1226
1333
|
let c;
|
|
1227
1334
|
if (a && l.sessions.get(a))
|
|
1228
1335
|
c = l.sessions.get(a);
|
|
1229
|
-
else if (!a &&
|
|
1230
|
-
const d =
|
|
1231
|
-
c = new
|
|
1336
|
+
else if (!a && D(t.body)) {
|
|
1337
|
+
const d = y();
|
|
1338
|
+
c = new k({
|
|
1232
1339
|
sessionIdGenerator: () => d,
|
|
1233
|
-
onsessioninitialized: (
|
|
1234
|
-
l.sessions.set(
|
|
1340
|
+
onsessioninitialized: (u) => {
|
|
1341
|
+
l.sessions.set(u, c);
|
|
1235
1342
|
}
|
|
1236
1343
|
});
|
|
1237
1344
|
try {
|
|
@@ -1265,7 +1372,7 @@ _ = function(e, s) {
|
|
|
1265
1372
|
* @param base - Base path for routes
|
|
1266
1373
|
* @private
|
|
1267
1374
|
*/
|
|
1268
|
-
|
|
1375
|
+
Q = function(e, s) {
|
|
1269
1376
|
e.get(`${s}/mcp`, async (t, o) => {
|
|
1270
1377
|
const i = t.headers["mcp-client-id"]?.trim(), n = i && i.length > 0 ? i : "";
|
|
1271
1378
|
if (!n)
|
|
@@ -1285,7 +1392,7 @@ B = function(e, s) {
|
|
|
1285
1392
|
* @param base - Base path for routes
|
|
1286
1393
|
* @private
|
|
1287
1394
|
*/
|
|
1288
|
-
|
|
1395
|
+
X = function(e, s) {
|
|
1289
1396
|
e.delete(
|
|
1290
1397
|
`${s}/mcp`,
|
|
1291
1398
|
async (t, o) => {
|
|
@@ -1325,8 +1432,8 @@ Y = function(e, s) {
|
|
|
1325
1432
|
* @returns Client request context with ID and headers
|
|
1326
1433
|
* @private
|
|
1327
1434
|
*/
|
|
1328
|
-
|
|
1329
|
-
const s = e.headers["mcp-client-id"]?.trim(), t = s && s.length > 0 ? s : `anon-${
|
|
1435
|
+
E = function(e) {
|
|
1436
|
+
const s = e.headers["mcp-client-id"]?.trim(), t = s && s.length > 0 ? s : `anon-${y()}`, o = {};
|
|
1330
1437
|
for (const [i, n] of Object.entries(e.headers))
|
|
1331
1438
|
typeof n == "string" && (o[i] = n);
|
|
1332
1439
|
return { clientId: t, headers: o };
|
|
@@ -1338,7 +1445,7 @@ H = function(e) {
|
|
|
1338
1445
|
* @returns JSON-RPC error response object
|
|
1339
1446
|
* @private
|
|
1340
1447
|
*/
|
|
1341
|
-
|
|
1448
|
+
Z = function(e = "Access denied", s = -32e3) {
|
|
1342
1449
|
return {
|
|
1343
1450
|
jsonrpc: "2.0",
|
|
1344
1451
|
error: {
|
|
@@ -1348,7 +1455,7 @@ U = function(e = "Access denied", s = -32e3) {
|
|
|
1348
1455
|
id: null
|
|
1349
1456
|
};
|
|
1350
1457
|
};
|
|
1351
|
-
function
|
|
1458
|
+
function ge(r) {
|
|
1352
1459
|
if (!r) return;
|
|
1353
1460
|
const e = {
|
|
1354
1461
|
namespaceToolsWithSetKey: r.namespaceToolsWithSetKey
|
|
@@ -1363,12 +1470,12 @@ function ae(r) {
|
|
|
1363
1470
|
"Permission-based servers: exposurePolicy.onLimitExceeded is ignored. No toolset limits are enforced."
|
|
1364
1471
|
), e;
|
|
1365
1472
|
}
|
|
1366
|
-
async function
|
|
1473
|
+
async function Ee(r) {
|
|
1367
1474
|
if (!r.permissions)
|
|
1368
1475
|
throw new Error(
|
|
1369
1476
|
"Permission configuration is required for createPermissionBasedMcpServer. Please provide a 'permissions' field in the options."
|
|
1370
1477
|
);
|
|
1371
|
-
if (
|
|
1478
|
+
if (ne(r.permissions), r.startup)
|
|
1372
1479
|
throw new Error(
|
|
1373
1480
|
"Permission-based servers determine toolsets from client permissions. The 'startup' option is not allowed. Remove it from your configuration."
|
|
1374
1481
|
);
|
|
@@ -1376,9 +1483,9 @@ async function pe(r) {
|
|
|
1376
1483
|
throw new Error(
|
|
1377
1484
|
"createPermissionBasedMcpServer: `createServer` (factory) is required"
|
|
1378
1485
|
);
|
|
1379
|
-
const e =
|
|
1486
|
+
const e = ge(
|
|
1380
1487
|
r.exposurePolicy
|
|
1381
|
-
), s = new
|
|
1488
|
+
), s = new ue(r.permissions), t = r.createServer(), o = new w({
|
|
1382
1489
|
server: t,
|
|
1383
1490
|
catalog: r.catalog,
|
|
1384
1491
|
moduleLoaders: r.moduleLoaders,
|
|
@@ -1388,9 +1495,9 @@ async function pe(r) {
|
|
|
1388
1495
|
// No notifications in STATIC mode
|
|
1389
1496
|
startup: { mode: "STATIC", toolsets: [] },
|
|
1390
1497
|
registerMetaTools: !1
|
|
1391
|
-
}), i =
|
|
1498
|
+
}), i = fe(
|
|
1392
1499
|
(l) => {
|
|
1393
|
-
const a = r.createServer(), c = new
|
|
1500
|
+
const a = r.createServer(), c = new w({
|
|
1394
1501
|
server: a,
|
|
1395
1502
|
catalog: r.catalog,
|
|
1396
1503
|
moduleLoaders: r.moduleLoaders,
|
|
@@ -1406,7 +1513,7 @@ async function pe(r) {
|
|
|
1406
1513
|
return { server: a, orchestrator: c };
|
|
1407
1514
|
},
|
|
1408
1515
|
s
|
|
1409
|
-
), n = new
|
|
1516
|
+
), n = new me(
|
|
1410
1517
|
o.getManager(),
|
|
1411
1518
|
i,
|
|
1412
1519
|
r.http,
|
|
@@ -1426,8 +1533,16 @@ async function pe(r) {
|
|
|
1426
1533
|
}
|
|
1427
1534
|
};
|
|
1428
1535
|
}
|
|
1536
|
+
function Ce(r) {
|
|
1537
|
+
return r;
|
|
1538
|
+
}
|
|
1539
|
+
function xe(r) {
|
|
1540
|
+
return r;
|
|
1541
|
+
}
|
|
1429
1542
|
export {
|
|
1430
|
-
|
|
1431
|
-
|
|
1543
|
+
Se as createMcpServer,
|
|
1544
|
+
Ee as createPermissionBasedMcpServer,
|
|
1545
|
+
Ce as defineEndpoint,
|
|
1546
|
+
xe as definePermissionAwareEndpoint
|
|
1432
1547
|
};
|
|
1433
1548
|
//# sourceMappingURL=index.js.map
|