mcp-mongo-server 3.0.0 → 3.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/README.md +8 -8
- package/build/index.js +53 -36
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -107,14 +107,14 @@ string. Aggregation stages that reach another database (`$out`, `$merge`,
|
|
|
107
107
|
quietly read from or write to databases you didn't point it at. Enable them
|
|
108
108
|
with `--allow-cross-db` if you need them.
|
|
109
109
|
|
|
110
|
-
**Server-side JavaScript.** The
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
**Read-only mode** blocks
|
|
116
|
-
|
|
117
|
-
|
|
110
|
+
**Server-side JavaScript.** The operators `$function`, `$where`, and
|
|
111
|
+
`$accumulator` are blocked by default in aggregation pipelines and in the
|
|
112
|
+
filters for `query`, `count`, and `update`. In read-write mode,
|
|
113
|
+
`--allow-server-js` enables these operators.
|
|
114
|
+
|
|
115
|
+
**Read-only mode** blocks writes, including the `$out` and `$merge`
|
|
116
|
+
aggregation stages. It also blocks server-side JavaScript even when
|
|
117
|
+
`--allow-server-js` is set.
|
|
118
118
|
|
|
119
119
|
**Least privilege.** Application-level checks only go so far — the strongest
|
|
120
120
|
guarantee comes from the database. Connect with a MongoDB user scoped to just
|
package/build/index.js
CHANGED
|
@@ -82,6 +82,14 @@ function findAggOperator(value, operators, depth = 0) {
|
|
|
82
82
|
}
|
|
83
83
|
return null;
|
|
84
84
|
}
|
|
85
|
+
function assertNoServerJs(value) {
|
|
86
|
+
const operator = findAggOperator(value, SERVER_JS_AGG_OPERATORS);
|
|
87
|
+
if (operator) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Server-side JavaScript operator '${operator}' is disabled by the current server policy.`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
85
93
|
function findCrossDbTarget(pipeline, connectedDb) {
|
|
86
94
|
const foreignDb = (target) => {
|
|
87
95
|
if (target && typeof target === "object" && "db" in target) {
|
|
@@ -164,7 +172,13 @@ async function handleCallToolRequest({
|
|
|
164
172
|
async function executeOperation(operation, collection, db, isReadOnlyMode, allowCrossDb, allowServerJs, args, objectIdMode, signal) {
|
|
165
173
|
switch (operation) {
|
|
166
174
|
case "query":
|
|
167
|
-
return handleQuery(
|
|
175
|
+
return handleQuery(
|
|
176
|
+
collection,
|
|
177
|
+
args,
|
|
178
|
+
objectIdMode,
|
|
179
|
+
allowServerJs && !isReadOnlyMode,
|
|
180
|
+
signal
|
|
181
|
+
);
|
|
168
182
|
case "aggregate":
|
|
169
183
|
return handleAggregate(
|
|
170
184
|
collection,
|
|
@@ -176,7 +190,7 @@ async function executeOperation(operation, collection, db, isReadOnlyMode, allow
|
|
|
176
190
|
signal
|
|
177
191
|
);
|
|
178
192
|
case "update":
|
|
179
|
-
return handleUpdate(collection, args, objectIdMode, signal);
|
|
193
|
+
return handleUpdate(collection, args, objectIdMode, allowServerJs, signal);
|
|
180
194
|
case "serverInfo":
|
|
181
195
|
return handleServerInfo(db, isReadOnlyMode, args, signal);
|
|
182
196
|
case "insert":
|
|
@@ -184,7 +198,13 @@ async function executeOperation(operation, collection, db, isReadOnlyMode, allow
|
|
|
184
198
|
case "createIndex":
|
|
185
199
|
return handleCreateIndex(collection, args, objectIdMode, signal);
|
|
186
200
|
case "count":
|
|
187
|
-
return handleCount(
|
|
201
|
+
return handleCount(
|
|
202
|
+
collection,
|
|
203
|
+
args,
|
|
204
|
+
objectIdMode,
|
|
205
|
+
allowServerJs && !isReadOnlyMode,
|
|
206
|
+
signal
|
|
207
|
+
);
|
|
188
208
|
case "listCollections":
|
|
189
209
|
return handleListCollections(db, args, objectIdMode, signal);
|
|
190
210
|
case "convertTime":
|
|
@@ -377,7 +397,7 @@ function handleError(error, operation, collectionName) {
|
|
|
377
397
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
378
398
|
return toolExecutionError(`Failed to ${operation} ${context}: ${message}`);
|
|
379
399
|
}
|
|
380
|
-
async function handleQuery(collection, args, objectIdMode = "auto", signal) {
|
|
400
|
+
async function handleQuery(collection, args, objectIdMode = "auto", allowServerJs = false, signal) {
|
|
381
401
|
if (!collection) {
|
|
382
402
|
throw new Error("Collection is required for query operation");
|
|
383
403
|
}
|
|
@@ -385,6 +405,9 @@ async function handleQuery(collection, args, objectIdMode = "auto", signal) {
|
|
|
385
405
|
const limit = args.limit || 10;
|
|
386
406
|
const skip = args.skip || 0;
|
|
387
407
|
const queryFilter = parseFilter(filter, objectIdMode);
|
|
408
|
+
if (!allowServerJs) {
|
|
409
|
+
assertNoServerJs(queryFilter);
|
|
410
|
+
}
|
|
388
411
|
try {
|
|
389
412
|
if (explain) {
|
|
390
413
|
signal?.throwIfAborted();
|
|
@@ -440,12 +463,7 @@ async function handleAggregate(collection, args, objectIdMode = "auto", isReadOn
|
|
|
440
463
|
}
|
|
441
464
|
}
|
|
442
465
|
if (!allowServerJs) {
|
|
443
|
-
|
|
444
|
-
if (js) {
|
|
445
|
-
throw new Error(
|
|
446
|
-
`Server-side JavaScript operator '${js}' is not allowed. Enable it with --allow-server-js or MCP_MONGODB_ALLOW_SERVER_JS=true.`
|
|
447
|
-
);
|
|
448
|
-
}
|
|
466
|
+
assertNoServerJs(pipeline);
|
|
449
467
|
}
|
|
450
468
|
if (!allowCrossDb) {
|
|
451
469
|
const foreignDb = findCrossDbTarget(pipeline, collection.dbName);
|
|
@@ -486,12 +504,15 @@ async function handleAggregate(collection, args, objectIdMode = "auto", isReadOn
|
|
|
486
504
|
return handleError(error, "aggregate", collection.collectionName);
|
|
487
505
|
}
|
|
488
506
|
}
|
|
489
|
-
async function handleUpdate(collection, args, objectIdMode = "auto", signal) {
|
|
507
|
+
async function handleUpdate(collection, args, objectIdMode = "auto", allowServerJs = false, signal) {
|
|
490
508
|
if (!collection) {
|
|
491
509
|
throw new Error("Collection is required for update operation");
|
|
492
510
|
}
|
|
493
511
|
const { filter, update, upsert, multi } = args;
|
|
494
512
|
const queryFilter = parseFilter(filter, objectIdMode);
|
|
513
|
+
if (!allowServerJs) {
|
|
514
|
+
assertNoServerJs(queryFilter);
|
|
515
|
+
}
|
|
495
516
|
let processedUpdate = update;
|
|
496
517
|
if (update && typeof update === "object" && !Array.isArray(update)) {
|
|
497
518
|
processedUpdate = processObjectIdInFilter(
|
|
@@ -652,7 +673,7 @@ async function handleCreateIndex(collection, args, objectIdMode = "auto", signal
|
|
|
652
673
|
"Write concern must be a valid MongoDB write concern object"
|
|
653
674
|
);
|
|
654
675
|
}
|
|
655
|
-
if (commitQuorum && typeof commitQuorum !== "string" && typeof commitQuorum !== "number") {
|
|
676
|
+
if (commitQuorum !== void 0 && typeof commitQuorum !== "string" && typeof commitQuorum !== "number") {
|
|
656
677
|
throw new Error("Commit quorum must be a string or number");
|
|
657
678
|
}
|
|
658
679
|
const processedIndexes = indexes.map((index) => {
|
|
@@ -666,7 +687,7 @@ async function handleCreateIndex(collection, args, objectIdMode = "auto", signal
|
|
|
666
687
|
});
|
|
667
688
|
try {
|
|
668
689
|
const indexOptions = {
|
|
669
|
-
commitQuorum
|
|
690
|
+
commitQuorum
|
|
670
691
|
};
|
|
671
692
|
signal?.throwIfAborted();
|
|
672
693
|
const createdIndexes = await collection.createIndexes(
|
|
@@ -682,12 +703,15 @@ async function handleCreateIndex(collection, args, objectIdMode = "auto", signal
|
|
|
682
703
|
return handleError(error, "create indexes", collection.collectionName);
|
|
683
704
|
}
|
|
684
705
|
}
|
|
685
|
-
async function handleCount(collection, args, objectIdMode = "auto", signal) {
|
|
706
|
+
async function handleCount(collection, args, objectIdMode = "auto", allowServerJs = false, signal) {
|
|
686
707
|
if (!collection) {
|
|
687
708
|
throw new Error("Collection is required for count operation");
|
|
688
709
|
}
|
|
689
710
|
const { query, limit, skip, hint, readConcern, maxTimeMS, collation } = args;
|
|
690
711
|
const countQuery = parseFilter(query, objectIdMode);
|
|
712
|
+
if (!allowServerJs) {
|
|
713
|
+
assertNoServerJs(countQuery);
|
|
714
|
+
}
|
|
691
715
|
try {
|
|
692
716
|
const options = {
|
|
693
717
|
limit: typeof limit === "number" ? limit : void 0,
|
|
@@ -758,17 +782,10 @@ async function handleListCollections(db, args, objectIdMode = "auto", signal) {
|
|
|
758
782
|
const { nameOnly, filter } = args;
|
|
759
783
|
const skip = args.skip || 0;
|
|
760
784
|
const limit = args.limit || 20;
|
|
761
|
-
let processedFilter = filter;
|
|
762
|
-
if (filter && typeof filter === "object") {
|
|
763
|
-
processedFilter = processObjectIdInFilter(
|
|
764
|
-
filter,
|
|
765
|
-
objectIdMode
|
|
766
|
-
);
|
|
767
|
-
}
|
|
768
785
|
try {
|
|
769
|
-
const
|
|
786
|
+
const processedFilter = parseFilter(filter, objectIdMode);
|
|
770
787
|
signal?.throwIfAborted();
|
|
771
|
-
const collections = await db.listCollections(
|
|
788
|
+
const collections = await db.listCollections(processedFilter).toArray();
|
|
772
789
|
const allResults = nameOnly ? collections.map((collection) => collection.name) : collections;
|
|
773
790
|
const total = allResults.length;
|
|
774
791
|
const paged = allResults.slice(skip, skip + limit);
|
|
@@ -1142,7 +1159,7 @@ async function handleReadResourceRequest({
|
|
|
1142
1159
|
`Unsupported resource URI scheme '${url.protocol}': expected 'mongodb:'`
|
|
1143
1160
|
);
|
|
1144
1161
|
}
|
|
1145
|
-
collectionName = url.pathname.replace(/^\//, "");
|
|
1162
|
+
collectionName = decodeURIComponent(url.pathname.replace(/^\//, ""));
|
|
1146
1163
|
} catch (error) {
|
|
1147
1164
|
if (error instanceof ProtocolError2) throw error;
|
|
1148
1165
|
throw new ProtocolError2(
|
|
@@ -1250,7 +1267,7 @@ async function handleListResourcesRequest({
|
|
|
1250
1267
|
signal?.throwIfAborted();
|
|
1251
1268
|
const collections = await db.listCollections().toArray();
|
|
1252
1269
|
const allResources = collections.map((collection) => ({
|
|
1253
|
-
uri: `mongodb:///${collection.name}`,
|
|
1270
|
+
uri: `mongodb:///${encodeURIComponent(collection.name)}`,
|
|
1254
1271
|
mimeType: "application/json",
|
|
1255
1272
|
name: collection.name,
|
|
1256
1273
|
description: `MongoDB collection: ${collection.name}`
|
|
@@ -1328,7 +1345,7 @@ async function handleListToolsRequest({
|
|
|
1328
1345
|
tools: [
|
|
1329
1346
|
{
|
|
1330
1347
|
name: "query",
|
|
1331
|
-
description: "
|
|
1348
|
+
description: "Find documents in one collection using a filter, projection, or sort. Use aggregate for grouping or joins. Request an execution plan to inspect the query.",
|
|
1332
1349
|
inputSchema: {
|
|
1333
1350
|
type: "object",
|
|
1334
1351
|
properties: {
|
|
@@ -1375,7 +1392,7 @@ async function handleListToolsRequest({
|
|
|
1375
1392
|
},
|
|
1376
1393
|
{
|
|
1377
1394
|
name: "aggregate",
|
|
1378
|
-
description: "
|
|
1395
|
+
description: "Run a MongoDB aggregation pipeline for grouping, joins, or multi-stage transformations. Request an execution plan to inspect the pipeline.",
|
|
1379
1396
|
inputSchema: {
|
|
1380
1397
|
type: "object",
|
|
1381
1398
|
properties: {
|
|
@@ -1407,7 +1424,7 @@ async function handleListToolsRequest({
|
|
|
1407
1424
|
},
|
|
1408
1425
|
{
|
|
1409
1426
|
name: "update",
|
|
1410
|
-
description: "
|
|
1427
|
+
description: "Modify matching documents using operators such as $set, $unset, or $inc. Enable multi to update all matches or upsert to create a document when the filter matches none.",
|
|
1411
1428
|
inputSchema: {
|
|
1412
1429
|
type: "object",
|
|
1413
1430
|
properties: {
|
|
@@ -1443,7 +1460,7 @@ async function handleListToolsRequest({
|
|
|
1443
1460
|
},
|
|
1444
1461
|
{
|
|
1445
1462
|
name: "serverInfo",
|
|
1446
|
-
description: "
|
|
1463
|
+
description: "Inspect MongoDB server metadata such as version and storage engine. Enable includeDebugInfo for additional diagnostics.",
|
|
1447
1464
|
inputSchema: {
|
|
1448
1465
|
type: "object",
|
|
1449
1466
|
properties: {
|
|
@@ -1456,7 +1473,7 @@ async function handleListToolsRequest({
|
|
|
1456
1473
|
},
|
|
1457
1474
|
{
|
|
1458
1475
|
name: "insert",
|
|
1459
|
-
description: "Insert
|
|
1476
|
+
description: "Insert new documents into a MongoDB collection. Use update to modify existing records.",
|
|
1460
1477
|
inputSchema: {
|
|
1461
1478
|
type: "object",
|
|
1462
1479
|
properties: {
|
|
@@ -1493,7 +1510,7 @@ async function handleListToolsRequest({
|
|
|
1493
1510
|
},
|
|
1494
1511
|
{
|
|
1495
1512
|
name: "createIndex",
|
|
1496
|
-
description: "Create
|
|
1513
|
+
description: "Create indexes to improve query performance, enforce uniqueness, or expire documents with a TTL index.",
|
|
1497
1514
|
inputSchema: {
|
|
1498
1515
|
type: "object",
|
|
1499
1516
|
properties: {
|
|
@@ -1544,8 +1561,8 @@ async function handleListToolsRequest({
|
|
|
1544
1561
|
description: "Write concern for index creation"
|
|
1545
1562
|
},
|
|
1546
1563
|
commitQuorum: {
|
|
1547
|
-
type: "string",
|
|
1548
|
-
description: "
|
|
1564
|
+
type: ["string", "number"],
|
|
1565
|
+
description: "Replica-set commit quorum as a member count or a mode such as majority"
|
|
1549
1566
|
},
|
|
1550
1567
|
objectIdMode: {
|
|
1551
1568
|
type: "string",
|
|
@@ -1559,7 +1576,7 @@ async function handleListToolsRequest({
|
|
|
1559
1576
|
},
|
|
1560
1577
|
{
|
|
1561
1578
|
name: "count",
|
|
1562
|
-
description: "Count documents
|
|
1579
|
+
description: "Count documents matching a filter in one collection. Use aggregate for counts grouped by field.",
|
|
1563
1580
|
inputSchema: {
|
|
1564
1581
|
type: "object",
|
|
1565
1582
|
properties: {
|
|
@@ -1589,7 +1606,7 @@ async function handleListToolsRequest({
|
|
|
1589
1606
|
},
|
|
1590
1607
|
{
|
|
1591
1608
|
name: "listCollections",
|
|
1592
|
-
description: "List
|
|
1609
|
+
description: "List collections in the connected database. Filter by name or other collection properties and request names only for a compact result.",
|
|
1593
1610
|
inputSchema: {
|
|
1594
1611
|
type: "object",
|
|
1595
1612
|
properties: {
|
|
@@ -1650,7 +1667,7 @@ function createServer(client, db, isReadOnlyMode = false, allowCrossDb = false,
|
|
|
1650
1667
|
{
|
|
1651
1668
|
name: "mongodb",
|
|
1652
1669
|
title: "MongoDB MCP Server",
|
|
1653
|
-
version: "3.0.
|
|
1670
|
+
version: "3.0.2",
|
|
1654
1671
|
description: "MCP server for MongoDB: query, aggregate, and manage collections with read-only mode, progress notifications, and cancellation",
|
|
1655
1672
|
websiteUrl: "https://github.com/kiliczsh/mcp-mongo-server",
|
|
1656
1673
|
...options
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-mongo-server",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "A Model Context Protocol server for MongoDB connections",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"node": ">=20"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
|
-
"
|
|
11
|
+
"mcp-mongo-server": "./build/index.js"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"build"
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"@biomejs/biome": "^2.5.5",
|
|
36
36
|
"@modelcontextprotocol/client": "^2.0.0",
|
|
37
37
|
"@modelcontextprotocol/inspector": "^1.0.0",
|
|
38
|
+
"@types/express": "5",
|
|
38
39
|
"@types/node": "^26.1.1",
|
|
39
40
|
"tsup": "^8.5.1",
|
|
40
41
|
"typescript": "^5.9.3"
|