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.
- package/.env.sample +16 -0
- package/README.md +2 -0
- package/dist/bin/mcp-stdio-bridge.d.ts +3 -0
- package/dist/bin/mcp-stdio-bridge.d.ts.map +1 -0
- package/dist/bin/mcp-stdio-bridge.js +87 -0
- package/dist/bin/mcp-stdio-bridge.js.map +1 -0
- package/dist/bin/server.d.ts.map +1 -1
- package/dist/bin/server.js +2 -1
- package/dist/bin/server.js.map +1 -1
- package/dist/globals.d.ts +2 -2
- package/dist/globals.d.ts.map +1 -1
- package/dist/libs/docs-auth.d.ts.map +1 -1
- package/dist/libs/docs-auth.js +4 -2
- package/dist/libs/docs-auth.js.map +1 -1
- package/dist/libs/docs.js +74 -43
- package/dist/libs/docs.js.map +1 -1
- package/dist/libs/jxp.d.ts.map +1 -1
- package/dist/libs/jxp.js +22 -321
- package/dist/libs/jxp.js.map +1 -1
- package/dist/libs/load-config.d.ts +1 -1
- package/dist/libs/load-config.d.ts.map +1 -1
- package/dist/libs/load-config.js +2 -2
- package/dist/libs/load-config.js.map +1 -1
- package/dist/libs/mcp/auth.d.ts +4 -0
- package/dist/libs/mcp/auth.d.ts.map +1 -0
- package/dist/libs/mcp/auth.js +49 -0
- package/dist/libs/mcp/auth.js.map +1 -0
- package/dist/libs/mcp/config.d.ts +21 -0
- package/dist/libs/mcp/config.d.ts.map +1 -0
- package/dist/libs/mcp/config.js +66 -0
- package/dist/libs/mcp/config.js.map +1 -0
- package/dist/libs/mcp/context.d.ts +5 -0
- package/dist/libs/mcp/context.d.ts.map +1 -0
- package/dist/libs/mcp/context.js +14 -0
- package/dist/libs/mcp/context.js.map +1 -0
- package/dist/libs/mcp/describe.d.ts +13 -0
- package/dist/libs/mcp/describe.d.ts.map +1 -0
- package/dist/libs/mcp/describe.js +36 -0
- package/dist/libs/mcp/describe.js.map +1 -0
- package/dist/libs/mcp/docs_auth.d.ts +5 -0
- package/dist/libs/mcp/docs_auth.d.ts.map +1 -0
- package/dist/libs/mcp/docs_auth.js +36 -0
- package/dist/libs/mcp/docs_auth.js.map +1 -0
- package/dist/libs/mcp/dynamic_import.d.ts +6 -0
- package/dist/libs/mcp/dynamic_import.d.ts.map +1 -0
- package/dist/libs/mcp/dynamic_import.js +12 -0
- package/dist/libs/mcp/dynamic_import.js.map +1 -0
- package/dist/libs/mcp/execute_tool.d.ts +10 -0
- package/dist/libs/mcp/execute_tool.d.ts.map +1 -0
- package/dist/libs/mcp/execute_tool.js +65 -0
- package/dist/libs/mcp/execute_tool.js.map +1 -0
- package/dist/libs/mcp/guides/default.md +67 -0
- package/dist/libs/mcp/guides.d.ts +14 -0
- package/dist/libs/mcp/guides.d.ts.map +1 -0
- package/dist/libs/mcp/guides.js +127 -0
- package/dist/libs/mcp/guides.js.map +1 -0
- package/dist/libs/mcp/model_visibility.d.ts +31 -0
- package/dist/libs/mcp/model_visibility.d.ts.map +1 -0
- package/dist/libs/mcp/model_visibility.js +73 -0
- package/dist/libs/mcp/model_visibility.js.map +1 -0
- package/dist/libs/mcp/mount.d.ts +10 -0
- package/dist/libs/mcp/mount.d.ts.map +1 -0
- package/dist/libs/mcp/mount.js +68 -0
- package/dist/libs/mcp/mount.js.map +1 -0
- package/dist/libs/mcp/read_service.d.ts +23 -0
- package/dist/libs/mcp/read_service.d.ts.map +1 -0
- package/dist/libs/mcp/read_service.js +141 -0
- package/dist/libs/mcp/read_service.js.map +1 -0
- package/dist/libs/mcp/server.d.ts +23 -0
- package/dist/libs/mcp/server.d.ts.map +1 -0
- package/dist/libs/mcp/server.js +111 -0
- package/dist/libs/mcp/server.js.map +1 -0
- package/dist/libs/read_handlers.d.ts +13 -0
- package/dist/libs/read_handlers.d.ts.map +1 -0
- package/dist/libs/read_handlers.js +381 -0
- package/dist/libs/read_handlers.js.map +1 -0
- package/dist/libs/request_log.d.ts +2 -0
- package/dist/libs/request_log.d.ts.map +1 -1
- package/dist/libs/request_log.js +7 -1
- package/dist/libs/request_log.js.map +1 -1
- package/dist/libs/schema.d.ts.map +1 -1
- package/dist/libs/schema.js +5 -8
- package/dist/libs/schema.js.map +1 -1
- package/dist/libs/schema_description.d.ts +7 -1
- package/dist/libs/schema_description.d.ts.map +1 -1
- package/dist/libs/schema_description.js +24 -25
- package/dist/libs/schema_description.js.map +1 -1
- package/dist/libs/schema_serialize.d.ts +3 -0
- package/dist/libs/schema_serialize.d.ts.map +1 -0
- package/dist/libs/schema_serialize.js +45 -0
- package/dist/libs/schema_serialize.js.map +1 -0
- package/dist/types/jxp-config.d.ts +7 -0
- package/dist/types/jxp-config.d.ts.map +1 -1
- package/docs/changelog.md +61 -0
- package/docs/index.md +1 -0
- package/docs/mcp.md +134 -0
- package/mkdocs.yml +1 -0
- package/package.json +31 -26
- package/templates/assets/docs.css +107 -0
- package/templates/assets/mcp-console.js +343 -0
- package/templates/index.pug +37 -0
- package/templates/mcp.pug +112 -0
- package/templates/navbar.pug +2 -0
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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;
|