mcp-mongo-server 2.0.2 → 2.1.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/build/index.js +405 -86
- package/package.json +24 -8
package/build/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
+
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
4
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
6
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
6
|
-
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
7
7
|
|
|
8
8
|
// src/mongo.ts
|
|
9
9
|
import { MongoClient, ReadPreference } from "mongodb";
|
|
@@ -32,6 +32,10 @@ async function connectToMongoDB(url, readOnly) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// src/server.ts
|
|
35
|
+
import {
|
|
36
|
+
InMemoryTaskMessageQueue,
|
|
37
|
+
InMemoryTaskStore
|
|
38
|
+
} from "@modelcontextprotocol/sdk/experimental/tasks";
|
|
35
39
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
36
40
|
import {
|
|
37
41
|
CallToolRequestSchema,
|
|
@@ -46,6 +50,10 @@ import {
|
|
|
46
50
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
47
51
|
|
|
48
52
|
// src/schemas/call.ts
|
|
53
|
+
import {
|
|
54
|
+
ErrorCode,
|
|
55
|
+
McpError
|
|
56
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
49
57
|
import { ObjectId } from "mongodb";
|
|
50
58
|
var COLLECTION_OPERATIONS = [
|
|
51
59
|
"query",
|
|
@@ -60,7 +68,10 @@ async function handleCallToolRequest({
|
|
|
60
68
|
request,
|
|
61
69
|
client,
|
|
62
70
|
db,
|
|
63
|
-
isReadOnlyMode
|
|
71
|
+
isReadOnlyMode,
|
|
72
|
+
signal,
|
|
73
|
+
taskStore,
|
|
74
|
+
taskTtl
|
|
64
75
|
}) {
|
|
65
76
|
const { name, arguments: args = {} } = request.params;
|
|
66
77
|
const operation = name;
|
|
@@ -76,37 +87,87 @@ async function handleCallToolRequest({
|
|
|
76
87
|
}
|
|
77
88
|
Object.assign(args, filteredArgs);
|
|
78
89
|
validateOperation(operation);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
try {
|
|
91
|
+
checkReadOnlyMode(operation, isReadOnlyMode);
|
|
92
|
+
let collection = null;
|
|
93
|
+
if (COLLECTION_OPERATIONS.includes(operation)) {
|
|
94
|
+
const collectionName = args.collection;
|
|
95
|
+
if (!collectionName) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Collection name is required for '${operation}' operation`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
collection = db.collection(collectionName);
|
|
101
|
+
validateCollection(collection);
|
|
102
|
+
}
|
|
103
|
+
signal?.throwIfAborted();
|
|
104
|
+
if (taskStore) {
|
|
105
|
+
const task = await taskStore.createTask({
|
|
106
|
+
ttl: taskTtl ?? void 0
|
|
107
|
+
});
|
|
108
|
+
(async () => {
|
|
109
|
+
try {
|
|
110
|
+
const result = await executeOperation(
|
|
111
|
+
operation,
|
|
112
|
+
collection,
|
|
113
|
+
db,
|
|
114
|
+
isReadOnlyMode,
|
|
115
|
+
args,
|
|
116
|
+
objectIdMode,
|
|
117
|
+
signal
|
|
118
|
+
);
|
|
119
|
+
await taskStore.storeTaskResult(task.taskId, "completed", result);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
try {
|
|
122
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
123
|
+
await taskStore.storeTaskResult(task.taskId, "failed", {
|
|
124
|
+
content: [{ type: "text", text: message }],
|
|
125
|
+
isError: true
|
|
126
|
+
});
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
})();
|
|
131
|
+
return { task };
|
|
87
132
|
}
|
|
88
|
-
|
|
89
|
-
|
|
133
|
+
return await executeOperation(
|
|
134
|
+
operation,
|
|
135
|
+
collection,
|
|
136
|
+
db,
|
|
137
|
+
isReadOnlyMode,
|
|
138
|
+
args,
|
|
139
|
+
objectIdMode,
|
|
140
|
+
signal
|
|
141
|
+
);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (error instanceof McpError || signal?.aborted) {
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
return toolExecutionError(
|
|
147
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
148
|
+
);
|
|
90
149
|
}
|
|
150
|
+
}
|
|
151
|
+
async function executeOperation(operation, collection, db, isReadOnlyMode, args, objectIdMode, signal) {
|
|
91
152
|
switch (operation) {
|
|
92
153
|
case "query":
|
|
93
|
-
return handleQuery(collection, args, objectIdMode);
|
|
154
|
+
return handleQuery(collection, args, objectIdMode, signal);
|
|
94
155
|
case "aggregate":
|
|
95
|
-
return handleAggregate(collection, args, objectIdMode);
|
|
156
|
+
return handleAggregate(collection, args, objectIdMode, signal);
|
|
96
157
|
case "update":
|
|
97
|
-
return handleUpdate(collection, args, objectIdMode);
|
|
158
|
+
return handleUpdate(collection, args, objectIdMode, signal);
|
|
98
159
|
case "serverInfo":
|
|
99
|
-
return handleServerInfo(db, isReadOnlyMode, args);
|
|
160
|
+
return handleServerInfo(db, isReadOnlyMode, args, signal);
|
|
100
161
|
case "insert":
|
|
101
|
-
return handleInsert(collection, args, objectIdMode);
|
|
162
|
+
return handleInsert(collection, args, objectIdMode, signal);
|
|
102
163
|
case "createIndex":
|
|
103
|
-
return handleCreateIndex(collection, args, objectIdMode);
|
|
164
|
+
return handleCreateIndex(collection, args, objectIdMode, signal);
|
|
104
165
|
case "count":
|
|
105
|
-
return handleCount(collection, args, objectIdMode);
|
|
166
|
+
return handleCount(collection, args, objectIdMode, signal);
|
|
106
167
|
case "listCollections":
|
|
107
|
-
return handleListCollections(db, args, objectIdMode);
|
|
168
|
+
return handleListCollections(db, args, objectIdMode, signal);
|
|
108
169
|
default:
|
|
109
|
-
throw new
|
|
170
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${operation}`);
|
|
110
171
|
}
|
|
111
172
|
}
|
|
112
173
|
function validateOperation(operation) {
|
|
@@ -121,7 +182,7 @@ function validateOperation(operation) {
|
|
|
121
182
|
"listCollections"
|
|
122
183
|
];
|
|
123
184
|
if (!validOperations.includes(operation)) {
|
|
124
|
-
throw new
|
|
185
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${operation}`);
|
|
125
186
|
}
|
|
126
187
|
}
|
|
127
188
|
function validateCollection(collection) {
|
|
@@ -269,40 +330,64 @@ function formatResponse(data) {
|
|
|
269
330
|
]
|
|
270
331
|
};
|
|
271
332
|
}
|
|
333
|
+
function toolExecutionError(message) {
|
|
334
|
+
return {
|
|
335
|
+
content: [{ type: "text", text: message }],
|
|
336
|
+
isError: true
|
|
337
|
+
};
|
|
338
|
+
}
|
|
272
339
|
function handleError(error, operation, collectionName) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
throw new Error(`Failed to ${operation} ${context}: ${error.message}`);
|
|
340
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
341
|
+
throw error;
|
|
276
342
|
}
|
|
277
|
-
|
|
343
|
+
const context = collectionName ? `collection ${collectionName}` : "operation";
|
|
344
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
345
|
+
return toolExecutionError(`Failed to ${operation} ${context}: ${message}`);
|
|
278
346
|
}
|
|
279
|
-
async function handleQuery(collection, args, objectIdMode = "auto") {
|
|
347
|
+
async function handleQuery(collection, args, objectIdMode = "auto", signal) {
|
|
280
348
|
if (!collection) {
|
|
281
349
|
throw new Error("Collection is required for query operation");
|
|
282
350
|
}
|
|
283
|
-
const { filter, projection,
|
|
351
|
+
const { filter, projection, explain, sort } = args;
|
|
352
|
+
const limit = args.limit || 10;
|
|
353
|
+
const skip = args.skip || 0;
|
|
284
354
|
const queryFilter = parseFilter(filter, objectIdMode);
|
|
285
355
|
try {
|
|
286
356
|
if (explain) {
|
|
357
|
+
signal?.throwIfAborted();
|
|
287
358
|
const explainResult = await collection.find(queryFilter, {
|
|
288
359
|
projection,
|
|
289
|
-
limit
|
|
360
|
+
limit,
|
|
361
|
+
skip,
|
|
290
362
|
sort
|
|
291
363
|
}).explain(explain);
|
|
292
364
|
return formatResponse(explainResult);
|
|
293
365
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
366
|
+
signal?.throwIfAborted();
|
|
367
|
+
const [total, results] = await Promise.all([
|
|
368
|
+
collection.countDocuments(queryFilter),
|
|
369
|
+
collection.find(queryFilter, {
|
|
370
|
+
projection,
|
|
371
|
+
limit,
|
|
372
|
+
skip,
|
|
373
|
+
sort
|
|
374
|
+
}).toArray()
|
|
375
|
+
]);
|
|
376
|
+
return formatResponse({
|
|
377
|
+
results,
|
|
378
|
+
metadata: {
|
|
379
|
+
total,
|
|
380
|
+
returned: results.length,
|
|
381
|
+
skip,
|
|
382
|
+
limit,
|
|
383
|
+
hasMore: skip + results.length < total
|
|
384
|
+
}
|
|
298
385
|
});
|
|
299
|
-
const results = await cursor.toArray();
|
|
300
|
-
return formatResponse(results);
|
|
301
386
|
} catch (error) {
|
|
302
387
|
return handleError(error, "query", collection.collectionName);
|
|
303
388
|
}
|
|
304
389
|
}
|
|
305
|
-
async function handleAggregate(collection, args, objectIdMode = "auto") {
|
|
390
|
+
async function handleAggregate(collection, args, objectIdMode = "auto", signal) {
|
|
306
391
|
if (!collection) {
|
|
307
392
|
throw new Error("Collection is required for aggregate operation");
|
|
308
393
|
}
|
|
@@ -321,6 +406,7 @@ async function handleAggregate(collection, args, objectIdMode = "auto") {
|
|
|
321
406
|
});
|
|
322
407
|
try {
|
|
323
408
|
if (explain) {
|
|
409
|
+
signal?.throwIfAborted();
|
|
324
410
|
const explainResult = await collection.aggregate(processedPipeline, {
|
|
325
411
|
explain: {
|
|
326
412
|
verbosity: explain
|
|
@@ -328,13 +414,19 @@ async function handleAggregate(collection, args, objectIdMode = "auto") {
|
|
|
328
414
|
}).toArray();
|
|
329
415
|
return formatResponse(explainResult);
|
|
330
416
|
}
|
|
417
|
+
signal?.throwIfAborted();
|
|
331
418
|
const results = await collection.aggregate(processedPipeline).toArray();
|
|
332
|
-
return formatResponse(
|
|
419
|
+
return formatResponse({
|
|
420
|
+
results,
|
|
421
|
+
metadata: {
|
|
422
|
+
returned: results.length
|
|
423
|
+
}
|
|
424
|
+
});
|
|
333
425
|
} catch (error) {
|
|
334
426
|
return handleError(error, "aggregate", collection.collectionName);
|
|
335
427
|
}
|
|
336
428
|
}
|
|
337
|
-
async function handleUpdate(collection, args, objectIdMode = "auto") {
|
|
429
|
+
async function handleUpdate(collection, args, objectIdMode = "auto", signal) {
|
|
338
430
|
if (!collection) {
|
|
339
431
|
throw new Error("Collection is required for update operation");
|
|
340
432
|
}
|
|
@@ -375,6 +467,7 @@ async function handleUpdate(collection, args, objectIdMode = "auto") {
|
|
|
375
467
|
multi: !!multi
|
|
376
468
|
};
|
|
377
469
|
const updateMethod = options.multi ? "updateMany" : "updateOne";
|
|
470
|
+
signal?.throwIfAborted();
|
|
378
471
|
const result = await collection[updateMethod](
|
|
379
472
|
queryFilter,
|
|
380
473
|
processedUpdate,
|
|
@@ -390,12 +483,14 @@ async function handleUpdate(collection, args, objectIdMode = "auto") {
|
|
|
390
483
|
return handleError(error, "update", collection.collectionName);
|
|
391
484
|
}
|
|
392
485
|
}
|
|
393
|
-
async function handleServerInfo(db, isReadOnlyMode, args) {
|
|
486
|
+
async function handleServerInfo(db, isReadOnlyMode, args, signal) {
|
|
394
487
|
const { includeDebugInfo } = args;
|
|
395
488
|
try {
|
|
489
|
+
signal?.throwIfAborted();
|
|
396
490
|
const buildInfo = await db.command({ buildInfo: 1 });
|
|
397
491
|
let serverStatus = null;
|
|
398
492
|
if (includeDebugInfo) {
|
|
493
|
+
signal?.throwIfAborted();
|
|
399
494
|
serverStatus = await db.command({ serverStatus: 1 });
|
|
400
495
|
}
|
|
401
496
|
const serverInfo = {
|
|
@@ -440,7 +535,7 @@ async function handleServerInfo(db, isReadOnlyMode, args) {
|
|
|
440
535
|
return handleError(error, "get server information");
|
|
441
536
|
}
|
|
442
537
|
}
|
|
443
|
-
async function handleInsert(collection, args, objectIdMode = "auto") {
|
|
538
|
+
async function handleInsert(collection, args, objectIdMode = "auto", signal) {
|
|
444
539
|
if (!collection) {
|
|
445
540
|
throw new Error("Collection is required for insert operation");
|
|
446
541
|
}
|
|
@@ -466,6 +561,7 @@ async function handleInsert(collection, args, objectIdMode = "auto") {
|
|
|
466
561
|
writeConcern,
|
|
467
562
|
bypassDocumentValidation
|
|
468
563
|
};
|
|
564
|
+
signal?.throwIfAborted();
|
|
469
565
|
const result = await collection.insertMany(
|
|
470
566
|
processedDocuments,
|
|
471
567
|
options
|
|
@@ -488,7 +584,7 @@ async function handleInsert(collection, args, objectIdMode = "auto") {
|
|
|
488
584
|
return handleError(error, "insert", collection.collectionName);
|
|
489
585
|
}
|
|
490
586
|
}
|
|
491
|
-
async function handleCreateIndex(collection, args, objectIdMode = "auto") {
|
|
587
|
+
async function handleCreateIndex(collection, args, objectIdMode = "auto", signal) {
|
|
492
588
|
if (!collection) {
|
|
493
589
|
throw new Error("Collection is required for createIndex operation");
|
|
494
590
|
}
|
|
@@ -517,6 +613,7 @@ async function handleCreateIndex(collection, args, objectIdMode = "auto") {
|
|
|
517
613
|
const indexOptions = {
|
|
518
614
|
commitQuorum: typeof commitQuorum === "number" ? commitQuorum : void 0
|
|
519
615
|
};
|
|
616
|
+
signal?.throwIfAborted();
|
|
520
617
|
const result = await collection.createIndexes(
|
|
521
618
|
processedIndexes,
|
|
522
619
|
indexOptions
|
|
@@ -531,7 +628,7 @@ async function handleCreateIndex(collection, args, objectIdMode = "auto") {
|
|
|
531
628
|
return handleError(error, "create indexes", collection.collectionName);
|
|
532
629
|
}
|
|
533
630
|
}
|
|
534
|
-
async function handleCount(collection, args, objectIdMode = "auto") {
|
|
631
|
+
async function handleCount(collection, args, objectIdMode = "auto", signal) {
|
|
535
632
|
if (!collection) {
|
|
536
633
|
throw new Error("Collection is required for count operation");
|
|
537
634
|
}
|
|
@@ -551,6 +648,7 @@ async function handleCount(collection, args, objectIdMode = "auto") {
|
|
|
551
648
|
delete options[key];
|
|
552
649
|
}
|
|
553
650
|
}
|
|
651
|
+
signal?.throwIfAborted();
|
|
554
652
|
const count = await collection.countDocuments(countQuery, options);
|
|
555
653
|
return formatResponse({
|
|
556
654
|
count,
|
|
@@ -560,8 +658,10 @@ async function handleCount(collection, args, objectIdMode = "auto") {
|
|
|
560
658
|
return handleError(error, "count documents", collection.collectionName);
|
|
561
659
|
}
|
|
562
660
|
}
|
|
563
|
-
async function handleListCollections(db, args, objectIdMode = "auto") {
|
|
661
|
+
async function handleListCollections(db, args, objectIdMode = "auto", signal) {
|
|
564
662
|
const { nameOnly, filter } = args;
|
|
663
|
+
const skip = args.skip || 0;
|
|
664
|
+
const limit = args.limit || 20;
|
|
565
665
|
let processedFilter = filter;
|
|
566
666
|
if (filter && typeof filter === "object") {
|
|
567
667
|
processedFilter = processObjectIdInFilter(
|
|
@@ -571,20 +671,58 @@ async function handleListCollections(db, args, objectIdMode = "auto") {
|
|
|
571
671
|
}
|
|
572
672
|
try {
|
|
573
673
|
const options = processedFilter ? { filter: processedFilter } : {};
|
|
674
|
+
signal?.throwIfAborted();
|
|
574
675
|
const collections = await db.listCollections(options).toArray();
|
|
575
|
-
const
|
|
576
|
-
|
|
676
|
+
const allResults = nameOnly ? collections.map((collection) => collection.name) : collections;
|
|
677
|
+
const total = allResults.length;
|
|
678
|
+
const paged = allResults.slice(skip, skip + limit);
|
|
679
|
+
return formatResponse({
|
|
680
|
+
results: paged,
|
|
681
|
+
metadata: {
|
|
682
|
+
total,
|
|
683
|
+
returned: paged.length,
|
|
684
|
+
skip,
|
|
685
|
+
limit,
|
|
686
|
+
hasMore: skip + paged.length < total
|
|
687
|
+
}
|
|
688
|
+
});
|
|
577
689
|
} catch (error) {
|
|
578
690
|
return handleError(error, "list collections");
|
|
579
691
|
}
|
|
580
692
|
}
|
|
581
693
|
|
|
694
|
+
// src/utils/pagination.ts
|
|
695
|
+
var DEFAULT_PAGE_SIZE = 20;
|
|
696
|
+
function encodeCursor(offset) {
|
|
697
|
+
const data = { offset };
|
|
698
|
+
return Buffer.from(JSON.stringify(data)).toString("base64");
|
|
699
|
+
}
|
|
700
|
+
function decodeCursor(cursor) {
|
|
701
|
+
if (!cursor) return 0;
|
|
702
|
+
try {
|
|
703
|
+
const data = JSON.parse(
|
|
704
|
+
Buffer.from(cursor, "base64").toString("utf-8")
|
|
705
|
+
);
|
|
706
|
+
return typeof data.offset === "number" && data.offset >= 0 ? data.offset : 0;
|
|
707
|
+
} catch {
|
|
708
|
+
return 0;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
function paginate(items, cursor, pageSize = DEFAULT_PAGE_SIZE) {
|
|
712
|
+
const offset = decodeCursor(cursor);
|
|
713
|
+
const page = items.slice(offset, offset + pageSize);
|
|
714
|
+
const nextOffset = offset + pageSize;
|
|
715
|
+
const nextCursor = nextOffset < items.length ? encodeCursor(nextOffset) : void 0;
|
|
716
|
+
return { items: page, nextCursor };
|
|
717
|
+
}
|
|
718
|
+
|
|
582
719
|
// src/schemas/completion.ts
|
|
583
720
|
async function handleCompletionRequest({
|
|
584
721
|
request,
|
|
585
722
|
client,
|
|
586
723
|
db,
|
|
587
|
-
isReadOnlyMode
|
|
724
|
+
isReadOnlyMode,
|
|
725
|
+
signal
|
|
588
726
|
}) {
|
|
589
727
|
const { ref, argument } = request.params;
|
|
590
728
|
if (ref.type === "ref/prompt") {
|
|
@@ -593,7 +731,8 @@ async function handleCompletionRequest({
|
|
|
593
731
|
db,
|
|
594
732
|
isReadOnlyMode,
|
|
595
733
|
ref.name,
|
|
596
|
-
argument
|
|
734
|
+
argument,
|
|
735
|
+
signal
|
|
597
736
|
);
|
|
598
737
|
}
|
|
599
738
|
if (ref.type === "ref/resource") {
|
|
@@ -602,34 +741,46 @@ async function handleCompletionRequest({
|
|
|
602
741
|
db,
|
|
603
742
|
isReadOnlyMode,
|
|
604
743
|
ref.uri,
|
|
605
|
-
argument
|
|
744
|
+
argument,
|
|
745
|
+
signal
|
|
606
746
|
);
|
|
607
747
|
}
|
|
608
748
|
return emptyCompletionResult();
|
|
609
749
|
}
|
|
610
|
-
async function handlePromptCompletion(client, db, isReadOnlyMode, promptName, argument) {
|
|
750
|
+
async function handlePromptCompletion(client, db, isReadOnlyMode, promptName, argument, signal) {
|
|
611
751
|
if (!promptName) {
|
|
612
752
|
return emptyCompletionResult();
|
|
613
753
|
}
|
|
614
754
|
if (argument.name === "collection") {
|
|
615
|
-
return await completeCollectionNames(
|
|
755
|
+
return await completeCollectionNames(
|
|
756
|
+
argument.value,
|
|
757
|
+
db,
|
|
758
|
+
isReadOnlyMode,
|
|
759
|
+
signal
|
|
760
|
+
);
|
|
616
761
|
}
|
|
617
762
|
return emptyCompletionResult();
|
|
618
763
|
}
|
|
619
|
-
async function handleResourceCompletion(client, db, isReadOnlyMode, promptName, argument) {
|
|
764
|
+
async function handleResourceCompletion(client, db, isReadOnlyMode, promptName, argument, signal) {
|
|
620
765
|
if (!promptName) {
|
|
621
766
|
return emptyCompletionResult();
|
|
622
767
|
}
|
|
623
768
|
if (argument.name === "collection") {
|
|
624
|
-
return await completeCollectionNames(
|
|
769
|
+
return await completeCollectionNames(
|
|
770
|
+
argument.value,
|
|
771
|
+
db,
|
|
772
|
+
isReadOnlyMode,
|
|
773
|
+
signal
|
|
774
|
+
);
|
|
625
775
|
}
|
|
626
776
|
return emptyCompletionResult();
|
|
627
777
|
}
|
|
628
|
-
async function completeCollectionNames(partialValue, db, isReadOnlyMode) {
|
|
778
|
+
async function completeCollectionNames(partialValue, db, isReadOnlyMode, signal) {
|
|
629
779
|
try {
|
|
630
780
|
console.warn(
|
|
631
781
|
`Completing collection names with partial value: ${partialValue}`
|
|
632
782
|
);
|
|
783
|
+
signal?.throwIfAborted();
|
|
633
784
|
const collections = await db.listCollections().toArray();
|
|
634
785
|
const matchingCollections = collections.map(
|
|
635
786
|
(c) => c.name
|
|
@@ -641,14 +792,17 @@ async function completeCollectionNames(partialValue, db, isReadOnlyMode) {
|
|
|
641
792
|
)
|
|
642
793
|
).sort();
|
|
643
794
|
console.warn(`Found ${matchingCollections.length} matching collections`);
|
|
644
|
-
const
|
|
645
|
-
const limitedResults =
|
|
646
|
-
|
|
795
|
+
const PAGE_SIZE = 100;
|
|
796
|
+
const { items: limitedResults, nextCursor } = paginate(
|
|
797
|
+
matchingCollections,
|
|
798
|
+
void 0,
|
|
799
|
+
PAGE_SIZE
|
|
800
|
+
);
|
|
647
801
|
return {
|
|
648
802
|
completion: {
|
|
649
803
|
values: limitedResults,
|
|
650
804
|
total: matchingCollections.length,
|
|
651
|
-
hasMore
|
|
805
|
+
hasMore: !!nextCursor
|
|
652
806
|
}
|
|
653
807
|
};
|
|
654
808
|
} catch (error) {
|
|
@@ -671,12 +825,14 @@ async function handlePingRequest({
|
|
|
671
825
|
request,
|
|
672
826
|
client,
|
|
673
827
|
db,
|
|
674
|
-
isReadOnlyMode
|
|
828
|
+
isReadOnlyMode,
|
|
829
|
+
signal
|
|
675
830
|
}) {
|
|
676
831
|
try {
|
|
677
832
|
if (!client) {
|
|
678
833
|
throw new Error("MongoDB connection is not available");
|
|
679
834
|
}
|
|
835
|
+
signal?.throwIfAborted();
|
|
680
836
|
const pong = await db.command({ ping: 1 });
|
|
681
837
|
if (pong.ok !== 1) {
|
|
682
838
|
throw new Error(`MongoDB ping failed: ${pong.errmsg}`);
|
|
@@ -695,7 +851,8 @@ async function handleListPromptsRequest({
|
|
|
695
851
|
request,
|
|
696
852
|
client,
|
|
697
853
|
db,
|
|
698
|
-
isReadOnlyMode
|
|
854
|
+
isReadOnlyMode,
|
|
855
|
+
signal
|
|
699
856
|
}) {
|
|
700
857
|
return {
|
|
701
858
|
prompts: [
|
|
@@ -717,7 +874,9 @@ async function handleGetPromptRequest({
|
|
|
717
874
|
request,
|
|
718
875
|
client,
|
|
719
876
|
db,
|
|
720
|
-
isReadOnlyMode
|
|
877
|
+
isReadOnlyMode,
|
|
878
|
+
signal,
|
|
879
|
+
sendProgress
|
|
721
880
|
}) {
|
|
722
881
|
const { name, arguments: args = {} } = request.params;
|
|
723
882
|
if (name !== "analyze_collection") {
|
|
@@ -732,8 +891,14 @@ async function handleGetPromptRequest({
|
|
|
732
891
|
if (collection.collectionName.startsWith("system.")) {
|
|
733
892
|
throw new Error("Access to system collections is not allowed");
|
|
734
893
|
}
|
|
894
|
+
await sendProgress?.(1, 3, "Getting schema");
|
|
895
|
+
signal?.throwIfAborted();
|
|
735
896
|
const schemaSample = await collection.findOne({});
|
|
897
|
+
await sendProgress?.(2, 3, "Getting stats");
|
|
898
|
+
signal?.throwIfAborted();
|
|
736
899
|
const stats = await collection.aggregate([{ $collStats: { count: {} } }]).toArray();
|
|
900
|
+
await sendProgress?.(3, 3, "Fetching samples");
|
|
901
|
+
signal?.throwIfAborted();
|
|
737
902
|
const sampleDocs = await collection.find({}).limit(5).toArray();
|
|
738
903
|
const documentCount = stats[0]?.count ?? "unknown";
|
|
739
904
|
return {
|
|
@@ -864,7 +1029,9 @@ async function handleReadResourceRequest({
|
|
|
864
1029
|
request,
|
|
865
1030
|
client,
|
|
866
1031
|
db,
|
|
867
|
-
isReadOnlyMode
|
|
1032
|
+
isReadOnlyMode,
|
|
1033
|
+
signal,
|
|
1034
|
+
sendProgress
|
|
868
1035
|
}) {
|
|
869
1036
|
const url = new URL(request.params.uri);
|
|
870
1037
|
const collectionName = url.pathname.replace(/^\//, "");
|
|
@@ -872,18 +1039,25 @@ async function handleReadResourceRequest({
|
|
|
872
1039
|
const collection = db.collection(collectionName);
|
|
873
1040
|
const sampleSize = 100;
|
|
874
1041
|
let sampleDocuments = [];
|
|
1042
|
+
await sendProgress?.(1, 4, "Sampling documents");
|
|
875
1043
|
try {
|
|
1044
|
+
signal?.throwIfAborted();
|
|
876
1045
|
sampleDocuments = await collection.aggregate([{ $sample: { size: sampleSize } }]).toArray();
|
|
877
1046
|
} catch (sampleError) {
|
|
878
1047
|
console.warn(
|
|
879
1048
|
`$sample aggregation failed for ${collectionName}, falling back to sequential scan: ${sampleError}`
|
|
880
1049
|
);
|
|
1050
|
+
signal?.throwIfAborted();
|
|
881
1051
|
sampleDocuments = await collection.find({}).limit(sampleSize).toArray();
|
|
882
1052
|
}
|
|
1053
|
+
await sendProgress?.(2, 4, "Getting indexes");
|
|
1054
|
+
signal?.throwIfAborted();
|
|
883
1055
|
const indexes = await collection.indexes();
|
|
884
1056
|
const inferredSchema = inferSchemaFromSamples(sampleDocuments);
|
|
1057
|
+
await sendProgress?.(3, 4, "Counting documents");
|
|
885
1058
|
let documentCount = null;
|
|
886
1059
|
try {
|
|
1060
|
+
signal?.throwIfAborted();
|
|
887
1061
|
documentCount = await Promise.race([
|
|
888
1062
|
collection.countDocuments(),
|
|
889
1063
|
new Promise(
|
|
@@ -898,12 +1072,14 @@ async function handleReadResourceRequest({
|
|
|
898
1072
|
`Count operation failed or timed out for ${collectionName}: ${countError}`
|
|
899
1073
|
);
|
|
900
1074
|
try {
|
|
1075
|
+
signal?.throwIfAborted();
|
|
901
1076
|
const stats = await db.command({ collStats: collectionName });
|
|
902
1077
|
documentCount = stats.count;
|
|
903
1078
|
} catch {
|
|
904
1079
|
documentCount = "unknown (count operation timed out)";
|
|
905
1080
|
}
|
|
906
1081
|
}
|
|
1082
|
+
await sendProgress?.(4, 4, "Done");
|
|
907
1083
|
const schema = {
|
|
908
1084
|
type: "collection",
|
|
909
1085
|
name: collectionName,
|
|
@@ -940,17 +1116,23 @@ async function handleListResourcesRequest({
|
|
|
940
1116
|
request,
|
|
941
1117
|
client,
|
|
942
1118
|
db,
|
|
943
|
-
isReadOnlyMode
|
|
1119
|
+
isReadOnlyMode,
|
|
1120
|
+
signal
|
|
944
1121
|
}) {
|
|
945
1122
|
try {
|
|
1123
|
+
signal?.throwIfAborted();
|
|
946
1124
|
const collections = await db.listCollections().toArray();
|
|
1125
|
+
const allResources = collections.map((collection) => ({
|
|
1126
|
+
uri: `mongodb:///${collection.name}`,
|
|
1127
|
+
mimeType: "application/json",
|
|
1128
|
+
name: collection.name,
|
|
1129
|
+
description: `MongoDB collection: ${collection.name}`
|
|
1130
|
+
}));
|
|
1131
|
+
const cursor = request.params?.cursor;
|
|
1132
|
+
const { items, nextCursor } = paginate(allResources, cursor);
|
|
947
1133
|
return {
|
|
948
|
-
resources:
|
|
949
|
-
|
|
950
|
-
mimeType: "application/json",
|
|
951
|
-
name: collection.name,
|
|
952
|
-
description: `MongoDB collection: ${collection.name}`
|
|
953
|
-
}))
|
|
1134
|
+
resources: items,
|
|
1135
|
+
...nextCursor ? { nextCursor } : {}
|
|
954
1136
|
};
|
|
955
1137
|
} catch (error) {
|
|
956
1138
|
if (error instanceof Error) {
|
|
@@ -965,7 +1147,8 @@ async function handleListResourceTemplatesRequest({
|
|
|
965
1147
|
request,
|
|
966
1148
|
client,
|
|
967
1149
|
db,
|
|
968
|
-
isReadOnlyMode
|
|
1150
|
+
isReadOnlyMode,
|
|
1151
|
+
signal
|
|
969
1152
|
}) {
|
|
970
1153
|
return {
|
|
971
1154
|
resourceTemplates: [
|
|
@@ -1011,13 +1194,15 @@ async function handleListToolsRequest({
|
|
|
1011
1194
|
request,
|
|
1012
1195
|
client,
|
|
1013
1196
|
db,
|
|
1014
|
-
isReadOnlyMode
|
|
1197
|
+
isReadOnlyMode,
|
|
1198
|
+
signal
|
|
1015
1199
|
}) {
|
|
1016
1200
|
return {
|
|
1017
1201
|
tools: [
|
|
1018
1202
|
{
|
|
1019
1203
|
name: "query",
|
|
1020
1204
|
description: "Execute a MongoDB query with optional execution plan analysis",
|
|
1205
|
+
execution: { taskSupport: "optional" },
|
|
1021
1206
|
inputSchema: {
|
|
1022
1207
|
type: "object",
|
|
1023
1208
|
properties: {
|
|
@@ -1038,6 +1223,11 @@ async function handleListToolsRequest({
|
|
|
1038
1223
|
description: "Maximum number of documents to return",
|
|
1039
1224
|
default: 10
|
|
1040
1225
|
},
|
|
1226
|
+
skip: {
|
|
1227
|
+
type: "number",
|
|
1228
|
+
description: "Number of documents to skip before returning results",
|
|
1229
|
+
default: 0
|
|
1230
|
+
},
|
|
1041
1231
|
explain: {
|
|
1042
1232
|
type: "string",
|
|
1043
1233
|
description: "Optional: Get query execution information",
|
|
@@ -1056,6 +1246,7 @@ async function handleListToolsRequest({
|
|
|
1056
1246
|
{
|
|
1057
1247
|
name: "aggregate",
|
|
1058
1248
|
description: "Execute a MongoDB aggregation pipeline with optional execution plan analysis",
|
|
1249
|
+
execution: { taskSupport: "optional" },
|
|
1059
1250
|
inputSchema: {
|
|
1060
1251
|
type: "object",
|
|
1061
1252
|
properties: {
|
|
@@ -1088,6 +1279,7 @@ async function handleListToolsRequest({
|
|
|
1088
1279
|
{
|
|
1089
1280
|
name: "update",
|
|
1090
1281
|
description: "Update documents in a MongoDB collection",
|
|
1282
|
+
execution: { taskSupport: "optional" },
|
|
1091
1283
|
inputSchema: {
|
|
1092
1284
|
type: "object",
|
|
1093
1285
|
properties: {
|
|
@@ -1124,6 +1316,7 @@ async function handleListToolsRequest({
|
|
|
1124
1316
|
{
|
|
1125
1317
|
name: "serverInfo",
|
|
1126
1318
|
description: "Get MongoDB server information including version, storage engine, and other details",
|
|
1319
|
+
execution: { taskSupport: "optional" },
|
|
1127
1320
|
inputSchema: {
|
|
1128
1321
|
type: "object",
|
|
1129
1322
|
properties: {
|
|
@@ -1137,6 +1330,7 @@ async function handleListToolsRequest({
|
|
|
1137
1330
|
{
|
|
1138
1331
|
name: "insert",
|
|
1139
1332
|
description: "Insert one or more documents into a MongoDB collection",
|
|
1333
|
+
execution: { taskSupport: "optional" },
|
|
1140
1334
|
inputSchema: {
|
|
1141
1335
|
type: "object",
|
|
1142
1336
|
properties: {
|
|
@@ -1174,6 +1368,7 @@ async function handleListToolsRequest({
|
|
|
1174
1368
|
{
|
|
1175
1369
|
name: "createIndex",
|
|
1176
1370
|
description: "Create one or more indexes on a MongoDB collection",
|
|
1371
|
+
execution: { taskSupport: "optional" },
|
|
1177
1372
|
inputSchema: {
|
|
1178
1373
|
type: "object",
|
|
1179
1374
|
properties: {
|
|
@@ -1240,6 +1435,7 @@ async function handleListToolsRequest({
|
|
|
1240
1435
|
{
|
|
1241
1436
|
name: "count",
|
|
1242
1437
|
description: "Count documents in a collection matching a query",
|
|
1438
|
+
execution: { taskSupport: "optional" },
|
|
1243
1439
|
inputSchema: {
|
|
1244
1440
|
type: "object",
|
|
1245
1441
|
properties: {
|
|
@@ -1270,6 +1466,7 @@ async function handleListToolsRequest({
|
|
|
1270
1466
|
{
|
|
1271
1467
|
name: "listCollections",
|
|
1272
1468
|
description: "List all collections in the MongoDB database",
|
|
1469
|
+
execution: { taskSupport: "optional" },
|
|
1273
1470
|
inputSchema: {
|
|
1274
1471
|
type: "object",
|
|
1275
1472
|
properties: {
|
|
@@ -1281,6 +1478,16 @@ async function handleListToolsRequest({
|
|
|
1281
1478
|
type: "object",
|
|
1282
1479
|
description: "Filter for collections"
|
|
1283
1480
|
},
|
|
1481
|
+
skip: {
|
|
1482
|
+
type: "number",
|
|
1483
|
+
description: "Number of collections to skip before returning results",
|
|
1484
|
+
default: 0
|
|
1485
|
+
},
|
|
1486
|
+
limit: {
|
|
1487
|
+
type: "number",
|
|
1488
|
+
description: "Maximum number of collections to return",
|
|
1489
|
+
default: 20
|
|
1490
|
+
},
|
|
1284
1491
|
objectIdMode: {
|
|
1285
1492
|
type: "string",
|
|
1286
1493
|
description: "Control how 24-character hex strings are handled",
|
|
@@ -1295,11 +1502,24 @@ async function handleListToolsRequest({
|
|
|
1295
1502
|
}
|
|
1296
1503
|
|
|
1297
1504
|
// src/server.ts
|
|
1505
|
+
function createSendProgress(extra) {
|
|
1506
|
+
const progressToken = extra._meta?.progressToken;
|
|
1507
|
+
if (progressToken === void 0) return void 0;
|
|
1508
|
+
return (progress, total, message) => extra.sendNotification({
|
|
1509
|
+
method: "notifications/progress",
|
|
1510
|
+
params: { progressToken, progress, total, message }
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1298
1513
|
function createServer(client, db, isReadOnlyMode = false, options = {}) {
|
|
1514
|
+
const taskStore = new InMemoryTaskStore();
|
|
1515
|
+
const taskMessageQueue = new InMemoryTaskMessageQueue();
|
|
1299
1516
|
const server = new Server(
|
|
1300
1517
|
{
|
|
1301
1518
|
name: "mongodb",
|
|
1302
|
-
|
|
1519
|
+
title: "MongoDB MCP Server",
|
|
1520
|
+
version: "2.1.0",
|
|
1521
|
+
description: "MCP server for MongoDB: query, aggregate, and manage collections with read-only mode, progress notifications, cancellation, and task support",
|
|
1522
|
+
websiteUrl: "https://github.com/kiliczsh/mcp-mongo-server",
|
|
1303
1523
|
...options
|
|
1304
1524
|
},
|
|
1305
1525
|
{
|
|
@@ -1307,46 +1527,111 @@ function createServer(client, db, isReadOnlyMode = false, options = {}) {
|
|
|
1307
1527
|
completions: {},
|
|
1308
1528
|
resources: {},
|
|
1309
1529
|
tools: {},
|
|
1310
|
-
prompts: {}
|
|
1530
|
+
prompts: {},
|
|
1531
|
+
tasks: {
|
|
1532
|
+
list: {},
|
|
1533
|
+
cancel: {},
|
|
1534
|
+
requests: { tools: { call: {} } }
|
|
1535
|
+
}
|
|
1311
1536
|
},
|
|
1537
|
+
taskStore,
|
|
1538
|
+
taskMessageQueue,
|
|
1312
1539
|
...options
|
|
1313
1540
|
}
|
|
1314
1541
|
);
|
|
1315
1542
|
server.setRequestHandler(
|
|
1316
1543
|
PingRequestSchema,
|
|
1317
|
-
(request) => handlePingRequest({
|
|
1544
|
+
(request, extra) => handlePingRequest({
|
|
1545
|
+
request,
|
|
1546
|
+
client,
|
|
1547
|
+
db,
|
|
1548
|
+
isReadOnlyMode,
|
|
1549
|
+
signal: extra.signal
|
|
1550
|
+
})
|
|
1318
1551
|
);
|
|
1319
1552
|
server.setRequestHandler(
|
|
1320
1553
|
ListResourcesRequestSchema,
|
|
1321
|
-
(request) => handleListResourcesRequest({
|
|
1554
|
+
(request, extra) => handleListResourcesRequest({
|
|
1555
|
+
request,
|
|
1556
|
+
client,
|
|
1557
|
+
db,
|
|
1558
|
+
isReadOnlyMode,
|
|
1559
|
+
signal: extra.signal
|
|
1560
|
+
})
|
|
1322
1561
|
);
|
|
1323
1562
|
server.setRequestHandler(
|
|
1324
1563
|
ReadResourceRequestSchema,
|
|
1325
|
-
(request) => handleReadResourceRequest({
|
|
1564
|
+
(request, extra) => handleReadResourceRequest({
|
|
1565
|
+
request,
|
|
1566
|
+
client,
|
|
1567
|
+
db,
|
|
1568
|
+
isReadOnlyMode,
|
|
1569
|
+
signal: extra.signal,
|
|
1570
|
+
sendProgress: createSendProgress(extra)
|
|
1571
|
+
})
|
|
1326
1572
|
);
|
|
1327
1573
|
server.setRequestHandler(
|
|
1328
1574
|
ListToolsRequestSchema,
|
|
1329
|
-
(request) => handleListToolsRequest({
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1575
|
+
(request, extra) => handleListToolsRequest({
|
|
1576
|
+
request,
|
|
1577
|
+
client,
|
|
1578
|
+
db,
|
|
1579
|
+
isReadOnlyMode,
|
|
1580
|
+
signal: extra.signal
|
|
1581
|
+
})
|
|
1334
1582
|
);
|
|
1583
|
+
server.setRequestHandler(CallToolRequestSchema, (request, extra) => {
|
|
1584
|
+
const hasTaskParams = !!request.params.task;
|
|
1585
|
+
return handleCallToolRequest({
|
|
1586
|
+
request,
|
|
1587
|
+
client,
|
|
1588
|
+
db,
|
|
1589
|
+
isReadOnlyMode,
|
|
1590
|
+
signal: extra.signal,
|
|
1591
|
+
taskStore: hasTaskParams ? extra.taskStore : void 0,
|
|
1592
|
+
taskTtl: hasTaskParams ? extra.taskRequestedTtl : void 0
|
|
1593
|
+
});
|
|
1594
|
+
});
|
|
1335
1595
|
server.setRequestHandler(
|
|
1336
1596
|
ListPromptsRequestSchema,
|
|
1337
|
-
(request) => handleListPromptsRequest({
|
|
1597
|
+
(request, extra) => handleListPromptsRequest({
|
|
1598
|
+
request,
|
|
1599
|
+
client,
|
|
1600
|
+
db,
|
|
1601
|
+
isReadOnlyMode,
|
|
1602
|
+
signal: extra.signal
|
|
1603
|
+
})
|
|
1338
1604
|
);
|
|
1339
1605
|
server.setRequestHandler(
|
|
1340
1606
|
GetPromptRequestSchema,
|
|
1341
|
-
(request) => handleGetPromptRequest({
|
|
1607
|
+
(request, extra) => handleGetPromptRequest({
|
|
1608
|
+
request,
|
|
1609
|
+
client,
|
|
1610
|
+
db,
|
|
1611
|
+
isReadOnlyMode,
|
|
1612
|
+
signal: extra.signal,
|
|
1613
|
+
sendProgress: createSendProgress(extra)
|
|
1614
|
+
})
|
|
1342
1615
|
);
|
|
1343
1616
|
server.setRequestHandler(
|
|
1344
1617
|
ListResourceTemplatesRequestSchema,
|
|
1345
|
-
(request) => handleListResourceTemplatesRequest({
|
|
1618
|
+
(request, extra) => handleListResourceTemplatesRequest({
|
|
1619
|
+
request,
|
|
1620
|
+
client,
|
|
1621
|
+
db,
|
|
1622
|
+
isReadOnlyMode,
|
|
1623
|
+
signal: extra.signal
|
|
1624
|
+
})
|
|
1346
1625
|
);
|
|
1347
1626
|
server.setRequestHandler(
|
|
1348
1627
|
CompleteRequestSchema,
|
|
1349
|
-
(request) => handleCompletionRequest({
|
|
1628
|
+
(request, extra) => handleCompletionRequest({
|
|
1629
|
+
request,
|
|
1630
|
+
client,
|
|
1631
|
+
db,
|
|
1632
|
+
isReadOnlyMode,
|
|
1633
|
+
signal: extra.signal
|
|
1634
|
+
})
|
|
1350
1635
|
);
|
|
1351
1636
|
return server;
|
|
1352
1637
|
}
|
|
@@ -1359,6 +1644,7 @@ async function main() {
|
|
|
1359
1644
|
let readOnlyMode = process.env.MCP_MONGODB_READONLY === "true" || false;
|
|
1360
1645
|
let transportMode = "stdio";
|
|
1361
1646
|
let port = Number(process.env.MCP_PORT) || 3001;
|
|
1647
|
+
const allowedOrigins = (process.env.MCP_HTTP_ALLOWED_ORIGINS || "").split(",").map((origin) => origin.trim()).filter(Boolean);
|
|
1362
1648
|
for (let i = 0; i < args.length; i++) {
|
|
1363
1649
|
if (args[i] === "--read-only" || args[i] === "-r") {
|
|
1364
1650
|
readOnlyMode = true;
|
|
@@ -1375,6 +1661,10 @@ async function main() {
|
|
|
1375
1661
|
console.error("Invalid port number.");
|
|
1376
1662
|
process.exit(1);
|
|
1377
1663
|
}
|
|
1664
|
+
} else if (args[i] === "--allowed-origins") {
|
|
1665
|
+
allowedOrigins.push(
|
|
1666
|
+
...(args[++i] || "").split(",").map((origin) => origin.trim()).filter(Boolean)
|
|
1667
|
+
);
|
|
1378
1668
|
} else if (!connectionUrl) {
|
|
1379
1669
|
connectionUrl = args[i];
|
|
1380
1670
|
}
|
|
@@ -1411,7 +1701,7 @@ async function main() {
|
|
|
1411
1701
|
process.exit(1);
|
|
1412
1702
|
}
|
|
1413
1703
|
if (transportMode === "http") {
|
|
1414
|
-
await startHttpServer(client, db, isReadOnlyMode, port);
|
|
1704
|
+
await startHttpServer(client, db, isReadOnlyMode, port, allowedOrigins);
|
|
1415
1705
|
} else {
|
|
1416
1706
|
await startStdioServer(client, db, isReadOnlyMode);
|
|
1417
1707
|
}
|
|
@@ -1429,7 +1719,21 @@ async function startStdioServer(client, db, isReadOnlyMode) {
|
|
|
1429
1719
|
await server.connect(transport);
|
|
1430
1720
|
console.warn("Server connected successfully via stdio");
|
|
1431
1721
|
}
|
|
1432
|
-
|
|
1722
|
+
function isOriginAllowed(origin, allowedOrigins) {
|
|
1723
|
+
if (!origin) {
|
|
1724
|
+
return true;
|
|
1725
|
+
}
|
|
1726
|
+
if (allowedOrigins.includes(origin)) {
|
|
1727
|
+
return true;
|
|
1728
|
+
}
|
|
1729
|
+
try {
|
|
1730
|
+
const { hostname } = new URL(origin);
|
|
1731
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
1732
|
+
} catch {
|
|
1733
|
+
return false;
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
async function startHttpServer(client, db, isReadOnlyMode, port, allowedOrigins) {
|
|
1433
1737
|
const app = createMcpExpressApp({ host: "0.0.0.0" });
|
|
1434
1738
|
app.use((req, res, next) => {
|
|
1435
1739
|
const start = Date.now();
|
|
@@ -1448,6 +1752,21 @@ async function startHttpServer(client, db, isReadOnlyMode, port) {
|
|
|
1448
1752
|
});
|
|
1449
1753
|
next();
|
|
1450
1754
|
});
|
|
1755
|
+
app.use((req, res, next) => {
|
|
1756
|
+
const origin = req.headers.origin;
|
|
1757
|
+
if (!isOriginAllowed(origin, allowedOrigins)) {
|
|
1758
|
+
res.status(403).json({
|
|
1759
|
+
jsonrpc: "2.0",
|
|
1760
|
+
error: {
|
|
1761
|
+
code: -32603,
|
|
1762
|
+
message: "Forbidden: origin not allowed"
|
|
1763
|
+
},
|
|
1764
|
+
id: null
|
|
1765
|
+
});
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
next();
|
|
1769
|
+
});
|
|
1451
1770
|
app.post("/mcp", async (req, res) => {
|
|
1452
1771
|
const server = createServer(client, db, isReadOnlyMode);
|
|
1453
1772
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-mongo-server",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "A Model Context Protocol server for MongoDB connections",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
"inspector": "mcp-inspector --config inspector-config.json --server mongodb"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
27
|
-
"mongodb": "^7.
|
|
28
|
-
"zod": "^
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
27
|
+
"mongodb": "^7.5.0",
|
|
28
|
+
"zod": "^4.4.3"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@biomejs/biome": "^2.
|
|
32
|
-
"@modelcontextprotocol/inspector": "^0.
|
|
33
|
-
"@types/node": "^
|
|
31
|
+
"@biomejs/biome": "^2.5.5",
|
|
32
|
+
"@modelcontextprotocol/inspector": "^1.0.0",
|
|
33
|
+
"@types/node": "^26.1.1",
|
|
34
34
|
"tsup": "^8.5.1",
|
|
35
35
|
"typescript": "^5.9.3"
|
|
36
36
|
},
|
|
@@ -56,5 +56,21 @@
|
|
|
56
56
|
"type": "git",
|
|
57
57
|
"url": "https://github.com/kiliczsh/mcp-mongo-server"
|
|
58
58
|
},
|
|
59
|
-
"homepage": "https://github.com/kiliczsh/mcp-mongo-server"
|
|
59
|
+
"homepage": "https://github.com/kiliczsh/mcp-mongo-server",
|
|
60
|
+
"overrides": {
|
|
61
|
+
"qs": "^6.15.2",
|
|
62
|
+
"hono": "^4.12.18",
|
|
63
|
+
"@hono/node-server": "^1.19.10",
|
|
64
|
+
"express-rate-limit": "^8.2.2",
|
|
65
|
+
"fast-uri": "^3.1.2",
|
|
66
|
+
"ws": "^8.21.1",
|
|
67
|
+
"shell-quote": "^1.10.0",
|
|
68
|
+
"minimatch": "^3.1.3",
|
|
69
|
+
"brace-expansion": "^1.1.13",
|
|
70
|
+
"rollup": "^4.59.0",
|
|
71
|
+
"picomatch": "^4.0.4",
|
|
72
|
+
"body-parser": "^2.3.0",
|
|
73
|
+
"ip-address": "^10.2.0",
|
|
74
|
+
"esbuild": "^0.28.1"
|
|
75
|
+
}
|
|
60
76
|
}
|