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/dist/index.js CHANGED
@@ -1,16 +1,16 @@
1
- var A = (r) => {
1
+ var P = (r) => {
2
2
  throw TypeError(r);
3
3
  };
4
- var W = (r, e, s) => e.has(r) || A("Cannot " + s);
5
- var y = (r, e, s) => e.has(r) ? A("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(r) : e.set(r, s);
6
- var u = (r, e, s) => (W(r, e, "access private method"), s);
7
- import { z as w } from "zod";
8
- import M from "fastify";
9
- import x from "@fastify/cors";
10
- import { randomUUID as v } from "node:crypto";
11
- import { StreamableHTTPServerTransport as E } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
12
- import { isInitializeRequest as I } from "@modelcontextprotocol/sdk/types.js";
13
- const S = {
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 q {
21
+ class se {
22
22
  constructor(e = {}) {
23
23
  this.keys = {
24
- dynamic: e.keys?.dynamic ?? S.dynamic,
25
- toolsets: e.keys?.toolsets ?? S.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 J {
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 C extends Error {
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 P {
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 C(
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 C(
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 G {
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 P({ namespaceWithToolset: !0 });
218
+ this.activeToolsets = /* @__PURE__ */ new Set(), this.server = e.server, this.resolver = e.resolver, this.context = e.context, this.onToolsListChanged = e.onToolsListChanged, this.exposurePolicy = e.exposurePolicy, this.toolRegistry = e.toolRegistry ?? new z({ namespaceWithToolset: !0 });
219
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 K(r, e, s) {
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: w.string().describe("Toolset 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: w.string().describe("Toolset 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: w.string().describe("Toolset 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 T {
489
+ class w {
490
490
  constructor(e) {
491
- this.initError = null, this.toolsetValidator = new q();
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 J({
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 P({
497
+ const o = new z({
498
498
  namespaceWithToolset: e.exposurePolicy?.namespaceToolsWithSetKey ?? !0
499
499
  });
500
- this.manager = new G({
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 && K(e.server, this.manager, { mode: this.mode });
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 p, b;
585
- class $ {
584
+ var v, S;
585
+ class O {
586
586
  constructor(e = {}) {
587
- y(this, p);
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), u(this, p, b).call(this, e, s.resource));
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
- u(this, p, b).call(this, s, t.resource);
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
- p = new WeakSet(), /**
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
- b = function(e, s) {
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
- class Q {
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 ?? M({ logger: this.options.logger });
691
- this.options.cors && await e.register(x, { origin: !0 });
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-${v()}`, l = !n.startsWith("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 m = this.createBundle(), g = m.sessions;
794
+ const u = this.createBundle(), p = u.sessions;
709
795
  a = {
710
- server: m.server,
711
- orchestrator: m.orchestrator,
712
- sessions: g instanceof Map ? g : /* @__PURE__ */ new Map()
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 && I(t.body)) {
720
- const m = v();
721
- d = new E({
722
- sessionIdGenerator: () => m,
723
- onsessioninitialized: (g) => {
724
- a.sessions.set(g, d);
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 ge(r) {
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 T({
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
- }), l = new Q(
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 T({
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 X(r) {
877
- Z(r), ee(r), se(r), te(r);
966
+ function ne(r) {
967
+ ae(r), le(r), ce(r), de(r);
878
968
  }
879
- function Z(r) {
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 ee(r) {
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 se(r) {
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 te(r) {
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
- oe(r.staticMap);
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 oe(r) {
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 f, L, j, N, k, D;
924
- class re {
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
- y(this, f);
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 = u(this, f, L).call(this, s) : t = u(this, f, N).call(this, e), Array.isArray(t) || (console.warn(
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
- f = new WeakSet(), /**
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
- L = function(e) {
1079
+ F = function(e) {
990
1080
  if (!e)
991
1081
  return [];
992
- const s = u(this, f, j).call(this, e, this.normalizedHeaderName);
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
- j = function(e, s) {
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
- N = function(e) {
1115
+ B = function(e) {
1026
1116
  if (this.config.resolver) {
1027
- const s = u(this, f, k).call(this, e);
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 = u(this, f, D).call(this, e);
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
- k = function(e) {
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
- D = function(e) {
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 ie(r, e) {
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, z, R, O, F, V, _, B, Y, H, U;
1093
- class ne {
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
- y(this, h);
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
- u(this, h, z).call(this, n);
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 ?? M({ logger: this.options.logger });
1123
- this.options.cors && await e.register(x, { origin: !0 });
1124
- const s = u(this, h, R).call(this, this.options.basePath);
1125
- u(this, h, O).call(this, e, s), u(this, h, F).call(this, e, s), u(this, h, V).call(this, e, s), u(this, h, _).call(this, e, s), u(this, h, B).call(this, e, s), u(this, h, Y).call(this, e, s), this.options.app || await e.listen({ host: this.options.host, port: this.options.port }), this.app = e;
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
- z = function(e) {
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
- R = function(e) {
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
- O = function(e, s) {
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
- F = function(e, s) {
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
- V = function(e, s) {
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
- _ = function(e, s) {
1308
+ K = function(e, s) {
1200
1309
  e.post(
1201
1310
  `${s}/mcp`,
1202
1311
  async (t, o) => {
1203
- const i = u(this, h, H).call(this, t), n = !i.clientId.startsWith("anon-");
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 m = d.sessions;
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: m instanceof Map ? m : /* @__PURE__ */ new Map()
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), u(this, h, U).call(this, "Access denied");
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 && I(t.body)) {
1230
- const d = v();
1231
- c = new E({
1338
+ else if (!a && D(t.body)) {
1339
+ const d = y();
1340
+ c = new k({
1232
1341
  sessionIdGenerator: () => d,
1233
- onsessioninitialized: (m) => {
1234
- l.sessions.set(m, c);
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
- B = function(e, s) {
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
- Y = function(e, s) {
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
- H = function(e) {
1329
- const s = e.headers["mcp-client-id"]?.trim(), t = s && s.length > 0 ? s : `anon-${v()}`, o = {};
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
- U = function(e = "Access denied", s = -32e3) {
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 ae(r) {
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 pe(r) {
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 (X(r.permissions), r.startup)
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 = ae(
1488
+ const e = ge(
1380
1489
  r.exposurePolicy
1381
- ), s = new re(r.permissions), t = r.createServer(), o = new T({
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 = ie(
1500
+ }), i = fe(
1392
1501
  (l) => {
1393
- const a = r.createServer(), c = new T({
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 ne(
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
- ge as createMcpServer,
1431
- pe as createPermissionBasedMcpServer
1545
+ Se as createMcpServer,
1546
+ Ee as createPermissionBasedMcpServer,
1547
+ Ce as defineEndpoint,
1548
+ xe as definePermissionAwareEndpoint
1432
1549
  };
1433
1550
  //# sourceMappingURL=index.js.map