mcp-mongo-server 2.1.0 → 3.0.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.
Files changed (3) hide show
  1. package/README.md +97 -20
  2. package/build/index.js +357 -198
  3. package/package.json +9 -4
package/README.md CHANGED
@@ -4,54 +4,131 @@
4
4
  ![NPM Version](https://img.shields.io/npm/v/mcp-mongo-server)
5
5
  ![NPM Downloads](https://img.shields.io/npm/dm/mcp-mongo-server)
6
6
  ![NPM License](https://img.shields.io/npm/l/mcp-mongo-server)
7
- [![smithery badge](https://smithery.ai/badge/mcp-mongo-server)](https://smithery.ai/server/mcp-mongo-server)
8
- [![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/e274a3dd-7fe6-4440-8c43-043bae668251)
9
7
 
10
- A Model Context Protocol server that enables LLMs to interact with MongoDB databases. This server provides capabilities for inspecting collection schemas and executing MongoDB operations through a standardized interface.
8
+ A Model Context Protocol (MCP) server that lets AI assistants work with your MongoDB databases. It exposes your collections, infers their schemas, and runs queries, aggregations, and writes through a standard interface — so tools like Claude Desktop and Cursor can read and reason about your data.
11
9
 
12
10
  ## Demo
13
11
 
14
12
  [![MCP MongoDB Server Demo | Claude Desktop](https://img.youtube.com/vi/FI-oE_voCpA/0.jpg)](https://www.youtube.com/watch?v=FI-oE_voCpA)
15
13
 
14
+ ## Why use it
15
+
16
+ - **Talk to your database in plain language** — the assistant discovers your collections and their shape automatically.
17
+ - **Safe by default** — turn on read-only mode to let an assistant explore without any risk of changing data.
18
+ - **Works everywhere** — connects to standalone, replica set, sharded, and Atlas deployments, over plain or TLS connections.
19
+
16
20
  ## Key Features
17
21
 
18
- - **Smart ObjectId Handling** - Configurable auto/none/force modes for string-to-ObjectId conversion
19
- - **Read-Only Mode** - Protection against write operations, uses secondary read preference
20
- - **Schema Inference** - Automatic collection schema detection from document samples
21
- - **Query & Aggregation** - Full MongoDB query and aggregation pipeline support with optional explain plans
22
- - **Write Operations** - Insert, update, and index creation (when not in read-only mode)
23
- - **Collection Completions** - Auto-complete collection names for LLM integration
22
+ - **Read-Only Mode** — blocks every write path (insert, update, index creation, and aggregation stages like `$out`/`$merge` that could modify data).
23
+ - **Smart ObjectId Handling** — configurable `auto`/`none`/`force` conversion of 24-character hex strings to ObjectIds.
24
+ - **Schema Inference** — automatic collection schema detection from document samples.
25
+ - **Query & Aggregation** — full query and aggregation pipeline support, with optional `explain` plans.
26
+ - **Write Operations** — insert, update, and index creation (when read-only mode is off).
27
+ - **Time Conversion** — a `convertTime` helper turns Unix timestamps and date strings into UTC/GMT/ISO, so date queries stay unambiguous across timezones.
28
+ - **Progress & Cancellation** — long operations report progress and can be cancelled mid-flight.
29
+ - **Two Transports** — run locally over stdio, or expose an HTTP endpoint for remote access.
30
+
31
+ ## Requirements
24
32
 
25
- ## Installation
33
+ - Node.js 20 or newer
34
+
35
+ ## Quick Start
36
+
37
+ Point the server at your database — no install step needed:
26
38
 
27
39
  ```bash
28
- npx -y mcp-mongo-server mongodb://localhost:27017/database
40
+ npx -y mcp-mongo-server mongodb://localhost:27017/mydatabase
41
+ ```
42
+
43
+ Explore safely, without any chance of changing data:
44
+
45
+ ```bash
46
+ npx -y mcp-mongo-server mongodb://localhost:27017/mydatabase --read-only
29
47
  ```
30
48
 
31
49
  ## Usage
32
50
 
51
+ ### Local (stdio)
52
+
53
+ This is the default, used by Claude Desktop, Cursor, and other local clients:
54
+
33
55
  ```bash
34
- # Start server with MongoDB URI
35
- npx -y mcp-mongo-server mongodb://muhammed:kilic@localhost:27017/database
56
+ npx -y mcp-mongo-server "mongodb://user:pass@localhost:27017/mydatabase"
57
+ ```
58
+
59
+ ### Remote (HTTP)
60
+
61
+ Expose an HTTP endpoint at `/mcp` for remote or multi-client access:
62
+
63
+ ```bash
64
+ npx -y mcp-mongo-server "mongodb://user:pass@localhost:27017/mydatabase" --transport http --port 3001
65
+ ```
66
+
67
+ By default, only requests without a browser `Origin` (CLIs, IDEs) and requests
68
+ from `localhost` are accepted; everything else is rejected with `403` to guard
69
+ against DNS-rebinding attacks. Allow specific browser origins with
70
+ `--allowed-origins`:
36
71
 
37
- # Connect in read-only mode
38
- npx -y mcp-mongo-server mongodb://muhammed:kilic@localhost:27017/database --read-only
72
+ ```bash
73
+ npx -y mcp-mongo-server "mongodb://..." --transport http --port 3001 --allowed-origins "https://app.example.com"
39
74
  ```
40
75
 
76
+ ### Options
77
+
78
+ | Flag | Description |
79
+ |------|-------------|
80
+ | `--read-only`, `-r` | Block all write operations |
81
+ | `--allow-cross-db` | Allow aggregation `$out`/`$merge`/`$lookup` to target other databases (off by default) |
82
+ | `--allow-server-js` | Allow server-side JavaScript operators `$function`/`$where`/`$accumulator` (off by default) |
83
+ | `--transport`, `-t` | `stdio` (default) or `http` |
84
+ | `--port`, `-p` | HTTP port (default `3001`) |
85
+ | `--allowed-origins` | Comma-separated browser origins to allow in HTTP mode |
86
+ | `--json-limit` | Max HTTP request body size (default `10mb`) |
87
+ | `--auth-token` | Require `Authorization: Bearer <token>` on the HTTP endpoint |
88
+
41
89
  ### Environment Variables
42
90
 
43
91
  | Variable | Description |
44
92
  |----------|-------------|
45
- | `MCP_MONGODB_URI` | MongoDB connection URI |
93
+ | `MCP_MONGODB_URI` | MongoDB connection URI (alternative to the argument) |
46
94
  | `MCP_MONGODB_READONLY` | Enable read-only mode (`"true"`) |
95
+ | `MCP_MONGODB_ALLOW_CROSS_DB` | Allow cross-database aggregation stages (`"true"`) |
96
+ | `MCP_MONGODB_ALLOW_SERVER_JS` | Allow server-side JavaScript operators (`"true"`) |
97
+ | `MCP_PORT` | HTTP port |
98
+ | `MCP_HTTP_ALLOWED_ORIGINS` | Comma-separated browser origins to allow in HTTP mode |
99
+ | `MCP_HTTP_JSON_LIMIT` | Max HTTP request body size (default `10mb`) |
100
+ | `MCP_HTTP_AUTH_TOKEN` | Bearer token required to access the HTTP endpoint |
101
+
102
+ ## Security
103
+
104
+ **Database scope.** The server operates on the database in your connection
105
+ string. Aggregation stages that reach another database (`$out`, `$merge`,
106
+ `$lookup` with an explicit `db`) are rejected by default, so a pipeline can't
107
+ quietly read from or write to databases you didn't point it at. Enable them
108
+ with `--allow-cross-db` if you need them.
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`).
118
+
119
+ **Least privilege.** Application-level checks only go so far — the strongest
120
+ guarantee comes from the database. Connect with a MongoDB user scoped to just
121
+ the database you need, with read-only permissions when the assistant only needs
122
+ to explore. That way the database itself enforces the boundary, as defense in
123
+ depth.
47
124
 
48
125
  ## Documentation
49
126
 
50
- - [Integration Guide](docs/integration.md) - Claude Desktop, Windsurf, Cursor, Docker
51
- - [Available Tools](docs/tools.md) - Query, aggregate, update, insert, and more
52
- - [Development](docs/development.md) - Setup, scripts, and debugging
127
+ - [Integration Guide](docs/integration.md) — Claude Desktop, Windsurf, Cursor, Docker
128
+ - [Available Tools](docs/tools.md) — query, aggregate, update, insert, and more
129
+ - [Development](docs/development.md) — setup, scripts, and debugging
53
130
  - [Contributing](CONTRIBUTING.md)
54
131
 
55
132
  ## License
56
133
 
57
- MIT - see [LICENSE](LICENSE) for details.
134
+ MIT — see [LICENSE](LICENSE) for details.
package/build/index.js CHANGED
@@ -1,16 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
5
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { timingSafeEqual } from "crypto";
5
+ import { toNodeHandler } from "@modelcontextprotocol/node";
6
+ import { createMcpHandler } from "@modelcontextprotocol/server";
7
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
8
+ import { createMcpExpressApp } from "@modelcontextprotocol/express";
7
9
 
8
10
  // src/mongo.ts
9
- import { MongoClient, ReadPreference } from "mongodb";
11
+ import { MongoClient } from "mongodb";
10
12
  async function connectToMongoDB(url, readOnly) {
11
13
  try {
12
- const options = readOnly ? { readPreference: ReadPreference.SECONDARY } : {};
13
- const client = new MongoClient(url, options);
14
+ const client = new MongoClient(url);
14
15
  await client.connect();
15
16
  const db = client.db();
16
17
  console.warn(`Connected to MongoDB database: ${db.databaseName}`);
@@ -32,28 +33,10 @@ async function connectToMongoDB(url, readOnly) {
32
33
  }
33
34
 
34
35
  // src/server.ts
35
- import {
36
- InMemoryTaskMessageQueue,
37
- InMemoryTaskStore
38
- } from "@modelcontextprotocol/sdk/experimental/tasks";
39
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
40
- import {
41
- CallToolRequestSchema,
42
- CompleteRequestSchema,
43
- GetPromptRequestSchema,
44
- ListPromptsRequestSchema,
45
- ListResourcesRequestSchema,
46
- ListResourceTemplatesRequestSchema,
47
- ListToolsRequestSchema,
48
- PingRequestSchema,
49
- ReadResourceRequestSchema
50
- } from "@modelcontextprotocol/sdk/types.js";
36
+ import { Server } from "@modelcontextprotocol/server";
51
37
 
52
38
  // src/schemas/call.ts
53
- import {
54
- ErrorCode,
55
- McpError
56
- } from "@modelcontextprotocol/sdk/types.js";
39
+ import { ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/server";
57
40
  import { ObjectId } from "mongodb";
58
41
  var COLLECTION_OPERATIONS = [
59
42
  "query",
@@ -64,14 +47,71 @@ var COLLECTION_OPERATIONS = [
64
47
  "count"
65
48
  ];
66
49
  var WRITE_OPERATIONS = ["update", "insert", "createIndex"];
50
+ var MAX_OBJECT_DEPTH = 100;
51
+ var WRITE_AGG_STAGES = /* @__PURE__ */ new Set(["$out", "$merge"]);
52
+ var SERVER_JS_AGG_OPERATORS = /* @__PURE__ */ new Set([
53
+ "$function",
54
+ "$accumulator",
55
+ "$where"
56
+ ]);
57
+ var READONLY_FORBIDDEN_AGG_OPERATORS = /* @__PURE__ */ new Set([
58
+ ...WRITE_AGG_STAGES,
59
+ ...SERVER_JS_AGG_OPERATORS
60
+ ]);
61
+ function findAggOperator(value, operators, depth = 0) {
62
+ if (depth > MAX_OBJECT_DEPTH) {
63
+ throw new Error(
64
+ `Object nesting exceeds the maximum depth of ${MAX_OBJECT_DEPTH}`
65
+ );
66
+ }
67
+ if (Array.isArray(value)) {
68
+ for (const item of value) {
69
+ const found = findAggOperator(item, operators, depth + 1);
70
+ if (found) return found;
71
+ }
72
+ return null;
73
+ }
74
+ if (value && typeof value === "object") {
75
+ for (const [key, nested] of Object.entries(value)) {
76
+ if (operators.has(key)) {
77
+ return key;
78
+ }
79
+ const found = findAggOperator(nested, operators, depth + 1);
80
+ if (found) return found;
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+ function findCrossDbTarget(pipeline, connectedDb) {
86
+ const foreignDb = (target) => {
87
+ if (target && typeof target === "object" && "db" in target) {
88
+ const db = target.db;
89
+ if (typeof db === "string" && db && db !== connectedDb) return db;
90
+ }
91
+ return null;
92
+ };
93
+ for (const stage of pipeline) {
94
+ if (!stage || typeof stage !== "object") continue;
95
+ const s = stage;
96
+ const out = foreignDb(s.$out);
97
+ if (out) return out;
98
+ const merge = s.$merge;
99
+ const mergeInto = foreignDb(merge?.into ?? s.$merge);
100
+ if (mergeInto) return mergeInto;
101
+ const lookup = s.$lookup;
102
+ const lookupFrom = foreignDb(lookup?.from);
103
+ if (lookupFrom) return lookupFrom;
104
+ }
105
+ return null;
106
+ }
67
107
  async function handleCallToolRequest({
68
108
  request,
69
109
  client,
70
110
  db,
71
111
  isReadOnlyMode,
72
- signal,
73
- taskStore,
74
- taskTtl
112
+ allowCrossDb = false,
113
+ allowServerJs = false,
114
+ signal
75
115
  }) {
76
116
  const { name, arguments: args = {} } = request.params;
77
117
  const operation = name;
@@ -101,46 +141,19 @@ async function handleCallToolRequest({
101
141
  validateCollection(collection);
102
142
  }
103
143
  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 };
132
- }
133
144
  return await executeOperation(
134
145
  operation,
135
146
  collection,
136
147
  db,
137
148
  isReadOnlyMode,
149
+ allowCrossDb,
150
+ allowServerJs,
138
151
  args,
139
152
  objectIdMode,
140
153
  signal
141
154
  );
142
155
  } catch (error) {
143
- if (error instanceof McpError || signal?.aborted) {
156
+ if (error instanceof ProtocolError || signal?.aborted) {
144
157
  throw error;
145
158
  }
146
159
  return toolExecutionError(
@@ -148,12 +161,20 @@ async function handleCallToolRequest({
148
161
  );
149
162
  }
150
163
  }
151
- async function executeOperation(operation, collection, db, isReadOnlyMode, args, objectIdMode, signal) {
164
+ async function executeOperation(operation, collection, db, isReadOnlyMode, allowCrossDb, allowServerJs, args, objectIdMode, signal) {
152
165
  switch (operation) {
153
166
  case "query":
154
167
  return handleQuery(collection, args, objectIdMode, signal);
155
168
  case "aggregate":
156
- return handleAggregate(collection, args, objectIdMode, signal);
169
+ return handleAggregate(
170
+ collection,
171
+ args,
172
+ objectIdMode,
173
+ isReadOnlyMode,
174
+ allowCrossDb,
175
+ allowServerJs,
176
+ signal
177
+ );
157
178
  case "update":
158
179
  return handleUpdate(collection, args, objectIdMode, signal);
159
180
  case "serverInfo":
@@ -166,8 +187,10 @@ async function executeOperation(operation, collection, db, isReadOnlyMode, args,
166
187
  return handleCount(collection, args, objectIdMode, signal);
167
188
  case "listCollections":
168
189
  return handleListCollections(db, args, objectIdMode, signal);
190
+ case "convertTime":
191
+ return handleConvertTime(args);
169
192
  default:
170
- throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${operation}`);
193
+ throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Unknown tool: ${operation}`);
171
194
  }
172
195
  }
173
196
  function validateOperation(operation) {
@@ -179,17 +202,18 @@ function validateOperation(operation) {
179
202
  "insert",
180
203
  "createIndex",
181
204
  "count",
182
- "listCollections"
205
+ "listCollections",
206
+ "convertTime"
183
207
  ];
184
208
  if (!validOperations.includes(operation)) {
185
- throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${operation}`);
209
+ throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Unknown tool: ${operation}`);
186
210
  }
187
211
  }
188
212
  function validateCollection(collection) {
189
213
  if (!collection.collectionName) {
190
214
  throw new Error("Collection name cannot be empty");
191
215
  }
192
- if (collection.collectionName.startsWith("system.")) {
216
+ if (collection.collectionName.toLowerCase().startsWith("system.")) {
193
217
  throw new Error("Access to system collections is not allowed");
194
218
  }
195
219
  }
@@ -236,7 +260,12 @@ function isObjectIdField(fieldName) {
236
260
  const lowerFieldName = fieldName.toLowerCase();
237
261
  return lowerFieldName === "_id" || lowerFieldName === "id" || lowerFieldName.endsWith("id") || lowerFieldName.endsWith("_id");
238
262
  }
239
- function processObjectIdInFilter(filter, objectIdMode = "auto") {
263
+ function processObjectIdInFilter(filter, objectIdMode = "auto", depth = 0) {
264
+ if (depth > MAX_OBJECT_DEPTH) {
265
+ throw new Error(
266
+ `Object nesting exceeds the maximum depth of ${MAX_OBJECT_DEPTH}`
267
+ );
268
+ }
240
269
  if (objectIdMode === "none") {
241
270
  const result2 = {};
242
271
  for (const [key, value] of Object.entries(filter)) {
@@ -263,7 +292,8 @@ function processObjectIdInFilter(filter, objectIdMode = "auto") {
263
292
  } else {
264
293
  result2[key] = processObjectIdInFilter(
265
294
  value,
266
- "none"
295
+ "none",
296
+ depth + 1
267
297
  );
268
298
  }
269
299
  } else {
@@ -305,7 +335,8 @@ function processObjectIdInFilter(filter, objectIdMode = "auto") {
305
335
  } else {
306
336
  result[key] = processObjectIdInFilter(
307
337
  value,
308
- objectIdMode
338
+ objectIdMode,
339
+ depth + 1
309
340
  );
310
341
  }
311
342
  } else {
@@ -318,7 +349,9 @@ function isObjectIdString(str) {
318
349
  return /^[0-9a-fA-F]{24}$/.test(str);
319
350
  }
320
351
  function isISODateString(str) {
321
- return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/.test(str);
352
+ return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?(Z|[+-]\d{2}:\d{2})$/.test(
353
+ str
354
+ );
322
355
  }
323
356
  function formatResponse(data) {
324
357
  return {
@@ -387,7 +420,7 @@ async function handleQuery(collection, args, objectIdMode = "auto", signal) {
387
420
  return handleError(error, "query", collection.collectionName);
388
421
  }
389
422
  }
390
- async function handleAggregate(collection, args, objectIdMode = "auto", signal) {
423
+ async function handleAggregate(collection, args, objectIdMode = "auto", isReadOnlyMode = false, allowCrossDb = false, allowServerJs = false, signal) {
391
424
  if (!collection) {
392
425
  throw new Error("Collection is required for aggregate operation");
393
426
  }
@@ -395,6 +428,33 @@ async function handleAggregate(collection, args, objectIdMode = "auto", signal)
395
428
  if (!Array.isArray(pipeline)) {
396
429
  throw new Error("Pipeline must be an array");
397
430
  }
431
+ if (isReadOnlyMode) {
432
+ const forbidden = findAggOperator(
433
+ pipeline,
434
+ READONLY_FORBIDDEN_AGG_OPERATORS
435
+ );
436
+ if (forbidden) {
437
+ throw new Error(
438
+ `ReadonlyError: Aggregation operator '${forbidden}' is not allowed in read-only mode`
439
+ );
440
+ }
441
+ }
442
+ 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
+ }
449
+ }
450
+ if (!allowCrossDb) {
451
+ const foreignDb = findCrossDbTarget(pipeline, collection.dbName);
452
+ if (foreignDb) {
453
+ throw new Error(
454
+ `Cross-database access to '${foreignDb}' is not allowed (connected database is '${collection.dbName}'). Enable it with --allow-cross-db or MCP_MONGODB_ALLOW_CROSS_DB=true.`
455
+ );
456
+ }
457
+ }
398
458
  const processedPipeline = pipeline.map((stage) => {
399
459
  if (typeof stage === "object" && stage !== null) {
400
460
  return processObjectIdInFilter(
@@ -488,32 +548,27 @@ async function handleServerInfo(db, isReadOnlyMode, args, signal) {
488
548
  try {
489
549
  signal?.throwIfAborted();
490
550
  const buildInfo = await db.command({ buildInfo: 1 });
491
- let serverStatus = null;
492
- if (includeDebugInfo) {
493
- signal?.throwIfAborted();
494
- serverStatus = await db.command({ serverStatus: 1 });
495
- }
496
551
  const serverInfo = {
497
552
  version: buildInfo.version,
498
553
  gitVersion: buildInfo.gitVersion,
499
- modules: buildInfo.modules,
500
- allocator: buildInfo.allocator,
501
- javascriptEngine: buildInfo.javascriptEngine,
502
- sysInfo: buildInfo.sysInfo,
503
554
  storageEngines: buildInfo.storageEngines,
504
- debug: buildInfo.debug,
505
555
  maxBsonObjectSize: buildInfo.maxBsonObjectSize,
506
- openssl: buildInfo.openssl,
507
- buildEnvironment: buildInfo.buildEnvironment,
508
556
  bits: buildInfo.bits,
509
557
  ok: buildInfo.ok,
510
- status: {},
511
558
  connectionInfo: {
512
- readOnlyMode: isReadOnlyMode,
513
- readPreference: isReadOnlyMode ? "secondary" : "primary"
559
+ readOnlyMode: isReadOnlyMode
514
560
  }
515
561
  };
516
- if (serverStatus) {
562
+ if (includeDebugInfo) {
563
+ signal?.throwIfAborted();
564
+ const serverStatus = await db.command({ serverStatus: 1 });
565
+ serverInfo.modules = buildInfo.modules;
566
+ serverInfo.allocator = buildInfo.allocator;
567
+ serverInfo.javascriptEngine = buildInfo.javascriptEngine;
568
+ serverInfo.sysInfo = buildInfo.sysInfo;
569
+ serverInfo.debug = buildInfo.debug;
570
+ serverInfo.openssl = buildInfo.openssl;
571
+ serverInfo.buildEnvironment = buildInfo.buildEnvironment;
517
572
  serverInfo.status = {
518
573
  host: serverStatus.host,
519
574
  version: serverStatus.version,
@@ -614,15 +669,14 @@ async function handleCreateIndex(collection, args, objectIdMode = "auto", signal
614
669
  commitQuorum: typeof commitQuorum === "number" ? commitQuorum : void 0
615
670
  };
616
671
  signal?.throwIfAborted();
617
- const result = await collection.createIndexes(
672
+ const createdIndexes = await collection.createIndexes(
618
673
  processedIndexes,
619
674
  indexOptions
620
675
  );
621
676
  return formatResponse({
622
- acknowledged: result.acknowledged,
623
- createdIndexes: result.createdIndexes,
624
- numIndexesBefore: result.numIndexesBefore,
625
- numIndexesAfter: result.numIndexesAfter
677
+ acknowledged: true,
678
+ createdIndexes,
679
+ indexCount: createdIndexes.length
626
680
  });
627
681
  } catch (error) {
628
682
  return handleError(error, "create indexes", collection.collectionName);
@@ -658,6 +712,48 @@ async function handleCount(collection, args, objectIdMode = "auto", signal) {
658
712
  return handleError(error, "count documents", collection.collectionName);
659
713
  }
660
714
  }
715
+ function handleConvertTime(args) {
716
+ const { input } = args;
717
+ let date;
718
+ if (input === void 0 || input === null || input === "") {
719
+ date = /* @__PURE__ */ new Date();
720
+ } else if (typeof input === "number") {
721
+ date = new Date(Math.abs(input) < 1e12 ? input * 1e3 : input);
722
+ } else if (typeof input === "string") {
723
+ const trimmed = input.trim();
724
+ if (/^-?\d+$/.test(trimmed)) {
725
+ const n = Number(trimmed);
726
+ date = new Date(Math.abs(n) < 1e12 ? n * 1e3 : n);
727
+ } else {
728
+ date = new Date(trimmed);
729
+ }
730
+ } else {
731
+ throw new Error(
732
+ "convertTime 'input' must be a Unix timestamp (number) or a date string"
733
+ );
734
+ }
735
+ if (Number.isNaN(date.getTime())) {
736
+ throw new Error(
737
+ `Could not interpret '${String(input)}' as a date or Unix timestamp`
738
+ );
739
+ }
740
+ const offsetMinutes = -date.getTimezoneOffset();
741
+ const sign = offsetMinutes >= 0 ? "+" : "-";
742
+ const absMinutes = Math.abs(offsetMinutes);
743
+ const offsetLabel = `${sign}${String(Math.floor(absMinutes / 60)).padStart(
744
+ 2,
745
+ "0"
746
+ )}:${String(absMinutes % 60).padStart(2, "0")}`;
747
+ return formatResponse({
748
+ iso: date.toISOString(),
749
+ utc: date.toUTCString(),
750
+ unixSeconds: Math.floor(date.getTime() / 1e3),
751
+ unixMillis: date.getTime(),
752
+ serverLocal: date.toString(),
753
+ serverTimeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
754
+ serverUtcOffset: offsetLabel
755
+ });
756
+ }
661
757
  async function handleListCollections(db, args, objectIdMode = "auto", signal) {
662
758
  const { nameOnly, filter } = args;
663
759
  const skip = args.skip || 0;
@@ -936,6 +1032,10 @@ ${JSON.stringify(sampleDocs, null, 2)}`
936
1032
  }
937
1033
 
938
1034
  // src/schemas/resource.ts
1035
+ import {
1036
+ ProtocolError as ProtocolError2,
1037
+ ProtocolErrorCode as ProtocolErrorCode2
1038
+ } from "@modelcontextprotocol/server";
939
1039
  import { ObjectId as ObjectId2 } from "mongodb";
940
1040
  function detectMongoType(value) {
941
1041
  if (value === null) return "null";
@@ -1033,8 +1133,35 @@ async function handleReadResourceRequest({
1033
1133
  signal,
1034
1134
  sendProgress
1035
1135
  }) {
1036
- const url = new URL(request.params.uri);
1037
- const collectionName = url.pathname.replace(/^\//, "");
1136
+ let collectionName;
1137
+ try {
1138
+ const url = new URL(request.params.uri);
1139
+ if (url.protocol !== "mongodb:") {
1140
+ throw new ProtocolError2(
1141
+ ProtocolErrorCode2.InvalidParams,
1142
+ `Unsupported resource URI scheme '${url.protocol}': expected 'mongodb:'`
1143
+ );
1144
+ }
1145
+ collectionName = url.pathname.replace(/^\//, "");
1146
+ } catch (error) {
1147
+ if (error instanceof ProtocolError2) throw error;
1148
+ throw new ProtocolError2(
1149
+ ProtocolErrorCode2.InvalidParams,
1150
+ `Invalid resource URI: ${request.params.uri}`
1151
+ );
1152
+ }
1153
+ if (!collectionName) {
1154
+ throw new ProtocolError2(
1155
+ ProtocolErrorCode2.InvalidParams,
1156
+ "Resource URI is missing a collection name"
1157
+ );
1158
+ }
1159
+ if (collectionName.startsWith("system.")) {
1160
+ throw new ProtocolError2(
1161
+ ProtocolErrorCode2.InvalidParams,
1162
+ "Access to system collections is not allowed"
1163
+ );
1164
+ }
1038
1165
  try {
1039
1166
  const collection = db.collection(collectionName);
1040
1167
  const sampleSize = 100;
@@ -1202,7 +1329,6 @@ async function handleListToolsRequest({
1202
1329
  {
1203
1330
  name: "query",
1204
1331
  description: "Execute a MongoDB query with optional execution plan analysis",
1205
- execution: { taskSupport: "optional" },
1206
1332
  inputSchema: {
1207
1333
  type: "object",
1208
1334
  properties: {
@@ -1228,6 +1354,10 @@ async function handleListToolsRequest({
1228
1354
  description: "Number of documents to skip before returning results",
1229
1355
  default: 0
1230
1356
  },
1357
+ sort: {
1358
+ type: "object",
1359
+ description: "Sort order as a field-to-direction map, e.g. { orderDate: -1 } for descending or { name: 1 } for ascending"
1360
+ },
1231
1361
  explain: {
1232
1362
  type: "string",
1233
1363
  description: "Optional: Get query execution information",
@@ -1246,7 +1376,6 @@ async function handleListToolsRequest({
1246
1376
  {
1247
1377
  name: "aggregate",
1248
1378
  description: "Execute a MongoDB aggregation pipeline with optional execution plan analysis",
1249
- execution: { taskSupport: "optional" },
1250
1379
  inputSchema: {
1251
1380
  type: "object",
1252
1381
  properties: {
@@ -1279,7 +1408,6 @@ async function handleListToolsRequest({
1279
1408
  {
1280
1409
  name: "update",
1281
1410
  description: "Update documents in a MongoDB collection",
1282
- execution: { taskSupport: "optional" },
1283
1411
  inputSchema: {
1284
1412
  type: "object",
1285
1413
  properties: {
@@ -1316,7 +1444,6 @@ async function handleListToolsRequest({
1316
1444
  {
1317
1445
  name: "serverInfo",
1318
1446
  description: "Get MongoDB server information including version, storage engine, and other details",
1319
- execution: { taskSupport: "optional" },
1320
1447
  inputSchema: {
1321
1448
  type: "object",
1322
1449
  properties: {
@@ -1330,7 +1457,6 @@ async function handleListToolsRequest({
1330
1457
  {
1331
1458
  name: "insert",
1332
1459
  description: "Insert one or more documents into a MongoDB collection",
1333
- execution: { taskSupport: "optional" },
1334
1460
  inputSchema: {
1335
1461
  type: "object",
1336
1462
  properties: {
@@ -1368,7 +1494,6 @@ async function handleListToolsRequest({
1368
1494
  {
1369
1495
  name: "createIndex",
1370
1496
  description: "Create one or more indexes on a MongoDB collection",
1371
- execution: { taskSupport: "optional" },
1372
1497
  inputSchema: {
1373
1498
  type: "object",
1374
1499
  properties: {
@@ -1435,7 +1560,6 @@ async function handleListToolsRequest({
1435
1560
  {
1436
1561
  name: "count",
1437
1562
  description: "Count documents in a collection matching a query",
1438
- execution: { taskSupport: "optional" },
1439
1563
  inputSchema: {
1440
1564
  type: "object",
1441
1565
  properties: {
@@ -1466,7 +1590,6 @@ async function handleListToolsRequest({
1466
1590
  {
1467
1591
  name: "listCollections",
1468
1592
  description: "List all collections in the MongoDB database",
1469
- execution: { taskSupport: "optional" },
1470
1593
  inputSchema: {
1471
1594
  type: "object",
1472
1595
  properties: {
@@ -1496,29 +1619,39 @@ async function handleListToolsRequest({
1496
1619
  }
1497
1620
  }
1498
1621
  }
1622
+ },
1623
+ {
1624
+ name: "convertTime",
1625
+ description: "Convert a Unix timestamp or date string to multiple formats (UTC ISO 8601, GMT, Unix seconds/milliseconds) and report the server's current time and timezone. Useful for building unambiguous date queries when the server and user are in different timezones. Omit 'input' to get the current time.",
1626
+ inputSchema: {
1627
+ type: "object",
1628
+ properties: {
1629
+ input: {
1630
+ type: "string",
1631
+ description: "A Unix timestamp (seconds or milliseconds, as a number or numeric string) or a date string (ISO 8601, with or without a timezone offset). Omit to use the current server time."
1632
+ }
1633
+ }
1634
+ }
1499
1635
  }
1500
1636
  ]
1501
1637
  };
1502
1638
  }
1503
1639
 
1504
1640
  // src/server.ts
1505
- function createSendProgress(extra) {
1506
- const progressToken = extra._meta?.progressToken;
1641
+ function createSendProgress(progressToken, notify) {
1507
1642
  if (progressToken === void 0) return void 0;
1508
- return (progress, total, message) => extra.sendNotification({
1643
+ return (progress, total, message) => notify({
1509
1644
  method: "notifications/progress",
1510
1645
  params: { progressToken, progress, total, message }
1511
1646
  });
1512
1647
  }
1513
- function createServer(client, db, isReadOnlyMode = false, options = {}) {
1514
- const taskStore = new InMemoryTaskStore();
1515
- const taskMessageQueue = new InMemoryTaskMessageQueue();
1648
+ function createServer(client, db, isReadOnlyMode = false, allowCrossDb = false, allowServerJs = false, options = {}) {
1516
1649
  const server = new Server(
1517
1650
  {
1518
1651
  name: "mongodb",
1519
1652
  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",
1653
+ version: "3.0.0",
1654
+ description: "MCP server for MongoDB: query, aggregate, and manage collections with read-only mode, progress notifications, and cancellation",
1522
1655
  websiteUrl: "https://github.com/kiliczsh/mcp-mongo-server",
1523
1656
  ...options
1524
1657
  },
@@ -1527,110 +1660,109 @@ function createServer(client, db, isReadOnlyMode = false, options = {}) {
1527
1660
  completions: {},
1528
1661
  resources: {},
1529
1662
  tools: {},
1530
- prompts: {},
1531
- tasks: {
1532
- list: {},
1533
- cancel: {},
1534
- requests: { tools: { call: {} } }
1535
- }
1663
+ prompts: {}
1536
1664
  },
1537
- taskStore,
1538
- taskMessageQueue,
1539
1665
  ...options
1540
1666
  }
1541
1667
  );
1542
1668
  server.setRequestHandler(
1543
- PingRequestSchema,
1544
- (request, extra) => handlePingRequest({
1669
+ "ping",
1670
+ (request, ctx) => handlePingRequest({
1545
1671
  request,
1546
1672
  client,
1547
1673
  db,
1548
1674
  isReadOnlyMode,
1549
- signal: extra.signal
1675
+ signal: ctx.mcpReq.signal
1550
1676
  })
1551
1677
  );
1552
1678
  server.setRequestHandler(
1553
- ListResourcesRequestSchema,
1554
- (request, extra) => handleListResourcesRequest({
1679
+ "resources/list",
1680
+ (request, ctx) => handleListResourcesRequest({
1555
1681
  request,
1556
1682
  client,
1557
1683
  db,
1558
1684
  isReadOnlyMode,
1559
- signal: extra.signal
1685
+ signal: ctx.mcpReq.signal
1560
1686
  })
1561
1687
  );
1562
1688
  server.setRequestHandler(
1563
- ReadResourceRequestSchema,
1564
- (request, extra) => handleReadResourceRequest({
1689
+ "resources/read",
1690
+ (request, ctx) => handleReadResourceRequest({
1565
1691
  request,
1566
1692
  client,
1567
1693
  db,
1568
1694
  isReadOnlyMode,
1569
- signal: extra.signal,
1570
- sendProgress: createSendProgress(extra)
1695
+ signal: ctx.mcpReq.signal,
1696
+ sendProgress: createSendProgress(
1697
+ request.params._meta?.progressToken,
1698
+ ctx.mcpReq.notify
1699
+ )
1571
1700
  })
1572
1701
  );
1573
1702
  server.setRequestHandler(
1574
- ListToolsRequestSchema,
1575
- (request, extra) => handleListToolsRequest({
1703
+ "tools/list",
1704
+ (request, ctx) => handleListToolsRequest({
1576
1705
  request,
1577
1706
  client,
1578
1707
  db,
1579
1708
  isReadOnlyMode,
1580
- signal: extra.signal
1709
+ signal: ctx.mcpReq.signal
1581
1710
  })
1582
1711
  );
1583
- server.setRequestHandler(CallToolRequestSchema, (request, extra) => {
1584
- const hasTaskParams = !!request.params.task;
1585
- return handleCallToolRequest({
1712
+ server.setRequestHandler(
1713
+ "tools/call",
1714
+ (request, ctx) => handleCallToolRequest({
1586
1715
  request,
1587
1716
  client,
1588
1717
  db,
1589
1718
  isReadOnlyMode,
1590
- signal: extra.signal,
1591
- taskStore: hasTaskParams ? extra.taskStore : void 0,
1592
- taskTtl: hasTaskParams ? extra.taskRequestedTtl : void 0
1593
- });
1594
- });
1719
+ allowCrossDb,
1720
+ allowServerJs,
1721
+ signal: ctx.mcpReq.signal
1722
+ })
1723
+ );
1595
1724
  server.setRequestHandler(
1596
- ListPromptsRequestSchema,
1597
- (request, extra) => handleListPromptsRequest({
1725
+ "prompts/list",
1726
+ (request, ctx) => handleListPromptsRequest({
1598
1727
  request,
1599
1728
  client,
1600
1729
  db,
1601
1730
  isReadOnlyMode,
1602
- signal: extra.signal
1731
+ signal: ctx.mcpReq.signal
1603
1732
  })
1604
1733
  );
1605
1734
  server.setRequestHandler(
1606
- GetPromptRequestSchema,
1607
- (request, extra) => handleGetPromptRequest({
1735
+ "prompts/get",
1736
+ (request, ctx) => handleGetPromptRequest({
1608
1737
  request,
1609
1738
  client,
1610
1739
  db,
1611
1740
  isReadOnlyMode,
1612
- signal: extra.signal,
1613
- sendProgress: createSendProgress(extra)
1741
+ signal: ctx.mcpReq.signal,
1742
+ sendProgress: createSendProgress(
1743
+ request.params._meta?.progressToken,
1744
+ ctx.mcpReq.notify
1745
+ )
1614
1746
  })
1615
1747
  );
1616
1748
  server.setRequestHandler(
1617
- ListResourceTemplatesRequestSchema,
1618
- (request, extra) => handleListResourceTemplatesRequest({
1749
+ "resources/templates/list",
1750
+ (request, ctx) => handleListResourceTemplatesRequest({
1619
1751
  request,
1620
1752
  client,
1621
1753
  db,
1622
1754
  isReadOnlyMode,
1623
- signal: extra.signal
1755
+ signal: ctx.mcpReq.signal
1624
1756
  })
1625
1757
  );
1626
1758
  server.setRequestHandler(
1627
- CompleteRequestSchema,
1628
- (request, extra) => handleCompletionRequest({
1759
+ "completion/complete",
1760
+ (request, ctx) => handleCompletionRequest({
1629
1761
  request,
1630
1762
  client,
1631
1763
  db,
1632
1764
  isReadOnlyMode,
1633
- signal: extra.signal
1765
+ signal: ctx.mcpReq.signal
1634
1766
  })
1635
1767
  );
1636
1768
  return server;
@@ -1642,12 +1774,20 @@ async function main() {
1642
1774
  const args = process.argv.slice(2);
1643
1775
  let connectionUrl = "";
1644
1776
  let readOnlyMode = process.env.MCP_MONGODB_READONLY === "true" || false;
1777
+ let allowCrossDb = process.env.MCP_MONGODB_ALLOW_CROSS_DB === "true" || false;
1778
+ let allowServerJs = process.env.MCP_MONGODB_ALLOW_SERVER_JS === "true" || false;
1645
1779
  let transportMode = "stdio";
1646
1780
  let port = Number(process.env.MCP_PORT) || 3001;
1647
1781
  const allowedOrigins = (process.env.MCP_HTTP_ALLOWED_ORIGINS || "").split(",").map((origin) => origin.trim()).filter(Boolean);
1782
+ let jsonLimit = process.env.MCP_HTTP_JSON_LIMIT || "10mb";
1783
+ let authToken = process.env.MCP_HTTP_AUTH_TOKEN || "";
1648
1784
  for (let i = 0; i < args.length; i++) {
1649
1785
  if (args[i] === "--read-only" || args[i] === "-r") {
1650
1786
  readOnlyMode = true;
1787
+ } else if (args[i] === "--allow-cross-db") {
1788
+ allowCrossDb = true;
1789
+ } else if (args[i] === "--allow-server-js") {
1790
+ allowServerJs = true;
1651
1791
  } else if (args[i] === "--transport" || args[i] === "-t") {
1652
1792
  const value = args[++i];
1653
1793
  if (value !== "stdio" && value !== "http") {
@@ -1665,6 +1805,12 @@ async function main() {
1665
1805
  allowedOrigins.push(
1666
1806
  ...(args[++i] || "").split(",").map((origin) => origin.trim()).filter(Boolean)
1667
1807
  );
1808
+ } else if (args[i] === "--json-limit") {
1809
+ const value = args[++i];
1810
+ if (value) jsonLimit = value;
1811
+ } else if (args[i] === "--auth-token") {
1812
+ const value = args[++i];
1813
+ if (value) authToken = value;
1668
1814
  } else if (!connectionUrl) {
1669
1815
  connectionUrl = args[i];
1670
1816
  }
@@ -1701,9 +1847,25 @@ async function main() {
1701
1847
  process.exit(1);
1702
1848
  }
1703
1849
  if (transportMode === "http") {
1704
- await startHttpServer(client, db, isReadOnlyMode, port, allowedOrigins);
1850
+ await startHttpServer(
1851
+ client,
1852
+ db,
1853
+ isReadOnlyMode,
1854
+ allowCrossDb,
1855
+ allowServerJs,
1856
+ port,
1857
+ allowedOrigins,
1858
+ jsonLimit,
1859
+ authToken
1860
+ );
1705
1861
  } else {
1706
- await startStdioServer(client, db, isReadOnlyMode);
1862
+ await startStdioServer(
1863
+ client,
1864
+ db,
1865
+ isReadOnlyMode,
1866
+ allowCrossDb,
1867
+ allowServerJs
1868
+ );
1707
1869
  }
1708
1870
  } catch (error) {
1709
1871
  console.error("Failed to connect to MongoDB:", error);
@@ -1713,10 +1875,10 @@ async function main() {
1713
1875
  process.exit(1);
1714
1876
  }
1715
1877
  }
1716
- async function startStdioServer(client, db, isReadOnlyMode) {
1717
- const server = createServer(client, db, isReadOnlyMode);
1718
- const transport = new StdioServerTransport();
1719
- await server.connect(transport);
1878
+ async function startStdioServer(client, db, isReadOnlyMode, allowCrossDb, allowServerJs) {
1879
+ serveStdio(
1880
+ () => createServer(client, db, isReadOnlyMode, allowCrossDb, allowServerJs)
1881
+ );
1720
1882
  console.warn("Server connected successfully via stdio");
1721
1883
  }
1722
1884
  function isOriginAllowed(origin, allowedOrigins) {
@@ -1733,8 +1895,17 @@ function isOriginAllowed(origin, allowedOrigins) {
1733
1895
  return false;
1734
1896
  }
1735
1897
  }
1736
- async function startHttpServer(client, db, isReadOnlyMode, port, allowedOrigins) {
1737
- const app = createMcpExpressApp({ host: "0.0.0.0" });
1898
+ function bearerTokenValid(authorization, expected) {
1899
+ const prefix = "Bearer ";
1900
+ if (!authorization?.startsWith(prefix)) {
1901
+ return false;
1902
+ }
1903
+ const provided = Buffer.from(authorization.slice(prefix.length));
1904
+ const wanted = Buffer.from(expected);
1905
+ return provided.length === wanted.length && timingSafeEqual(provided, wanted);
1906
+ }
1907
+ async function startHttpServer(client, db, isReadOnlyMode, allowCrossDb, allowServerJs, port, allowedOrigins, jsonLimit, authToken) {
1908
+ const app = createMcpExpressApp({ host: "0.0.0.0", jsonLimit });
1738
1909
  app.use((req, res, next) => {
1739
1910
  const start = Date.now();
1740
1911
  const ip = req.headers["x-forwarded-for"] || req.socket.remoteAddress;
@@ -1767,56 +1938,44 @@ async function startHttpServer(client, db, isReadOnlyMode, port, allowedOrigins)
1767
1938
  }
1768
1939
  next();
1769
1940
  });
1770
- app.post("/mcp", async (req, res) => {
1771
- const server = createServer(client, db, isReadOnlyMode);
1772
- try {
1773
- const transport = new StreamableHTTPServerTransport({
1774
- sessionIdGenerator: void 0
1775
- });
1776
- await server.connect(transport);
1777
- await transport.handleRequest(req, res, req.body);
1778
- res.on("close", () => {
1779
- transport.close();
1780
- server.close();
1781
- });
1782
- } catch (error) {
1783
- console.error("Error handling MCP request:", error);
1784
- if (!res.headersSent) {
1785
- res.status(500).json({
1941
+ if (authToken) {
1942
+ app.use((req, res, next) => {
1943
+ if (!bearerTokenValid(req.headers.authorization, authToken)) {
1944
+ res.status(401).set("WWW-Authenticate", 'Bearer realm="mcp", error="invalid_token"').json({
1786
1945
  jsonrpc: "2.0",
1787
1946
  error: {
1788
- code: -32603,
1789
- message: "Internal server error"
1947
+ code: -32001,
1948
+ message: "Unauthorized: missing or invalid bearer token"
1790
1949
  },
1791
1950
  id: null
1792
1951
  });
1952
+ return;
1793
1953
  }
1794
- }
1795
- });
1796
- app.get("/mcp", async (_req, res) => {
1797
- res.writeHead(405).end(
1798
- JSON.stringify({
1799
- jsonrpc: "2.0",
1800
- error: {
1801
- code: -32e3,
1802
- message: "Method not allowed."
1803
- },
1804
- id: null
1805
- })
1806
- );
1954
+ next();
1955
+ });
1956
+ }
1957
+ const mcpHandler = createMcpHandler(
1958
+ (_ctx) => createServer(client, db, isReadOnlyMode, allowCrossDb, allowServerJs)
1959
+ );
1960
+ const nodeHandler = toNodeHandler(mcpHandler, {
1961
+ onerror: (error) => console.error("Error handling MCP request:", error)
1807
1962
  });
1808
- app.delete("/mcp", async (_req, res) => {
1809
- res.writeHead(405).end(
1810
- JSON.stringify({
1963
+ app.all("/mcp", (req, res) => nodeHandler(req, res, req.body));
1964
+ app.use(
1965
+ (err, _req, res, next) => {
1966
+ if (res.headersSent) return next(err);
1967
+ const status = err.status ?? err.statusCode ?? 400;
1968
+ const tooLarge = err.type === "entity.too.large" || status === 413;
1969
+ res.status(tooLarge ? 413 : 400).json({
1811
1970
  jsonrpc: "2.0",
1812
1971
  error: {
1813
- code: -32e3,
1814
- message: "Method not allowed."
1972
+ code: tooLarge ? -32600 : -32700,
1973
+ message: tooLarge ? "Request body exceeds the configured size limit" : `Invalid request body: ${err.message ?? "parse error"}`
1815
1974
  },
1816
1975
  id: null
1817
- })
1818
- );
1819
- });
1976
+ });
1977
+ }
1978
+ );
1820
1979
  app.listen(port, () => {
1821
1980
  console.log(`MCP MongoDB Streamable HTTP Server listening on port ${port}`);
1822
1981
  console.log(`Endpoint: http://localhost:${port}/mcp`);
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "mcp-mongo-server",
3
- "version": "2.1.0",
3
+ "version": "3.0.0",
4
4
  "description": "A Model Context Protocol server for MongoDB connections",
5
5
  "private": false,
6
6
  "type": "module",
7
+ "engines": {
8
+ "node": ">=20"
9
+ },
7
10
  "bin": {
8
11
  "mongodb": "./build/index.js"
9
12
  },
@@ -23,12 +26,14 @@
23
26
  "inspector": "mcp-inspector --config inspector-config.json --server mongodb"
24
27
  },
25
28
  "dependencies": {
26
- "@modelcontextprotocol/sdk": "^1.29.0",
27
- "mongodb": "^7.5.0",
28
- "zod": "^4.4.3"
29
+ "@modelcontextprotocol/express": "^2.0.0",
30
+ "@modelcontextprotocol/node": "^2.0.0",
31
+ "@modelcontextprotocol/server": "^2.0.0",
32
+ "mongodb": "^7.5.0"
29
33
  },
30
34
  "devDependencies": {
31
35
  "@biomejs/biome": "^2.5.5",
36
+ "@modelcontextprotocol/client": "^2.0.0",
32
37
  "@modelcontextprotocol/inspector": "^1.0.0",
33
38
  "@types/node": "^26.1.1",
34
39
  "tsup": "^8.5.1",