jxp 4.2.0 → 5.0.2

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.
Files changed (104) hide show
  1. package/.env.sample +16 -0
  2. package/README.md +2 -0
  3. package/dist/bin/mcp-stdio-bridge.d.ts +3 -0
  4. package/dist/bin/mcp-stdio-bridge.d.ts.map +1 -0
  5. package/dist/bin/mcp-stdio-bridge.js +87 -0
  6. package/dist/bin/mcp-stdio-bridge.js.map +1 -0
  7. package/dist/bin/server.d.ts.map +1 -1
  8. package/dist/bin/server.js +2 -1
  9. package/dist/bin/server.js.map +1 -1
  10. package/dist/globals.d.ts +2 -2
  11. package/dist/globals.d.ts.map +1 -1
  12. package/dist/libs/docs-auth.d.ts.map +1 -1
  13. package/dist/libs/docs-auth.js +4 -2
  14. package/dist/libs/docs-auth.js.map +1 -1
  15. package/dist/libs/docs.js +74 -43
  16. package/dist/libs/docs.js.map +1 -1
  17. package/dist/libs/jxp.d.ts.map +1 -1
  18. package/dist/libs/jxp.js +22 -321
  19. package/dist/libs/jxp.js.map +1 -1
  20. package/dist/libs/load-config.d.ts +1 -1
  21. package/dist/libs/load-config.d.ts.map +1 -1
  22. package/dist/libs/load-config.js +2 -2
  23. package/dist/libs/load-config.js.map +1 -1
  24. package/dist/libs/mcp/auth.d.ts +4 -0
  25. package/dist/libs/mcp/auth.d.ts.map +1 -0
  26. package/dist/libs/mcp/auth.js +49 -0
  27. package/dist/libs/mcp/auth.js.map +1 -0
  28. package/dist/libs/mcp/config.d.ts +21 -0
  29. package/dist/libs/mcp/config.d.ts.map +1 -0
  30. package/dist/libs/mcp/config.js +66 -0
  31. package/dist/libs/mcp/config.js.map +1 -0
  32. package/dist/libs/mcp/context.d.ts +5 -0
  33. package/dist/libs/mcp/context.d.ts.map +1 -0
  34. package/dist/libs/mcp/context.js +14 -0
  35. package/dist/libs/mcp/context.js.map +1 -0
  36. package/dist/libs/mcp/describe.d.ts +13 -0
  37. package/dist/libs/mcp/describe.d.ts.map +1 -0
  38. package/dist/libs/mcp/describe.js +36 -0
  39. package/dist/libs/mcp/describe.js.map +1 -0
  40. package/dist/libs/mcp/docs_auth.d.ts +5 -0
  41. package/dist/libs/mcp/docs_auth.d.ts.map +1 -0
  42. package/dist/libs/mcp/docs_auth.js +36 -0
  43. package/dist/libs/mcp/docs_auth.js.map +1 -0
  44. package/dist/libs/mcp/dynamic_import.d.ts +6 -0
  45. package/dist/libs/mcp/dynamic_import.d.ts.map +1 -0
  46. package/dist/libs/mcp/dynamic_import.js +12 -0
  47. package/dist/libs/mcp/dynamic_import.js.map +1 -0
  48. package/dist/libs/mcp/execute_tool.d.ts +10 -0
  49. package/dist/libs/mcp/execute_tool.d.ts.map +1 -0
  50. package/dist/libs/mcp/execute_tool.js +65 -0
  51. package/dist/libs/mcp/execute_tool.js.map +1 -0
  52. package/dist/libs/mcp/guides/default.md +67 -0
  53. package/dist/libs/mcp/guides.d.ts +14 -0
  54. package/dist/libs/mcp/guides.d.ts.map +1 -0
  55. package/dist/libs/mcp/guides.js +127 -0
  56. package/dist/libs/mcp/guides.js.map +1 -0
  57. package/dist/libs/mcp/model_visibility.d.ts +31 -0
  58. package/dist/libs/mcp/model_visibility.d.ts.map +1 -0
  59. package/dist/libs/mcp/model_visibility.js +73 -0
  60. package/dist/libs/mcp/model_visibility.js.map +1 -0
  61. package/dist/libs/mcp/mount.d.ts +10 -0
  62. package/dist/libs/mcp/mount.d.ts.map +1 -0
  63. package/dist/libs/mcp/mount.js +68 -0
  64. package/dist/libs/mcp/mount.js.map +1 -0
  65. package/dist/libs/mcp/read_service.d.ts +23 -0
  66. package/dist/libs/mcp/read_service.d.ts.map +1 -0
  67. package/dist/libs/mcp/read_service.js +141 -0
  68. package/dist/libs/mcp/read_service.js.map +1 -0
  69. package/dist/libs/mcp/server.d.ts +23 -0
  70. package/dist/libs/mcp/server.d.ts.map +1 -0
  71. package/dist/libs/mcp/server.js +111 -0
  72. package/dist/libs/mcp/server.js.map +1 -0
  73. package/dist/libs/read_handlers.d.ts +13 -0
  74. package/dist/libs/read_handlers.d.ts.map +1 -0
  75. package/dist/libs/read_handlers.js +381 -0
  76. package/dist/libs/read_handlers.js.map +1 -0
  77. package/dist/libs/request_log.d.ts +2 -0
  78. package/dist/libs/request_log.d.ts.map +1 -1
  79. package/dist/libs/request_log.js +7 -1
  80. package/dist/libs/request_log.js.map +1 -1
  81. package/dist/libs/schema.d.ts.map +1 -1
  82. package/dist/libs/schema.js +5 -8
  83. package/dist/libs/schema.js.map +1 -1
  84. package/dist/libs/schema_description.d.ts +7 -1
  85. package/dist/libs/schema_description.d.ts.map +1 -1
  86. package/dist/libs/schema_description.js +24 -25
  87. package/dist/libs/schema_description.js.map +1 -1
  88. package/dist/libs/schema_serialize.d.ts +3 -0
  89. package/dist/libs/schema_serialize.d.ts.map +1 -0
  90. package/dist/libs/schema_serialize.js +45 -0
  91. package/dist/libs/schema_serialize.js.map +1 -0
  92. package/dist/types/jxp-config.d.ts +7 -0
  93. package/dist/types/jxp-config.d.ts.map +1 -1
  94. package/docs/changelog.md +61 -0
  95. package/docs/index.md +1 -0
  96. package/docs/mcp.md +134 -0
  97. package/mkdocs.yml +1 -0
  98. package/package.json +31 -26
  99. package/templates/assets/docs.css +107 -0
  100. package/templates/assets/mcp-console.js +343 -0
  101. package/templates/index.pug +37 -0
  102. package/templates/mcp.pug +112 -0
  103. package/templates/navbar.pug +2 -0
  104. package/templates/sidebar.pug +4 -0
package/dist/libs/jxp.js CHANGED
@@ -17,7 +17,6 @@ const ws = require("./ws");
17
17
  const modeldir = require("./modeldir");
18
18
  const query_manipulation = require("./query_manipulation");
19
19
  const corsMiddleware = require('restify-cors-middleware2');
20
- const { Parser: CsvParser } = require('@json2csv/plainjs');
21
20
  const cache = require("./cache");
22
21
  const query_limits = require("./query_limits");
23
22
  const query_sanitize = require("./query_sanitize");
@@ -27,14 +26,19 @@ const call_guard = require("./call_guard");
27
26
  const response_sanitize = require("./response_sanitize");
28
27
  const link_index = require("./link_index");
29
28
  const { safeErrorMessage } = require("./safe_error");
30
- const { logRequestError, logAndThrow } = require("./request_log");
29
+ const { logRequestError, logAndThrow, sanitizeRequestUrl } = require("./request_log");
31
30
  const index_diagnostics = require("./index_diagnostics");
32
31
  const builtin_models = require("./builtin_models");
32
+ const read_handlers = require("./read_handlers");
33
33
  const schemaModule = require("./schema");
34
34
  global.JXPSchema = schemaModule.default || schemaModule;
35
35
  var models = {};
36
36
  var ops = 0;
37
37
  var debug = false;
38
+ const SAFE_COMBINED_LOG_FORMAT = ':remote-addr - :remote-user [:date[clf]] ":method :safe-url HTTP/:http-version" ' +
39
+ ':status :res[content-length] ":safe-referrer" ":user-agent"';
40
+ morgan.token("safe-url", (req) => sanitizeRequestUrl(req.originalUrl || req.url || ""));
41
+ morgan.token("safe-referrer", (req) => sanitizeRequestUrl(req.headers?.referer || req.headers?.referrer || "-"));
38
42
  const USER_PRIVILEGE_FIELDS = ["admin", "password", "groups"];
39
43
  function getStripFields(req) {
40
44
  return req.config?.security?.strip_fields || ["password"];
@@ -129,20 +133,11 @@ const outputJSON = async (req, res) => {
129
133
  };
130
134
  // Outputs whatever is in res.result as CSV
131
135
  const outputCSV = (req, res, next) => {
132
- const opts = { "flatten": true };
133
136
  if (!res.result.data) {
134
137
  throw new errors.InternalServerError("Error generating CSV");
135
138
  }
136
139
  try {
137
- const data = res.result.data.map(row => row._doc);
138
- if (!data.length) {
139
- throw ("");
140
- }
141
- res.writeHead(200, {
142
- 'Content-Type': 'text/csv',
143
- 'Content-Disposition': 'attachment; filename=export.csv'
144
- });
145
- const csv = new CsvParser(opts).parse(data);
140
+ const csv = read_handlers.resultToCsv(res.result);
146
141
  const limits = query_limits.getLimits(req);
147
142
  if (limits.max_response_bytes > 0) {
148
143
  const size = Buffer.byteLength(csv, "utf8");
@@ -153,6 +148,10 @@ const outputCSV = (req, res, next) => {
153
148
  throw err;
154
149
  }
155
150
  }
151
+ res.writeHead(200, {
152
+ "Content-Type": "text/csv",
153
+ "Content-Disposition": "attachment; filename=export.csv",
154
+ });
156
155
  res.end(csv);
157
156
  next();
158
157
  }
@@ -167,142 +166,32 @@ const outputCSV = (req, res, next) => {
167
166
  const actionGet = async (req, res) => {
168
167
  const opname = `get ${req.modelname} ${ops++}`;
169
168
  console.time(opname);
170
- let filters = {};
171
- try {
172
- filters = parseFilter(req.query.filter);
173
- filters = query_sanitize.sanitizeFilter(filters, getSecurityOpts(req));
174
- }
175
- catch (err) {
176
- logRequestError(req, res, err, "filter");
177
- if (err instanceof errors.BadRequestError) {
178
- throw err;
179
- }
180
- if (err instanceof errors.ForbiddenError) {
181
- throw err;
182
- }
183
- throw new errors.InternalServerError(safeErrorMessage(err));
184
- }
185
- const search = query_sanitize.parseSearchObject(req.query.search);
186
- for (const i in search) {
187
- filters[i] = search[i];
188
- }
189
- let countquery = filters;
190
- let qcount = req.Model.find(filters);
191
- let q = req.Model.find(filters);
192
- let checkDeleted = [{ _deleted: false }, { _deleted: null }];
193
- if (!req.query.showDeleted) {
194
- countquery = Object.assign({ $or: checkDeleted }, countquery);
195
- qcount.or(checkDeleted);
196
- q.or(checkDeleted);
197
- }
198
- if (req.query.search) {
199
- // console.log({ search: req.query.search });
200
- q = req.Model.find({ $text: { $search: req.query.search } }, { score: { $meta: "textScore" } }).sort({ score: { $meta: "textScore" } });
201
- countquery = Object.assign({ $text: { $search: req.query.search } }, countquery);
202
- qcount = req.Model.find({ $text: { $search: req.query.search } });
203
- }
204
- if (res.user) {
205
- q.options = ({ user: res.user });
206
- }
207
169
  try {
208
- const estimatedCount = await req.Model.estimatedDocumentCount();
209
- const result = {};
210
- const { limit: effectiveLimit, limitCapped, filterExemption } = query_limits.enforceListLimit(req, estimatedCount, res, { result, bodyQuery: filters });
211
- let count = -1;
212
- if (query_limits.shouldRunCount(req, { filterExemption, limitCapped })) {
213
- if (estimatedCount < 100000 && Object.keys(countquery).length !== 0) {
214
- count = await qcount.countDocuments();
215
- }
216
- else {
217
- count = estimatedCount;
218
- }
219
- }
220
- if (count >= 0) {
221
- result.count = count;
222
- }
223
- query_limits.applyListPagination(q, result, req, effectiveLimit, count >= 0 ? count : 0, changeUrlParams);
224
- if (req.query.sort) {
225
- q.sort(req.query.sort);
226
- result.sort = req.query.sort;
227
- }
228
- if (req.query.populate) {
229
- if ((typeof req.query.populate === "object") && !Array.isArray(req.query.populate)) {
230
- for (let i in req.query.populate) {
231
- q.populate(i, req.query.populate[i].replace(/,/g, " "));
232
- }
233
- }
234
- else {
235
- q.populate(req.query.populate);
236
- }
237
- result.populate = req.query.populate;
238
- }
239
170
  if (req.query.autopopulate) {
240
171
  res.header("jxp-autopopulate-warning", "expensive");
241
- for (let key in req.Model.schema.paths) {
242
- const dirpath = req.Model.schema.paths[key];
243
- if (dirpath.instance == "ObjectID" && dirpath.options.link) {
244
- q.populate(String(dirpath.options.map_to || dirpath.options.virtual || dirpath.options.link.toLowerCase()));
245
- }
246
- }
247
- result.autopopulate = true;
248
172
  }
249
- if (req.query.fields) {
250
- const fields = req.query.fields.split(",");
251
- const select = {};
252
- fields.forEach(field => {
253
- select[field] = 1;
254
- });
255
- q.select(select);
256
- }
257
- if (req.query.search) {
258
- result.search = req.query.search;
259
- }
260
- result.data = await q.exec();
261
- response_sanitize.sanitizeListResult(result, getStripFields(req));
262
- query_limits.finalizeListPagination(result, req, Array.isArray(result.data) ? result.data.length : 0, effectiveLimit, count, changeUrlParams);
263
- query_limits.enforceResponseSize(result, req, res);
264
- res.result = result;
173
+ await read_handlers.executeList(req, res);
265
174
  if (debug)
266
175
  console.timeEnd(opname);
267
176
  }
268
177
  catch (err) {
269
178
  if (debug)
270
179
  console.timeEnd(opname);
271
- if (!(err instanceof errors.BadRequestError) &&
272
- !(err instanceof errors.ForbiddenError) &&
273
- !(err instanceof errors.PayloadTooLargeError)) {
274
- logRequestError(req, res, err, "get");
275
- }
276
- if (err instanceof errors.BadRequestError) {
277
- throw err;
278
- }
279
- if (err instanceof errors.ForbiddenError) {
280
- throw err;
281
- }
282
- if (err instanceof errors.PayloadTooLargeError) {
283
- throw err;
284
- }
285
- if (err.code)
286
- throw err;
287
- throw new errors.InternalServerError(safeErrorMessage(err));
180
+ throw err;
288
181
  }
289
182
  };
290
183
  const actionGetOne = async (req, res) => {
291
184
  const opname = `getOne ${req.modelname}/${req.params.item_id} ${ops++}`;
292
185
  console.time(opname);
293
186
  try {
294
- const data = await getOne(req.Model, req.params.item_id, req.query, { user: res.user });
295
- res.result = { data };
187
+ await read_handlers.executeGetOne(req, res);
296
188
  if (debug)
297
189
  console.timeEnd(opname);
298
190
  }
299
191
  catch (err) {
300
- logRequestError(req, res, err, "getOne");
301
192
  if (debug)
302
193
  console.timeEnd(opname);
303
- if (err.code)
304
- throw err;
305
- throw new errors.InternalServerError(safeErrorMessage(err));
194
+ throw err;
306
195
  }
307
196
  };
308
197
  const actionPost = async (req, res) => {
@@ -487,38 +376,15 @@ const actionDelete = async (req, res) => {
487
376
  const actionCount = async (req, res) => {
488
377
  const opname = `count ${req.modelname} ${ops++}`;
489
378
  console.time(opname);
490
- let filters = {};
491
379
  try {
492
- filters = parseFilter(req.query.filter);
493
- filters = query_sanitize.sanitizeFilter(filters, getSecurityOpts(req));
494
- }
495
- catch (err) {
496
- logRequestError(req, res, err, "filter");
497
- if (err instanceof errors.BadRequestError || err instanceof errors.ForbiddenError) {
498
- throw err;
499
- }
500
- throw new errors.InternalServerError(safeErrorMessage(err));
501
- }
502
- const search = query_sanitize.parseSearchObject(req.query.search);
503
- for (const i in search) {
504
- filters[i] = search[i];
505
- }
506
- if (!req.query.showDeleted) {
507
- filters = Object.assign({ $or: [{ _deleted: false }, { _deleted: null }] }, filters);
508
- }
509
- try {
510
- const count = await req.Model.countDocuments(filters).exec();
511
- res.result = { count };
380
+ await read_handlers.executeCount(req, res);
512
381
  if (debug)
513
382
  console.timeEnd(opname);
514
383
  }
515
384
  catch (err) {
516
- logRequestError(req, res, err, "count");
517
385
  if (debug)
518
386
  console.timeEnd(opname);
519
- if (err.code)
520
- throw err;
521
- throw new errors.InternalServerError(safeErrorMessage(err));
387
+ throw err;
522
388
  }
523
389
  };
524
390
  const actionCall = async (req, res) => {
@@ -771,175 +637,6 @@ const actionBulkWrite = async (req, res) => {
771
637
  // });
772
638
  // };
773
639
  // Utitlities
774
- const getOne = async (Model, item_id, params, options) => {
775
- const query = Model.findById(item_id, {}, options);
776
- if (params.populate) {
777
- if ((typeof params.populate === "object") && !Array.isArray(params.populate)) {
778
- for (let i in params.populate) {
779
- query.populate(i, params.populate[i].replace(/,/g, " "));
780
- }
781
- }
782
- else {
783
- query.populate(params.populate);
784
- }
785
- }
786
- if (params.autopopulate) {
787
- for (let key in Model.schema.paths) {
788
- var dirpath = Model.schema.paths[key];
789
- if (dirpath.instance == "ObjectID" && dirpath.options.link) {
790
- query.populate(String(dirpath.options.map_to || dirpath.options.virtual || dirpath.options.link.toLowerCase()));
791
- }
792
- }
793
- }
794
- try {
795
- var item = await query.exec();
796
- if (!item) {
797
- // console.error("Could not find document");
798
- throw new errors.NotFoundError(`Could not find document ${item_id} on ${Model.modelName}`);
799
- }
800
- if (item._deleted && !params.showDeleted) {
801
- // console.error("Document is deleted");
802
- throw new errors.NotFoundError(`Document ${item_id} is deleted on ${Model.modelName}`);
803
- }
804
- return response_sanitize.sanitizeDocument(item);
805
- }
806
- catch (err) {
807
- if (err.code)
808
- throw err;
809
- throw new errors.InternalServerError(safeErrorMessage(err));
810
- }
811
- };
812
- // Helper function to check if a string is an ISO date string
813
- function isISODateString(str) {
814
- if (typeof str !== 'string')
815
- return false;
816
- const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
817
- if (!isoDateRegex.test(str))
818
- return false;
819
- const date = new Date(str);
820
- if (isNaN(date.getTime())) {
821
- throw new errors.BadRequestError("Invalid date format");
822
- }
823
- return true;
824
- }
825
- const parseFilter = (filter, depth = 0) => {
826
- const MAX_DEPTH = 10;
827
- if (!filter)
828
- return {};
829
- if (depth > MAX_DEPTH) {
830
- throw new errors.BadRequestError("Maximum filter depth exceeded");
831
- }
832
- if (typeof filter !== "object" || filter === null)
833
- return filter;
834
- // Handle arrays by merging their operators
835
- if (Array.isArray(filter)) {
836
- const result = {};
837
- filter.forEach(item => {
838
- if (typeof item === "string" && item.includes(":")) {
839
- const parts = item.split(":");
840
- const key = parts[0];
841
- const value = parts.slice(1).join(":");
842
- if (key.startsWith("$")) {
843
- try {
844
- if (isISODateString(value)) {
845
- result[key] = new Date(value);
846
- }
847
- else {
848
- result[key] = value;
849
- }
850
- }
851
- catch (err) {
852
- if (err instanceof errors.BadRequestError) {
853
- throw err;
854
- }
855
- throw new errors.BadRequestError("Invalid date format");
856
- }
857
- }
858
- }
859
- });
860
- return result;
861
- }
862
- // Create a new object to avoid modifying the input
863
- const parsedFilter = {};
864
- for (let i in filter) {
865
- if (filter[i] === "false") {
866
- parsedFilter[i] = false;
867
- continue;
868
- }
869
- if (filter[i] === "true") {
870
- parsedFilter[i] = true;
871
- continue;
872
- }
873
- if (typeof filter[i] === "string") {
874
- try {
875
- if (isISODateString(filter[i])) {
876
- parsedFilter[i] = new Date(filter[i]);
877
- continue;
878
- }
879
- }
880
- catch (err) {
881
- if (err instanceof errors.BadRequestError) {
882
- throw err;
883
- }
884
- throw new errors.BadRequestError("Invalid date format");
885
- }
886
- if (filter[i].includes(":")) {
887
- const parts = filter[i].split(":");
888
- const key = parts[0];
889
- const value = parts.slice(1).join(":");
890
- if (key.startsWith("$")) {
891
- try {
892
- if (isISODateString(value)) {
893
- if (!parsedFilter[i])
894
- parsedFilter[i] = {};
895
- parsedFilter[i][key] = new Date(value);
896
- }
897
- else if (value.startsWith("[") && value.endsWith("]")) {
898
- if (!parsedFilter[i])
899
- parsedFilter[i] = {};
900
- parsedFilter[i][key] = value.slice(1, -1).split(",");
901
- }
902
- else if (key === "$regex" && value.startsWith("/")) {
903
- if (!parsedFilter[i])
904
- parsedFilter[i] = {};
905
- const match = value.match(/^\/(.+?)\/([gimy]*)$/);
906
- if (match) {
907
- parsedFilter[i][key] = new RegExp(match[1], match[2]);
908
- }
909
- }
910
- else {
911
- if (!parsedFilter[i])
912
- parsedFilter[i] = {};
913
- parsedFilter[i][key] = value;
914
- }
915
- }
916
- catch (err) {
917
- if (err instanceof errors.BadRequestError) {
918
- throw err;
919
- }
920
- throw new errors.BadRequestError("Invalid date format");
921
- }
922
- }
923
- else {
924
- parsedFilter[i] = filter[i];
925
- }
926
- }
927
- else {
928
- parsedFilter[i] = filter[i];
929
- }
930
- }
931
- else if (Array.isArray(filter[i])) {
932
- parsedFilter[i] = parseFilter(filter[i], depth + 1);
933
- }
934
- else if (typeof filter[i] === "object") {
935
- parsedFilter[i] = parseFilter(filter[i], depth + 1);
936
- }
937
- else {
938
- parsedFilter[i] = filter[i];
939
- }
940
- }
941
- return parsedFilter;
942
- };
943
640
  const _deSerialize = (data) => {
944
641
  function assign(obj, keyPath, value) {
945
642
  const MAX_DEPTH = 20; // Prevent excessive nesting
@@ -1132,7 +829,7 @@ const JXP = function (options) {
1132
829
  console.log("Logging to", config.log);
1133
830
  }
1134
831
  var accessLogStream = fs.createWriteStream(config.log, { flags: "a" });
1135
- server.use(morgan("combined", { stream: accessLogStream }));
832
+ server.use(morgan(SAFE_COMBINED_LOG_FORMAT, { stream: accessLogStream }));
1136
833
  // CORS
1137
834
  const corsOrigins = config.cors?.origins?.length ? config.cors.origins : ["*"];
1138
835
  const cors = corsMiddleware({
@@ -1221,6 +918,8 @@ const JXP = function (options) {
1221
918
  server.post("/docs/logout", docsAuth.logout);
1222
919
  server.get("/docs/assets/:file", docs.serveAsset.bind(docs));
1223
920
  server.get("/docs/api", docsAuth.docsAccessMiddleware, docs.apiIndex.bind(docs));
921
+ server.get("/docs/mcp", docsAuth.docsAccessMiddleware, docs.mcpPlayground.bind(docs));
922
+ server.post("/docs/mcp/call", docsAuth.docsAccessMiddleware, docs.mcpCall.bind(docs));
1224
923
  server.get("/docs/diagnostics", docsAuth.docsAccessMiddleware, docs.diagnostics.bind(docs));
1225
924
  server.get("/docs/md/:md_doc", docs.md.bind(docs));
1226
925
  server.get("/docs/model/:modelname", docsAuth.docsAccessMiddleware, docs.model.bind(docs));
@@ -1285,6 +984,8 @@ const JXP = function (options) {
1285
984
  server.get("/diagnostics/indexes", security.login, security.admin_only, actionDiagnosticsIndexes, outputJSON);
1286
985
  server.get("/diagnostics/queries", security.login, security.admin_only, actionDiagnosticsQueries, outputJSON);
1287
986
  server.post("/diagnostics/indexes/sync", security.login, security.admin_only, actionDiagnosticsIndexesSync, outputJSON);
987
+ const { mountMcp } = require("./mcp/mount");
988
+ mountMcp(server, { config, models });
1288
989
  return server;
1289
990
  };
1290
991
  module.exports = JXP;