mcp-mongo-server 3.0.1 → 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 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 aggregation operators `$function`, `$where`,
111
- and `$accumulator` run arbitrary JavaScript on the MongoDB server. They are
112
- rejected by default; enable them with `--allow-server-js` if you trust the
113
- pipelines being run.
114
-
115
- **Read-only mode** blocks every write path, including aggregation stages that
116
- write or run server-side JavaScript (`$out`, `$merge`, `$function`, `$where`,
117
- `$accumulator`).
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(collection, args, objectIdMode, signal);
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(collection, args, objectIdMode, signal);
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
- const js = findAggOperator(pipeline, SERVER_JS_AGG_OPERATORS);
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: typeof commitQuorum === "number" ? commitQuorum : void 0
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 options = processedFilter ? { filter: processedFilter } : {};
786
+ const processedFilter = parseFilter(filter, objectIdMode);
770
787
  signal?.throwIfAborted();
771
- const collections = await db.listCollections(options).toArray();
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: "Execute a MongoDB query with optional execution plan analysis",
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: "Execute a MongoDB aggregation pipeline with optional execution plan analysis",
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: "Update documents in a MongoDB collection",
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: "Get MongoDB server information including version, storage engine, and other details",
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 one or more documents into a MongoDB collection",
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 one or more indexes on a MongoDB collection",
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: "Number of members required to create the index"
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 in a collection matching a query",
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 all collections in the MongoDB database",
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.1",
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.1",
3
+ "version": "3.0.2",
4
4
  "description": "A Model Context Protocol server for MongoDB connections",
5
5
  "private": false,
6
6
  "type": "module",
@@ -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"