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/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,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 Q(
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 T({
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 X(r) {
877
- Z(r), ee(r), se(r), te(r);
964
+ function ne(r) {
965
+ ae(r), le(r), ce(r), de(r);
878
966
  }
879
- function Z(r) {
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 ee(r) {
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 se(r) {
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 te(r) {
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
- oe(r.staticMap);
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 oe(r) {
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 f, L, j, N, k, D;
924
- class re {
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
- y(this, f);
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 = u(this, f, L).call(this, s) : t = u(this, f, N).call(this, e), Array.isArray(t) || (console.warn(
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
- f = new WeakSet(), /**
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
- L = function(e) {
1077
+ F = function(e) {
990
1078
  if (!e)
991
1079
  return [];
992
- const s = u(this, f, j).call(this, e, this.normalizedHeaderName);
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
- j = function(e, s) {
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
- N = function(e) {
1113
+ B = function(e) {
1026
1114
  if (this.config.resolver) {
1027
- const s = u(this, f, k).call(this, e);
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 = u(this, f, D).call(this, e);
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
- k = function(e) {
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
- D = function(e) {
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 ie(r, e) {
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, z, R, O, F, V, _, B, Y, H, U;
1093
- class ne {
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
- y(this, h);
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
- u(this, h, z).call(this, n);
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 ?? 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;
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
- z = function(e) {
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
- R = function(e) {
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
- O = function(e, s) {
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
- F = function(e, s) {
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
- V = function(e, s) {
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
- _ = function(e, s) {
1306
+ K = function(e, s) {
1200
1307
  e.post(
1201
1308
  `${s}/mcp`,
1202
1309
  async (t, o) => {
1203
- const i = u(this, h, H).call(this, t), n = !i.clientId.startsWith("anon-");
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 m = d.sessions;
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: m instanceof Map ? m : /* @__PURE__ */ new Map()
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), u(this, h, U).call(this, "Access denied");
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 && I(t.body)) {
1230
- const d = v();
1231
- c = new E({
1336
+ else if (!a && D(t.body)) {
1337
+ const d = y();
1338
+ c = new k({
1232
1339
  sessionIdGenerator: () => d,
1233
- onsessioninitialized: (m) => {
1234
- l.sessions.set(m, c);
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
- B = function(e, s) {
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
- Y = function(e, s) {
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
- H = function(e) {
1329
- const s = e.headers["mcp-client-id"]?.trim(), t = s && s.length > 0 ? s : `anon-${v()}`, o = {};
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
- U = function(e = "Access denied", s = -32e3) {
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 ae(r) {
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 pe(r) {
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 (X(r.permissions), r.startup)
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 = ae(
1486
+ const e = ge(
1380
1487
  r.exposurePolicy
1381
- ), s = new re(r.permissions), t = r.createServer(), o = new T({
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 = ie(
1498
+ }), i = fe(
1392
1499
  (l) => {
1393
- const a = r.createServer(), c = new T({
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 ne(
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
- ge as createMcpServer,
1431
- pe as createPermissionBasedMcpServer
1543
+ Se as createMcpServer,
1544
+ Ee as createPermissionBasedMcpServer,
1545
+ Ce as defineEndpoint,
1546
+ xe as definePermissionAwareEndpoint
1432
1547
  };
1433
1548
  //# sourceMappingURL=index.js.map