mcp-mongo-server 1.2.0 → 2.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 +18 -333
- package/build/index.js +1501 -58
- package/package.json +58 -50
- package/build/mongo.js +0 -33
- package/build/schemas/call.js +0 -507
- package/build/schemas/completion.js +0 -112
- package/build/schemas/ping.js +0 -20
- package/build/schemas/prompts.js +0 -71
- package/build/schemas/resource.js +0 -62
- package/build/schemas/templates.js +0 -33
- package/build/schemas/tools.js +0 -278
- package/build/server.js +0 -65
package/package.json
CHANGED
|
@@ -1,52 +1,60 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
2
|
+
"name": "mcp-mongo-server",
|
|
3
|
+
"version": "2.0.2",
|
|
4
|
+
"description": "A Model Context Protocol server for MongoDB connections",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mongodb": "./build/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"build"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"clean": "rm -rf build",
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"prepare": "bun run build",
|
|
17
|
+
"watch": "tsup --watch",
|
|
18
|
+
"format": "biome format --write .",
|
|
19
|
+
"format:check": "biome format .",
|
|
20
|
+
"lint": "biome lint .",
|
|
21
|
+
"lint:fix": "biome lint --write .",
|
|
22
|
+
"check": "biome check --write .",
|
|
23
|
+
"inspector": "mcp-inspector --config inspector-config.json --server mongodb"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
27
|
+
"mongodb": "^7.1.0",
|
|
28
|
+
"zod": "^3.25.76"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@biomejs/biome": "^2.3.14",
|
|
32
|
+
"@modelcontextprotocol/inspector": "^0.19.0",
|
|
33
|
+
"@types/node": "^25.2.1",
|
|
34
|
+
"tsup": "^8.5.1",
|
|
35
|
+
"typescript": "^5.9.3"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"claude",
|
|
42
|
+
"claude-desktop",
|
|
43
|
+
"mcp",
|
|
44
|
+
"mongo",
|
|
45
|
+
"mongodb",
|
|
46
|
+
"model-context-protocol",
|
|
47
|
+
"llm",
|
|
48
|
+
"mcp-server",
|
|
49
|
+
"mcp-protocol",
|
|
50
|
+
"mcp-connector",
|
|
51
|
+
"mcp-connector-mongodb"
|
|
52
|
+
],
|
|
53
|
+
"author": "Muhammed Kilic <kiliczsh>",
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "https://github.com/kiliczsh/mcp-mongo-server"
|
|
58
|
+
},
|
|
59
|
+
"homepage": "https://github.com/kiliczsh/mcp-mongo-server"
|
|
52
60
|
}
|
package/build/mongo.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { MongoClient, ReadPreference } from "mongodb";
|
|
2
|
-
/**
|
|
3
|
-
* Initialize MongoDB connection
|
|
4
|
-
* @param url MongoDB connection string
|
|
5
|
-
* @param readOnly Whether to connect in read-only mode
|
|
6
|
-
* @returns Object containing client, db, connection status, and read-only mode
|
|
7
|
-
*/
|
|
8
|
-
export async function connectToMongoDB(url, readOnly) {
|
|
9
|
-
try {
|
|
10
|
-
const options = readOnly
|
|
11
|
-
? { readPreference: ReadPreference.SECONDARY }
|
|
12
|
-
: {};
|
|
13
|
-
const client = new MongoClient(url, options);
|
|
14
|
-
await client.connect();
|
|
15
|
-
const db = client.db();
|
|
16
|
-
console.warn(`Connected to MongoDB database: ${db.databaseName}`);
|
|
17
|
-
return {
|
|
18
|
-
client,
|
|
19
|
-
db,
|
|
20
|
-
isConnected: true,
|
|
21
|
-
isReadOnlyMode: readOnly,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
console.error("Failed to connect to MongoDB:", error);
|
|
26
|
-
return {
|
|
27
|
-
client: null,
|
|
28
|
-
db: null,
|
|
29
|
-
isConnected: false,
|
|
30
|
-
isReadOnlyMode: readOnly,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
}
|
package/build/schemas/call.js
DELETED
|
@@ -1,507 +0,0 @@
|
|
|
1
|
-
import { ObjectId } from "mongodb";
|
|
2
|
-
// Define operations that require a collection
|
|
3
|
-
const COLLECTION_OPERATIONS = [
|
|
4
|
-
"query",
|
|
5
|
-
"aggregate",
|
|
6
|
-
"update",
|
|
7
|
-
"insert",
|
|
8
|
-
"createIndex",
|
|
9
|
-
"count",
|
|
10
|
-
];
|
|
11
|
-
// Define write operations that are blocked in read-only mode
|
|
12
|
-
const WRITE_OPERATIONS = ["update", "insert", "createIndex"];
|
|
13
|
-
export async function handleCallToolRequest({ request, client, db, isReadOnlyMode, }) {
|
|
14
|
-
const { name, arguments: args = {} } = request.params;
|
|
15
|
-
const operation = name;
|
|
16
|
-
// Extract ObjectId conversion mode from args (default to 'auto')
|
|
17
|
-
const objectIdMode = args.objectIdMode || "auto";
|
|
18
|
-
// Create new args object without objectIdMode property
|
|
19
|
-
const filteredArgs = {};
|
|
20
|
-
for (const [key, value] of Object.entries(args)) {
|
|
21
|
-
if (key !== "objectIdMode") {
|
|
22
|
-
filteredArgs[key] = value;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
// Replace the original args with the filtered version
|
|
26
|
-
Object.assign(args, filteredArgs);
|
|
27
|
-
// Validate operation name
|
|
28
|
-
validateOperation(operation);
|
|
29
|
-
// Check if operation is allowed in read-only mode
|
|
30
|
-
checkReadOnlyMode(operation, isReadOnlyMode);
|
|
31
|
-
// Get collection only if the operation requires it
|
|
32
|
-
let collection = null;
|
|
33
|
-
if (COLLECTION_OPERATIONS.includes(operation)) {
|
|
34
|
-
const collectionName = args.collection;
|
|
35
|
-
if (!collectionName) {
|
|
36
|
-
throw new Error(`Collection name is required for '${operation}' operation`);
|
|
37
|
-
}
|
|
38
|
-
collection = db.collection(collectionName);
|
|
39
|
-
// Validate collection
|
|
40
|
-
validateCollection(collection);
|
|
41
|
-
}
|
|
42
|
-
// Route to the appropriate handler based on operation name
|
|
43
|
-
switch (operation) {
|
|
44
|
-
case "query":
|
|
45
|
-
return handleQuery(collection, args, objectIdMode);
|
|
46
|
-
case "aggregate":
|
|
47
|
-
return handleAggregate(collection, args, objectIdMode);
|
|
48
|
-
case "update":
|
|
49
|
-
return handleUpdate(collection, args, objectIdMode);
|
|
50
|
-
case "serverInfo":
|
|
51
|
-
return handleServerInfo(db, isReadOnlyMode, args);
|
|
52
|
-
case "insert":
|
|
53
|
-
return handleInsert(collection, args, objectIdMode);
|
|
54
|
-
case "createIndex":
|
|
55
|
-
return handleCreateIndex(collection, args, objectIdMode);
|
|
56
|
-
case "count":
|
|
57
|
-
return handleCount(collection, args, objectIdMode);
|
|
58
|
-
case "listCollections":
|
|
59
|
-
return handleListCollections(db, args, objectIdMode);
|
|
60
|
-
default:
|
|
61
|
-
throw new Error(`Unknown operation: ${operation}`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
// Helper functions
|
|
65
|
-
function validateOperation(operation) {
|
|
66
|
-
const validOperations = [
|
|
67
|
-
"query",
|
|
68
|
-
"aggregate",
|
|
69
|
-
"update",
|
|
70
|
-
"serverInfo",
|
|
71
|
-
"insert",
|
|
72
|
-
"createIndex",
|
|
73
|
-
"count",
|
|
74
|
-
"listCollections",
|
|
75
|
-
];
|
|
76
|
-
if (!validOperations.includes(operation)) {
|
|
77
|
-
throw new Error(`Unknown operation: ${operation}`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function validateCollection(collection) {
|
|
81
|
-
if (!collection.collectionName) {
|
|
82
|
-
throw new Error("Collection name cannot be empty");
|
|
83
|
-
}
|
|
84
|
-
if (collection.collectionName.startsWith("system.")) {
|
|
85
|
-
throw new Error("Access to system collections is not allowed");
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
function checkReadOnlyMode(operation, isReadOnlyMode) {
|
|
89
|
-
if (isReadOnlyMode && WRITE_OPERATIONS.includes(operation)) {
|
|
90
|
-
throw new Error(`ReadonlyError: Operation '${operation}' is not allowed in read-only mode`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
function parseFilter(filter, objectIdMode = "auto") {
|
|
94
|
-
if (!filter) {
|
|
95
|
-
return {};
|
|
96
|
-
}
|
|
97
|
-
if (typeof filter === "string") {
|
|
98
|
-
try {
|
|
99
|
-
return processObjectIdInFilter(JSON.parse(filter), objectIdMode);
|
|
100
|
-
}
|
|
101
|
-
catch (e) {
|
|
102
|
-
throw new Error("Invalid filter format: must be a valid JSON object");
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if (typeof filter === "object" && filter !== null && !Array.isArray(filter)) {
|
|
106
|
-
// Process the filter to convert potential ObjectId strings
|
|
107
|
-
return processObjectIdInFilter(filter, objectIdMode);
|
|
108
|
-
}
|
|
109
|
-
throw new Error("Query filter must be a plain object or ObjectId");
|
|
110
|
-
}
|
|
111
|
-
// Helper function to check if a field should be treated as an ObjectId based on its name
|
|
112
|
-
function isObjectIdField(fieldName) {
|
|
113
|
-
// Convert field name to lowercase for case-insensitive comparison
|
|
114
|
-
const lowerFieldName = fieldName.toLowerCase();
|
|
115
|
-
// Consider fields like _id, id, xxxId, xxx_id as potential ObjectId fields
|
|
116
|
-
return (lowerFieldName === "_id" ||
|
|
117
|
-
lowerFieldName === "id" ||
|
|
118
|
-
lowerFieldName.endsWith("id") ||
|
|
119
|
-
lowerFieldName.endsWith("_id"));
|
|
120
|
-
}
|
|
121
|
-
// Helper function to process potential ObjectId strings in filters
|
|
122
|
-
function processObjectIdInFilter(filter, objectIdMode = "auto") {
|
|
123
|
-
// If objectIdMode is "none", don't convert any strings to ObjectIds
|
|
124
|
-
if (objectIdMode === "none") {
|
|
125
|
-
return filter;
|
|
126
|
-
}
|
|
127
|
-
const result = {};
|
|
128
|
-
for (const [key, value] of Object.entries(filter)) {
|
|
129
|
-
if (typeof value === "string" && isObjectIdString(value)) {
|
|
130
|
-
// Convert string to ObjectId if either:
|
|
131
|
-
// 1. objectIdMode is "force" (convert all 24-char hex strings)
|
|
132
|
-
// 2. objectIdMode is "auto" AND the field name suggests it's an ObjectId
|
|
133
|
-
if (objectIdMode === "force" ||
|
|
134
|
-
(objectIdMode === "auto" && isObjectIdField(key))) {
|
|
135
|
-
result[key] = new ObjectId(value);
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
result[key] = value;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
else if (typeof value === "object" && value !== null) {
|
|
142
|
-
if (Array.isArray(value)) {
|
|
143
|
-
// For arrays, apply the same logic to each item
|
|
144
|
-
if (objectIdMode === "force" ||
|
|
145
|
-
(objectIdMode === "auto" && isObjectIdField(key))) {
|
|
146
|
-
result[key] = value.map((item) => typeof item === "string" && isObjectIdString(item)
|
|
147
|
-
? new ObjectId(item)
|
|
148
|
-
: item);
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
result[key] = value;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
// Process nested objects
|
|
156
|
-
result[key] = processObjectIdInFilter(value, objectIdMode);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
result[key] = value;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return result;
|
|
164
|
-
}
|
|
165
|
-
// Helper function to check if a string appears to be an ObjectId
|
|
166
|
-
function isObjectIdString(str) {
|
|
167
|
-
// MongoDB ObjectId is typically a 24-character hex string
|
|
168
|
-
return /^[0-9a-fA-F]{24}$/.test(str);
|
|
169
|
-
}
|
|
170
|
-
function formatResponse(data) {
|
|
171
|
-
return {
|
|
172
|
-
content: [
|
|
173
|
-
{
|
|
174
|
-
type: "text",
|
|
175
|
-
text: JSON.stringify(data, null, 2),
|
|
176
|
-
},
|
|
177
|
-
],
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
function handleError(error, operation, collectionName) {
|
|
181
|
-
const context = collectionName ? `collection ${collectionName}` : "operation";
|
|
182
|
-
if (error instanceof Error) {
|
|
183
|
-
throw new Error(`Failed to ${operation} ${context}: ${error.message}`);
|
|
184
|
-
}
|
|
185
|
-
throw new Error(`Failed to ${operation} ${context}: Unknown error`);
|
|
186
|
-
}
|
|
187
|
-
// Operation handlers
|
|
188
|
-
async function handleQuery(collection, args, objectIdMode = "auto") {
|
|
189
|
-
if (!collection) {
|
|
190
|
-
throw new Error("Collection is required for query operation");
|
|
191
|
-
}
|
|
192
|
-
const { filter, projection, limit, explain } = args;
|
|
193
|
-
const queryFilter = parseFilter(filter, objectIdMode);
|
|
194
|
-
try {
|
|
195
|
-
if (explain) {
|
|
196
|
-
const explainResult = await collection
|
|
197
|
-
.find(queryFilter, {
|
|
198
|
-
projection,
|
|
199
|
-
limit: limit || 100,
|
|
200
|
-
})
|
|
201
|
-
.explain(explain);
|
|
202
|
-
return formatResponse(explainResult);
|
|
203
|
-
}
|
|
204
|
-
const cursor = collection.find(queryFilter, {
|
|
205
|
-
projection,
|
|
206
|
-
limit: limit || 100,
|
|
207
|
-
});
|
|
208
|
-
const results = await cursor.toArray();
|
|
209
|
-
return formatResponse(results);
|
|
210
|
-
}
|
|
211
|
-
catch (error) {
|
|
212
|
-
return handleError(error, "query", collection.collectionName);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
async function handleAggregate(collection, args, objectIdMode = "auto") {
|
|
216
|
-
if (!collection) {
|
|
217
|
-
throw new Error("Collection is required for aggregate operation");
|
|
218
|
-
}
|
|
219
|
-
const { pipeline, explain } = args;
|
|
220
|
-
if (!Array.isArray(pipeline)) {
|
|
221
|
-
throw new Error("Pipeline must be an array");
|
|
222
|
-
}
|
|
223
|
-
// Process any ObjectId strings in the pipeline
|
|
224
|
-
const processedPipeline = pipeline.map((stage) => {
|
|
225
|
-
if (typeof stage === "object" && stage !== null) {
|
|
226
|
-
return processObjectIdInFilter(stage, objectIdMode);
|
|
227
|
-
}
|
|
228
|
-
return stage;
|
|
229
|
-
});
|
|
230
|
-
try {
|
|
231
|
-
if (explain) {
|
|
232
|
-
const explainResult = await collection
|
|
233
|
-
.aggregate(processedPipeline, {
|
|
234
|
-
explain: {
|
|
235
|
-
verbosity: explain,
|
|
236
|
-
},
|
|
237
|
-
})
|
|
238
|
-
.toArray();
|
|
239
|
-
return formatResponse(explainResult);
|
|
240
|
-
}
|
|
241
|
-
const results = await collection.aggregate(processedPipeline).toArray();
|
|
242
|
-
return formatResponse(results);
|
|
243
|
-
}
|
|
244
|
-
catch (error) {
|
|
245
|
-
return handleError(error, "aggregate", collection.collectionName);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
async function handleUpdate(collection, args, objectIdMode = "auto") {
|
|
249
|
-
if (!collection) {
|
|
250
|
-
throw new Error("Collection is required for update operation");
|
|
251
|
-
}
|
|
252
|
-
const { filter, update, upsert, multi } = args;
|
|
253
|
-
const queryFilter = parseFilter(filter, objectIdMode);
|
|
254
|
-
// Process update object for potential ObjectId strings
|
|
255
|
-
let processedUpdate = update;
|
|
256
|
-
if (update && typeof update === "object" && !Array.isArray(update)) {
|
|
257
|
-
processedUpdate = processObjectIdInFilter(update, objectIdMode);
|
|
258
|
-
}
|
|
259
|
-
// Validate update operations
|
|
260
|
-
if (!processedUpdate ||
|
|
261
|
-
typeof processedUpdate !== "object" ||
|
|
262
|
-
Array.isArray(processedUpdate)) {
|
|
263
|
-
throw new Error("Update must be a valid MongoDB update document");
|
|
264
|
-
}
|
|
265
|
-
// Check if update operations use valid operators
|
|
266
|
-
const validUpdateOperators = [
|
|
267
|
-
"$set",
|
|
268
|
-
"$unset",
|
|
269
|
-
"$inc",
|
|
270
|
-
"$push",
|
|
271
|
-
"$pull",
|
|
272
|
-
"$addToSet",
|
|
273
|
-
"$pop",
|
|
274
|
-
"$rename",
|
|
275
|
-
"$mul",
|
|
276
|
-
];
|
|
277
|
-
const hasValidOperator = Object.keys(processedUpdate).some((key) => validUpdateOperators.includes(key));
|
|
278
|
-
if (!hasValidOperator) {
|
|
279
|
-
throw new Error("Update must include at least one valid update operator ($set, $unset, etc.)");
|
|
280
|
-
}
|
|
281
|
-
try {
|
|
282
|
-
const options = {
|
|
283
|
-
upsert: !!upsert,
|
|
284
|
-
multi: !!multi,
|
|
285
|
-
};
|
|
286
|
-
// Use updateOne or updateMany based on multi option
|
|
287
|
-
const updateMethod = options.multi ? "updateMany" : "updateOne";
|
|
288
|
-
const result = await collection[updateMethod](queryFilter, processedUpdate, options);
|
|
289
|
-
return formatResponse({
|
|
290
|
-
matchedCount: result.matchedCount,
|
|
291
|
-
modifiedCount: result.modifiedCount,
|
|
292
|
-
upsertedCount: result.upsertedCount,
|
|
293
|
-
upsertedId: result.upsertedId,
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
catch (error) {
|
|
297
|
-
return handleError(error, "update", collection.collectionName);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
async function handleServerInfo(db, isReadOnlyMode, args) {
|
|
301
|
-
const { includeDebugInfo } = args;
|
|
302
|
-
try {
|
|
303
|
-
// Get basic server information using buildInfo command
|
|
304
|
-
const buildInfo = await db.command({ buildInfo: 1 });
|
|
305
|
-
// Get additional server status if debug info is requested
|
|
306
|
-
let serverStatus = null;
|
|
307
|
-
if (includeDebugInfo) {
|
|
308
|
-
serverStatus = await db.command({ serverStatus: 1 });
|
|
309
|
-
}
|
|
310
|
-
// Construct the response
|
|
311
|
-
const serverInfo = {
|
|
312
|
-
version: buildInfo.version,
|
|
313
|
-
gitVersion: buildInfo.gitVersion,
|
|
314
|
-
modules: buildInfo.modules,
|
|
315
|
-
allocator: buildInfo.allocator,
|
|
316
|
-
javascriptEngine: buildInfo.javascriptEngine,
|
|
317
|
-
sysInfo: buildInfo.sysInfo,
|
|
318
|
-
storageEngines: buildInfo.storageEngines,
|
|
319
|
-
debug: buildInfo.debug,
|
|
320
|
-
maxBsonObjectSize: buildInfo.maxBsonObjectSize,
|
|
321
|
-
openssl: buildInfo.openssl,
|
|
322
|
-
buildEnvironment: buildInfo.buildEnvironment,
|
|
323
|
-
bits: buildInfo.bits,
|
|
324
|
-
ok: buildInfo.ok,
|
|
325
|
-
status: {},
|
|
326
|
-
connectionInfo: {
|
|
327
|
-
readOnlyMode: isReadOnlyMode,
|
|
328
|
-
readPreference: isReadOnlyMode ? "secondary" : "primary",
|
|
329
|
-
},
|
|
330
|
-
};
|
|
331
|
-
// Add server status information if requested
|
|
332
|
-
if (serverStatus) {
|
|
333
|
-
serverInfo.status = {
|
|
334
|
-
host: serverStatus.host,
|
|
335
|
-
version: serverStatus.version,
|
|
336
|
-
process: serverStatus.process,
|
|
337
|
-
pid: serverStatus.pid,
|
|
338
|
-
uptime: serverStatus.uptime,
|
|
339
|
-
uptimeMillis: serverStatus.uptimeMillis,
|
|
340
|
-
uptimeEstimate: serverStatus.uptimeEstimate,
|
|
341
|
-
localTime: serverStatus.localTime,
|
|
342
|
-
connections: serverStatus.connections,
|
|
343
|
-
network: serverStatus.network,
|
|
344
|
-
memory: serverStatus.mem,
|
|
345
|
-
storageEngine: serverStatus.storageEngine,
|
|
346
|
-
security: serverStatus.security,
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
return formatResponse(serverInfo);
|
|
350
|
-
}
|
|
351
|
-
catch (error) {
|
|
352
|
-
return handleError(error, "get server information");
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
async function handleInsert(collection, args, objectIdMode = "auto") {
|
|
356
|
-
if (!collection) {
|
|
357
|
-
throw new Error("Collection is required for insert operation");
|
|
358
|
-
}
|
|
359
|
-
const { documents, ordered, writeConcern, bypassDocumentValidation } = args;
|
|
360
|
-
// Validate documents array
|
|
361
|
-
if (!Array.isArray(documents)) {
|
|
362
|
-
throw new Error("Documents must be an array");
|
|
363
|
-
}
|
|
364
|
-
if (documents.length === 0) {
|
|
365
|
-
throw new Error("Documents array cannot be empty");
|
|
366
|
-
}
|
|
367
|
-
if (!documents.every((doc) => doc && typeof doc === "object" && !Array.isArray(doc))) {
|
|
368
|
-
throw new Error("Each document must be a valid MongoDB document object");
|
|
369
|
-
}
|
|
370
|
-
// Process ObjectId strings in documents
|
|
371
|
-
const processedDocuments = documents.map((doc) => processObjectIdInFilter(doc, objectIdMode));
|
|
372
|
-
try {
|
|
373
|
-
// Type the options object correctly for BulkWriteOptions
|
|
374
|
-
const options = {
|
|
375
|
-
ordered: ordered !== false, // default to true if not specified
|
|
376
|
-
writeConcern: writeConcern,
|
|
377
|
-
bypassDocumentValidation: bypassDocumentValidation,
|
|
378
|
-
};
|
|
379
|
-
// Use insertMany for consistency, it works for single documents too
|
|
380
|
-
const result = await collection.insertMany(processedDocuments, options);
|
|
381
|
-
return formatResponse({
|
|
382
|
-
acknowledged: result.acknowledged,
|
|
383
|
-
insertedCount: result.insertedCount,
|
|
384
|
-
insertedIds: result.insertedIds,
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
catch (error) {
|
|
388
|
-
// Handle bulk write errors specially to provide more detail
|
|
389
|
-
if (error instanceof Error && error.name === "BulkWriteError") {
|
|
390
|
-
const bulkError = error;
|
|
391
|
-
return formatResponse({
|
|
392
|
-
error: "Bulk write error occurred",
|
|
393
|
-
writeErrors: bulkError.writeErrors,
|
|
394
|
-
insertedCount: bulkError.result?.nInserted || 0,
|
|
395
|
-
failedCount: bulkError.result?.nFailedInserts || 0,
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
return handleError(error, "insert", collection.collectionName);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
async function handleCreateIndex(collection, args, objectIdMode = "auto") {
|
|
402
|
-
if (!collection) {
|
|
403
|
-
throw new Error("Collection is required for createIndex operation");
|
|
404
|
-
}
|
|
405
|
-
const { indexes, commitQuorum, writeConcern } = args;
|
|
406
|
-
// Validate indexes array
|
|
407
|
-
if (!Array.isArray(indexes) || indexes.length === 0) {
|
|
408
|
-
throw new Error("Indexes must be a non-empty array");
|
|
409
|
-
}
|
|
410
|
-
// Validate writeConcern
|
|
411
|
-
if (writeConcern &&
|
|
412
|
-
(typeof writeConcern !== "object" || Array.isArray(writeConcern))) {
|
|
413
|
-
throw new Error("Write concern must be a valid MongoDB write concern object");
|
|
414
|
-
}
|
|
415
|
-
// Validate commitQuorum
|
|
416
|
-
if (commitQuorum &&
|
|
417
|
-
typeof commitQuorum !== "string" &&
|
|
418
|
-
typeof commitQuorum !== "number") {
|
|
419
|
-
throw new Error("Commit quorum must be a string or number");
|
|
420
|
-
}
|
|
421
|
-
// Process ObjectId strings in indexes
|
|
422
|
-
const processedIndexes = indexes.map((index) => {
|
|
423
|
-
if (index && typeof index === "object") {
|
|
424
|
-
return processObjectIdInFilter(index, objectIdMode);
|
|
425
|
-
}
|
|
426
|
-
return index;
|
|
427
|
-
});
|
|
428
|
-
try {
|
|
429
|
-
// Properly type createIndexes options
|
|
430
|
-
const indexOptions = {
|
|
431
|
-
commitQuorum: typeof commitQuorum === "number" ? commitQuorum : undefined,
|
|
432
|
-
};
|
|
433
|
-
const result = await collection.createIndexes(processedIndexes, indexOptions);
|
|
434
|
-
// Type assertion for createIndexes result
|
|
435
|
-
return formatResponse({
|
|
436
|
-
acknowledged: result.acknowledged,
|
|
437
|
-
createdIndexes: result.createdIndexes,
|
|
438
|
-
numIndexesBefore: result
|
|
439
|
-
.numIndexesBefore,
|
|
440
|
-
numIndexesAfter: result
|
|
441
|
-
.numIndexesAfter,
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
catch (error) {
|
|
445
|
-
return handleError(error, "create indexes", collection.collectionName);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
async function handleCount(collection, args, objectIdMode = "auto") {
|
|
449
|
-
if (!collection) {
|
|
450
|
-
throw new Error("Collection is required for count operation");
|
|
451
|
-
}
|
|
452
|
-
const { query, limit, skip, hint, readConcern, maxTimeMS, collation } = args;
|
|
453
|
-
const countQuery = parseFilter(query, objectIdMode);
|
|
454
|
-
try {
|
|
455
|
-
// Build options object, removing undefined values
|
|
456
|
-
const options = {
|
|
457
|
-
limit: typeof limit === "number" ? limit : undefined,
|
|
458
|
-
skip: typeof skip === "number" ? skip : undefined,
|
|
459
|
-
hint: typeof hint === "object" && hint !== null
|
|
460
|
-
? hint
|
|
461
|
-
: undefined,
|
|
462
|
-
readConcern: typeof readConcern === "object" && readConcern !== null
|
|
463
|
-
? readConcern
|
|
464
|
-
: undefined,
|
|
465
|
-
maxTimeMS: typeof maxTimeMS === "number" ? maxTimeMS : undefined,
|
|
466
|
-
collation: typeof collation === "object" && collation !== null
|
|
467
|
-
? collation
|
|
468
|
-
: undefined,
|
|
469
|
-
};
|
|
470
|
-
// Remove undefined options
|
|
471
|
-
for (const key of Object.keys(options)) {
|
|
472
|
-
if (options[key] === undefined) {
|
|
473
|
-
delete options[key];
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
// Execute count operation
|
|
477
|
-
const count = await collection.countDocuments(countQuery, options);
|
|
478
|
-
return formatResponse({
|
|
479
|
-
count,
|
|
480
|
-
ok: 1,
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
catch (error) {
|
|
484
|
-
return handleError(error, "count documents", collection.collectionName);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
async function handleListCollections(db, args, objectIdMode = "auto") {
|
|
488
|
-
const { nameOnly, filter } = args;
|
|
489
|
-
// Process ObjectId strings in filter if present
|
|
490
|
-
let processedFilter = filter;
|
|
491
|
-
if (filter && typeof filter === "object") {
|
|
492
|
-
processedFilter = processObjectIdInFilter(filter, objectIdMode);
|
|
493
|
-
}
|
|
494
|
-
try {
|
|
495
|
-
// Get the list of collections
|
|
496
|
-
const options = processedFilter ? { filter: processedFilter } : {};
|
|
497
|
-
const collections = await db.listCollections(options).toArray();
|
|
498
|
-
// If nameOnly is true, return only the collection names
|
|
499
|
-
const result = nameOnly
|
|
500
|
-
? collections.map((collection) => collection.name)
|
|
501
|
-
: collections;
|
|
502
|
-
return formatResponse(result);
|
|
503
|
-
}
|
|
504
|
-
catch (error) {
|
|
505
|
-
return handleError(error, "list collections");
|
|
506
|
-
}
|
|
507
|
-
}
|