mcp-mongo-server 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +276 -118
- package/build/index.js +42 -1022
- package/build/mongo.js +33 -0
- package/build/schemas/call.js +507 -0
- package/build/schemas/completion.js +112 -0
- package/build/schemas/ping.js +20 -0
- package/build/schemas/prompts.js +71 -0
- package/build/schemas/resource.js +62 -0
- package/build/schemas/templates.js +33 -0
- package/build/schemas/tools.js +278 -0
- package/build/server.js +65 -0
- package/package.json +1 -1
- package/build/types.js +0 -169
package/build/index.js
CHANGED
|
@@ -1,1023 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* This is an MCP server that implements a MongoDB interface.
|
|
4
|
-
* It demonstrates core MCP concepts by allowing:
|
|
5
|
-
* - Listing collections as resources
|
|
6
|
-
* - Reading collection schemas and contents
|
|
7
|
-
* - Executing MongoDB queries via tools
|
|
8
|
-
* - Providing collection summaries via prompts
|
|
9
|
-
*/
|
|
10
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
11
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
*/
|
|
17
|
-
let client = null;
|
|
18
|
-
let db = null;
|
|
19
|
-
/**
|
|
20
|
-
* Flag indicating whether the connection is in read-only mode
|
|
21
|
-
*/
|
|
22
|
-
let isReadOnlyMode = false;
|
|
23
|
-
/**
|
|
24
|
-
* Create an MCP server with capabilities for resources (to list/read collections),
|
|
25
|
-
* tools (to query data), and prompts (to analyze collections).
|
|
26
|
-
*/
|
|
27
|
-
const server = new Server({
|
|
28
|
-
name: "mongodb",
|
|
29
|
-
version: "1.1.2",
|
|
30
|
-
}, {
|
|
31
|
-
capabilities: {
|
|
32
|
-
resources: {},
|
|
33
|
-
tools: {},
|
|
34
|
-
prompts: {},
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
/**
|
|
38
|
-
* Initialize MongoDB connection
|
|
39
|
-
*/
|
|
40
|
-
async function connectToMongoDB(url, readOnly = false) {
|
|
41
|
-
try {
|
|
42
|
-
const options = readOnly
|
|
43
|
-
? { readPreference: ReadPreference.SECONDARY }
|
|
44
|
-
: {};
|
|
45
|
-
client = new MongoClient(url, options);
|
|
46
|
-
await client.connect();
|
|
47
|
-
db = client.db();
|
|
48
|
-
isReadOnlyMode = readOnly;
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
console.error("Failed to connect to MongoDB:", error);
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Handler for ping requests to check server health
|
|
58
|
-
*/
|
|
59
|
-
server.setRequestHandler(PingRequestSchema, async () => {
|
|
60
|
-
try {
|
|
61
|
-
// Check MongoDB connection
|
|
62
|
-
if (!client) {
|
|
63
|
-
throw new Error("MongoDB connection is not available");
|
|
64
|
-
}
|
|
65
|
-
// Ping MongoDB to verify connection
|
|
66
|
-
await db.command({ ping: 1 });
|
|
67
|
-
return {
|
|
68
|
-
readOnlyMode: isReadOnlyMode,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
if (error instanceof Error) {
|
|
73
|
-
throw new Error(`MongoDB ping failed: ${error.message}`);
|
|
74
|
-
}
|
|
75
|
-
throw new Error("MongoDB ping failed: Unknown error");
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
/**
|
|
79
|
-
* Handler for listing available collections as resources.
|
|
80
|
-
* Each collection is exposed as a resource with:
|
|
81
|
-
* - A mongodb:// URI scheme
|
|
82
|
-
* - JSON MIME type
|
|
83
|
-
* - Collection name and description
|
|
84
|
-
*/
|
|
85
|
-
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
86
|
-
try {
|
|
87
|
-
const collections = await db.listCollections().toArray();
|
|
88
|
-
return {
|
|
89
|
-
resources: collections.map((collection) => ({
|
|
90
|
-
uri: `mongodb:///${collection.name}`,
|
|
91
|
-
mimeType: "application/json",
|
|
92
|
-
name: collection.name,
|
|
93
|
-
description: `MongoDB collection: ${collection.name}`,
|
|
94
|
-
})),
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
catch (error) {
|
|
98
|
-
if (error instanceof Error) {
|
|
99
|
-
throw new Error(`Failed to list collections: ${error.message}`);
|
|
100
|
-
}
|
|
101
|
-
throw new Error("Failed to list collections: Unknown error");
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
/**
|
|
105
|
-
* Handler for reading a collection's schema or contents.
|
|
106
|
-
* Takes a mongodb:// URI and returns the collection info as JSON.
|
|
107
|
-
*/
|
|
108
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
109
|
-
const url = new URL(request.params.uri);
|
|
110
|
-
const collectionName = url.pathname.replace(/^\//, "");
|
|
111
|
-
try {
|
|
112
|
-
const collection = db.collection(collectionName);
|
|
113
|
-
const sample = await collection.findOne({});
|
|
114
|
-
const indexes = await collection.indexes();
|
|
115
|
-
// Infer schema from sample document
|
|
116
|
-
const schema = sample
|
|
117
|
-
? {
|
|
118
|
-
type: "collection",
|
|
119
|
-
name: collectionName,
|
|
120
|
-
fields: Object.entries(sample).map(([key, value]) => ({
|
|
121
|
-
name: key,
|
|
122
|
-
type: typeof value,
|
|
123
|
-
})),
|
|
124
|
-
indexes: indexes.map((idx) => ({
|
|
125
|
-
name: idx.name,
|
|
126
|
-
keys: idx.key,
|
|
127
|
-
})),
|
|
128
|
-
}
|
|
129
|
-
: {
|
|
130
|
-
type: "collection",
|
|
131
|
-
name: collectionName,
|
|
132
|
-
fields: [],
|
|
133
|
-
indexes: [],
|
|
134
|
-
};
|
|
135
|
-
return {
|
|
136
|
-
contents: [
|
|
137
|
-
{
|
|
138
|
-
uri: request.params.uri,
|
|
139
|
-
mimeType: "application/json",
|
|
140
|
-
text: JSON.stringify(schema, null, 2),
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
if (error instanceof Error) {
|
|
147
|
-
throw new Error(`Failed to read collection ${collectionName}: ${error.message}`);
|
|
148
|
-
}
|
|
149
|
-
throw new Error(`Failed to read collection ${collectionName}: Unknown error`);
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
/**
|
|
153
|
-
* Handler that lists available tools.
|
|
154
|
-
* Exposes MongoDB query tools for interacting with collections.
|
|
155
|
-
*/
|
|
156
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
157
|
-
return {
|
|
158
|
-
tools: [
|
|
159
|
-
{
|
|
160
|
-
name: "query",
|
|
161
|
-
description: "Execute a MongoDB query with optional execution plan analysis",
|
|
162
|
-
inputSchema: {
|
|
163
|
-
type: "object",
|
|
164
|
-
properties: {
|
|
165
|
-
collection: {
|
|
166
|
-
type: "string",
|
|
167
|
-
description: "Name of the collection to query",
|
|
168
|
-
},
|
|
169
|
-
filter: {
|
|
170
|
-
type: "object",
|
|
171
|
-
description: "MongoDB query filter",
|
|
172
|
-
},
|
|
173
|
-
projection: {
|
|
174
|
-
type: "object",
|
|
175
|
-
description: "Fields to include/exclude",
|
|
176
|
-
},
|
|
177
|
-
limit: {
|
|
178
|
-
type: "number",
|
|
179
|
-
description: "Maximum number of documents to return",
|
|
180
|
-
},
|
|
181
|
-
explain: {
|
|
182
|
-
type: "string",
|
|
183
|
-
description: "Optional: Get query execution information (queryPlanner, executionStats, or allPlansExecution)",
|
|
184
|
-
enum: ["queryPlanner", "executionStats", "allPlansExecution"],
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
required: ["collection"],
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
name: "aggregate",
|
|
192
|
-
description: "Execute a MongoDB aggregation pipeline with optional execution plan analysis",
|
|
193
|
-
inputSchema: {
|
|
194
|
-
type: "object",
|
|
195
|
-
properties: {
|
|
196
|
-
collection: {
|
|
197
|
-
type: "string",
|
|
198
|
-
description: "Name of the collection to aggregate",
|
|
199
|
-
},
|
|
200
|
-
pipeline: {
|
|
201
|
-
type: "array",
|
|
202
|
-
description: "Aggregation pipeline stages",
|
|
203
|
-
},
|
|
204
|
-
explain: {
|
|
205
|
-
type: "string",
|
|
206
|
-
description: "Optional: Get aggregation execution information (queryPlanner, executionStats, or allPlansExecution)",
|
|
207
|
-
enum: ["queryPlanner", "executionStats", "allPlansExecution"],
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
required: ["collection", "pipeline"],
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
name: "update",
|
|
215
|
-
description: "Update documents in a MongoDB collection",
|
|
216
|
-
inputSchema: {
|
|
217
|
-
type: "object",
|
|
218
|
-
properties: {
|
|
219
|
-
collection: {
|
|
220
|
-
type: "string",
|
|
221
|
-
description: "Name of the collection to update",
|
|
222
|
-
},
|
|
223
|
-
filter: {
|
|
224
|
-
type: "object",
|
|
225
|
-
description: "Filter to select documents to update",
|
|
226
|
-
},
|
|
227
|
-
update: {
|
|
228
|
-
type: "object",
|
|
229
|
-
description: "Update operations to apply ($set, $unset, $inc, etc.)",
|
|
230
|
-
},
|
|
231
|
-
upsert: {
|
|
232
|
-
type: "boolean",
|
|
233
|
-
description: "Create a new document if no documents match the filter",
|
|
234
|
-
},
|
|
235
|
-
multi: {
|
|
236
|
-
type: "boolean",
|
|
237
|
-
description: "Update multiple documents that match the filter",
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
required: ["collection", "filter", "update"],
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
name: "serverInfo",
|
|
245
|
-
description: "Get MongoDB server information including version, storage engine, and other details",
|
|
246
|
-
inputSchema: {
|
|
247
|
-
type: "object",
|
|
248
|
-
properties: {
|
|
249
|
-
includeDebugInfo: {
|
|
250
|
-
type: "boolean",
|
|
251
|
-
description: "Include additional debug information about the server",
|
|
252
|
-
},
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
name: "insert",
|
|
258
|
-
description: "Insert one or more documents into a MongoDB collection",
|
|
259
|
-
inputSchema: {
|
|
260
|
-
type: "object",
|
|
261
|
-
properties: {
|
|
262
|
-
collection: {
|
|
263
|
-
type: "string",
|
|
264
|
-
description: "Name of the collection to insert into",
|
|
265
|
-
},
|
|
266
|
-
documents: {
|
|
267
|
-
type: "array",
|
|
268
|
-
description: "Array of documents to insert",
|
|
269
|
-
items: {
|
|
270
|
-
type: "object",
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
ordered: {
|
|
274
|
-
type: "boolean",
|
|
275
|
-
description: "Optional: If true, perform an ordered insert of the documents. If false, perform an unordered insert",
|
|
276
|
-
},
|
|
277
|
-
writeConcern: {
|
|
278
|
-
type: "object",
|
|
279
|
-
description: "Optional: Write concern for the insert operation",
|
|
280
|
-
},
|
|
281
|
-
bypassDocumentValidation: {
|
|
282
|
-
type: "boolean",
|
|
283
|
-
description: "Optional: Allow insert to bypass schema validation",
|
|
284
|
-
},
|
|
285
|
-
},
|
|
286
|
-
required: ["collection", "documents"],
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
name: "createIndex",
|
|
291
|
-
description: "Create one or more indexes on a MongoDB collection",
|
|
292
|
-
inputSchema: {
|
|
293
|
-
type: "object",
|
|
294
|
-
properties: {
|
|
295
|
-
collection: {
|
|
296
|
-
type: "string",
|
|
297
|
-
description: "Name of the collection to create indexes on",
|
|
298
|
-
},
|
|
299
|
-
indexes: {
|
|
300
|
-
type: "array",
|
|
301
|
-
description: "Array of index specifications",
|
|
302
|
-
items: {
|
|
303
|
-
type: "object",
|
|
304
|
-
properties: {
|
|
305
|
-
key: {
|
|
306
|
-
type: "object",
|
|
307
|
-
description: "Index key pattern, e.g. { field: 1 } for ascending, { field: -1 } for descending",
|
|
308
|
-
},
|
|
309
|
-
name: {
|
|
310
|
-
type: "string",
|
|
311
|
-
description: "Optional: Name of the index",
|
|
312
|
-
},
|
|
313
|
-
unique: {
|
|
314
|
-
type: "boolean",
|
|
315
|
-
description: "Optional: If true, creates a unique index",
|
|
316
|
-
},
|
|
317
|
-
sparse: {
|
|
318
|
-
type: "boolean",
|
|
319
|
-
description: "Optional: If true, creates a sparse index",
|
|
320
|
-
},
|
|
321
|
-
background: {
|
|
322
|
-
type: "boolean",
|
|
323
|
-
description: "Optional: If true, creates the index in the background",
|
|
324
|
-
},
|
|
325
|
-
expireAfterSeconds: {
|
|
326
|
-
type: "number",
|
|
327
|
-
description: "Optional: Specifies the TTL for documents (time to live)",
|
|
328
|
-
},
|
|
329
|
-
partialFilterExpression: {
|
|
330
|
-
type: "object",
|
|
331
|
-
description: "Optional: Filter expression for partial indexes",
|
|
332
|
-
},
|
|
333
|
-
},
|
|
334
|
-
required: ["key"],
|
|
335
|
-
},
|
|
336
|
-
},
|
|
337
|
-
writeConcern: {
|
|
338
|
-
type: "object",
|
|
339
|
-
description: "Optional: Write concern for the index creation",
|
|
340
|
-
},
|
|
341
|
-
commitQuorum: {
|
|
342
|
-
type: ["string", "number"],
|
|
343
|
-
description: "Optional: Number of voting members required to create index",
|
|
344
|
-
},
|
|
345
|
-
},
|
|
346
|
-
required: ["collection", "indexes"],
|
|
347
|
-
},
|
|
348
|
-
},
|
|
349
|
-
{
|
|
350
|
-
name: "count",
|
|
351
|
-
description: "Count the number of documents in a collection that match a query",
|
|
352
|
-
inputSchema: {
|
|
353
|
-
type: "object",
|
|
354
|
-
properties: {
|
|
355
|
-
collection: {
|
|
356
|
-
type: "string",
|
|
357
|
-
description: "Name of the collection to count documents in",
|
|
358
|
-
},
|
|
359
|
-
query: {
|
|
360
|
-
type: "object",
|
|
361
|
-
description: "Optional: Query filter to select documents to count",
|
|
362
|
-
},
|
|
363
|
-
limit: {
|
|
364
|
-
type: "integer",
|
|
365
|
-
description: "Optional: Maximum number of documents to count",
|
|
366
|
-
},
|
|
367
|
-
skip: {
|
|
368
|
-
type: "integer",
|
|
369
|
-
description: "Optional: Number of documents to skip before counting",
|
|
370
|
-
},
|
|
371
|
-
hint: {
|
|
372
|
-
type: "object",
|
|
373
|
-
description: "Optional: Index hint to force query plan",
|
|
374
|
-
},
|
|
375
|
-
readConcern: {
|
|
376
|
-
type: "object",
|
|
377
|
-
description: "Optional: Read concern for the count operation",
|
|
378
|
-
},
|
|
379
|
-
maxTimeMS: {
|
|
380
|
-
type: "integer",
|
|
381
|
-
description: "Optional: Maximum time to allow the count to run",
|
|
382
|
-
},
|
|
383
|
-
collation: {
|
|
384
|
-
type: "object",
|
|
385
|
-
description: "Optional: Collation rules for string comparison",
|
|
386
|
-
},
|
|
387
|
-
},
|
|
388
|
-
required: ["collection"],
|
|
389
|
-
},
|
|
390
|
-
},
|
|
391
|
-
{
|
|
392
|
-
name: "listCollections",
|
|
393
|
-
description: "List all collections in the MongoDB database",
|
|
394
|
-
inputSchema: {
|
|
395
|
-
type: "object",
|
|
396
|
-
properties: {
|
|
397
|
-
nameOnly: {
|
|
398
|
-
type: "boolean",
|
|
399
|
-
description: "Optional: If true, returns only the collection names instead of full collection info",
|
|
400
|
-
},
|
|
401
|
-
filter: {
|
|
402
|
-
type: "object",
|
|
403
|
-
description: "Optional: Filter to apply to the collections",
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
|
-
},
|
|
407
|
-
},
|
|
408
|
-
],
|
|
409
|
-
};
|
|
410
|
-
});
|
|
411
|
-
/**
|
|
412
|
-
* Handler for MongoDB tools.
|
|
413
|
-
* Executes queries and returns results.
|
|
414
|
-
*/
|
|
415
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
416
|
-
const collection = db.collection(request.params.arguments?.collection);
|
|
417
|
-
// Define write operations that should be blocked in read-only mode
|
|
418
|
-
const writeOperations = ["update", "insert", "createIndex"];
|
|
419
|
-
// Check if the operation is a write operation and we're in read-only mode
|
|
420
|
-
if (isReadOnlyMode && writeOperations.includes(request.params.name)) {
|
|
421
|
-
throw new Error(`ReadonlyError: Operation '${request.params.name}' is not allowed in read-only mode`);
|
|
422
|
-
}
|
|
423
|
-
switch (request.params.name) {
|
|
424
|
-
case "query": {
|
|
425
|
-
const { filter, projection, limit, explain } = request.params.arguments || {};
|
|
426
|
-
// Validate collection name to prevent access to system collections
|
|
427
|
-
if (collection.collectionName.startsWith("system.")) {
|
|
428
|
-
throw new Error("Access to system collections is not allowed");
|
|
429
|
-
}
|
|
430
|
-
// Validate and parse filter
|
|
431
|
-
let queryFilter = {};
|
|
432
|
-
if (filter) {
|
|
433
|
-
if (typeof filter === "string") {
|
|
434
|
-
try {
|
|
435
|
-
queryFilter = JSON.parse(filter);
|
|
436
|
-
}
|
|
437
|
-
catch (e) {
|
|
438
|
-
throw new Error("Invalid filter format: must be a valid JSON object");
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
else if (typeof filter === "object" &&
|
|
442
|
-
filter !== null &&
|
|
443
|
-
!Array.isArray(filter)) {
|
|
444
|
-
queryFilter = filter;
|
|
445
|
-
}
|
|
446
|
-
else {
|
|
447
|
-
throw new Error("Query filter must be a plain object or ObjectId");
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
// Execute the find operation with error handling
|
|
451
|
-
try {
|
|
452
|
-
if (explain) {
|
|
453
|
-
// Use explain for query analysis
|
|
454
|
-
const explainResult = await collection
|
|
455
|
-
.find(queryFilter, {
|
|
456
|
-
projection,
|
|
457
|
-
limit: limit || 100,
|
|
458
|
-
})
|
|
459
|
-
.explain(explain);
|
|
460
|
-
return {
|
|
461
|
-
content: [
|
|
462
|
-
{
|
|
463
|
-
type: "text",
|
|
464
|
-
text: JSON.stringify(explainResult, null, 2),
|
|
465
|
-
},
|
|
466
|
-
],
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
else {
|
|
470
|
-
// Regular query execution
|
|
471
|
-
const cursor = collection.find(queryFilter, {
|
|
472
|
-
projection,
|
|
473
|
-
limit: limit || 100,
|
|
474
|
-
});
|
|
475
|
-
const results = await cursor.toArray();
|
|
476
|
-
return {
|
|
477
|
-
content: [
|
|
478
|
-
{
|
|
479
|
-
type: "text",
|
|
480
|
-
text: JSON.stringify(results, null, 2),
|
|
481
|
-
},
|
|
482
|
-
],
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
catch (error) {
|
|
487
|
-
if (error instanceof Error) {
|
|
488
|
-
throw new Error(`Failed to query collection ${collection.collectionName}: ${error.message}`);
|
|
489
|
-
}
|
|
490
|
-
throw new Error(`Failed to query collection ${collection.collectionName}: Unknown error`);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
case "aggregate": {
|
|
494
|
-
const { pipeline, explain } = request.params.arguments || {};
|
|
495
|
-
if (!Array.isArray(pipeline)) {
|
|
496
|
-
throw new Error("Pipeline must be an array");
|
|
497
|
-
}
|
|
498
|
-
// Validate collection name to prevent access to system collections
|
|
499
|
-
if (collection.collectionName.startsWith("system.")) {
|
|
500
|
-
throw new Error("Access to system collections is not allowed");
|
|
501
|
-
}
|
|
502
|
-
// Execute the aggregation operation with error handling
|
|
503
|
-
try {
|
|
504
|
-
if (explain) {
|
|
505
|
-
// Use explain for aggregation analysis
|
|
506
|
-
const explainResult = await collection
|
|
507
|
-
.aggregate(pipeline, {
|
|
508
|
-
explain: {
|
|
509
|
-
verbosity: explain,
|
|
510
|
-
},
|
|
511
|
-
})
|
|
512
|
-
.toArray();
|
|
513
|
-
return {
|
|
514
|
-
content: [
|
|
515
|
-
{
|
|
516
|
-
type: "text",
|
|
517
|
-
text: JSON.stringify(explainResult, null, 2),
|
|
518
|
-
},
|
|
519
|
-
],
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
else {
|
|
523
|
-
// Regular aggregation execution
|
|
524
|
-
const results = await collection.aggregate(pipeline).toArray();
|
|
525
|
-
return {
|
|
526
|
-
content: [
|
|
527
|
-
{
|
|
528
|
-
type: "text",
|
|
529
|
-
text: JSON.stringify(results, null, 2),
|
|
530
|
-
},
|
|
531
|
-
],
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
catch (error) {
|
|
536
|
-
if (error instanceof Error) {
|
|
537
|
-
throw new Error(`Failed to aggregate collection ${collection.collectionName}: ${error.message}`);
|
|
538
|
-
}
|
|
539
|
-
throw new Error(`Failed to aggregate collection ${collection.collectionName}: Unknown error`);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
case "update": {
|
|
543
|
-
const { filter, update, upsert, multi } = request.params.arguments || {};
|
|
544
|
-
// Validate collection name to prevent access to system collections
|
|
545
|
-
if (collection.collectionName.startsWith("system.")) {
|
|
546
|
-
throw new Error("Access to system collections is not allowed");
|
|
547
|
-
}
|
|
548
|
-
// Validate and parse filter
|
|
549
|
-
let queryFilter = {};
|
|
550
|
-
if (filter) {
|
|
551
|
-
if (typeof filter === "string") {
|
|
552
|
-
try {
|
|
553
|
-
queryFilter = JSON.parse(filter);
|
|
554
|
-
}
|
|
555
|
-
catch (e) {
|
|
556
|
-
throw new Error("Invalid filter format: must be a valid JSON object");
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
else if (typeof filter === "object" &&
|
|
560
|
-
filter !== null &&
|
|
561
|
-
!Array.isArray(filter)) {
|
|
562
|
-
queryFilter = filter;
|
|
563
|
-
}
|
|
564
|
-
else {
|
|
565
|
-
throw new Error("Query filter must be a plain object or ObjectId");
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
// Validate update operations
|
|
569
|
-
if (!update || typeof update !== "object" || Array.isArray(update)) {
|
|
570
|
-
throw new Error("Update must be a valid MongoDB update document");
|
|
571
|
-
}
|
|
572
|
-
// Check if update operations use valid operators
|
|
573
|
-
const validUpdateOperators = [
|
|
574
|
-
"$set",
|
|
575
|
-
"$unset",
|
|
576
|
-
"$inc",
|
|
577
|
-
"$push",
|
|
578
|
-
"$pull",
|
|
579
|
-
"$addToSet",
|
|
580
|
-
"$pop",
|
|
581
|
-
"$rename",
|
|
582
|
-
"$mul",
|
|
583
|
-
];
|
|
584
|
-
const hasValidOperator = Object.keys(update).some((key) => validUpdateOperators.includes(key));
|
|
585
|
-
if (!hasValidOperator) {
|
|
586
|
-
throw new Error("Update must include at least one valid update operator ($set, $unset, etc.)");
|
|
587
|
-
}
|
|
588
|
-
try {
|
|
589
|
-
const options = {
|
|
590
|
-
upsert: !!upsert,
|
|
591
|
-
multi: !!multi,
|
|
592
|
-
};
|
|
593
|
-
// Use updateOne or updateMany based on multi option
|
|
594
|
-
const updateMethod = options.multi ? "updateMany" : "updateOne";
|
|
595
|
-
const result = await collection[updateMethod](queryFilter, update, options);
|
|
596
|
-
return {
|
|
597
|
-
content: [
|
|
598
|
-
{
|
|
599
|
-
type: "text",
|
|
600
|
-
text: JSON.stringify({
|
|
601
|
-
matchedCount: result.matchedCount,
|
|
602
|
-
modifiedCount: result.modifiedCount,
|
|
603
|
-
upsertedCount: result.upsertedCount,
|
|
604
|
-
upsertedId: result.upsertedId,
|
|
605
|
-
}, null, 2),
|
|
606
|
-
},
|
|
607
|
-
],
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
catch (error) {
|
|
611
|
-
if (error instanceof Error) {
|
|
612
|
-
throw new Error(`Failed to update collection ${collection.collectionName}: ${error.message}`);
|
|
613
|
-
}
|
|
614
|
-
throw new Error(`Failed to update collection ${collection.collectionName}: Unknown error`);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
case "serverInfo": {
|
|
618
|
-
const { includeDebugInfo } = request.params.arguments || {};
|
|
619
|
-
try {
|
|
620
|
-
// Get basic server information using buildInfo command
|
|
621
|
-
const buildInfo = await db.command({ buildInfo: 1 });
|
|
622
|
-
// Get additional server status if debug info is requested
|
|
623
|
-
let serverStatus = null;
|
|
624
|
-
if (includeDebugInfo) {
|
|
625
|
-
serverStatus = await db.command({ serverStatus: 1 });
|
|
626
|
-
}
|
|
627
|
-
// Construct the response
|
|
628
|
-
const serverInfo = {
|
|
629
|
-
version: buildInfo.version,
|
|
630
|
-
gitVersion: buildInfo.gitVersion,
|
|
631
|
-
modules: buildInfo.modules,
|
|
632
|
-
allocator: buildInfo.allocator,
|
|
633
|
-
javascriptEngine: buildInfo.javascriptEngine,
|
|
634
|
-
sysInfo: buildInfo.sysInfo,
|
|
635
|
-
storageEngines: buildInfo.storageEngines,
|
|
636
|
-
debug: buildInfo.debug,
|
|
637
|
-
maxBsonObjectSize: buildInfo.maxBsonObjectSize,
|
|
638
|
-
openssl: buildInfo.openssl,
|
|
639
|
-
buildEnvironment: buildInfo.buildEnvironment,
|
|
640
|
-
bits: buildInfo.bits,
|
|
641
|
-
ok: buildInfo.ok,
|
|
642
|
-
status: {},
|
|
643
|
-
connectionInfo: {
|
|
644
|
-
readOnlyMode: isReadOnlyMode,
|
|
645
|
-
readPreference: isReadOnlyMode ? "secondary" : "primary",
|
|
646
|
-
},
|
|
647
|
-
};
|
|
648
|
-
// Add server status information if requested
|
|
649
|
-
if (serverStatus) {
|
|
650
|
-
serverInfo.status = {
|
|
651
|
-
host: serverStatus.host,
|
|
652
|
-
version: serverStatus.version,
|
|
653
|
-
process: serverStatus.process,
|
|
654
|
-
pid: serverStatus.pid,
|
|
655
|
-
uptime: serverStatus.uptime,
|
|
656
|
-
uptimeMillis: serverStatus.uptimeMillis,
|
|
657
|
-
uptimeEstimate: serverStatus.uptimeEstimate,
|
|
658
|
-
localTime: serverStatus.localTime,
|
|
659
|
-
connections: serverStatus.connections,
|
|
660
|
-
network: serverStatus.network,
|
|
661
|
-
memory: serverStatus.mem,
|
|
662
|
-
storageEngine: serverStatus.storageEngine,
|
|
663
|
-
security: serverStatus.security,
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
return {
|
|
667
|
-
content: [
|
|
668
|
-
{
|
|
669
|
-
type: "text",
|
|
670
|
-
text: JSON.stringify(serverInfo, null, 2),
|
|
671
|
-
},
|
|
672
|
-
],
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
catch (error) {
|
|
676
|
-
if (error instanceof Error) {
|
|
677
|
-
throw new Error(`Failed to get server information: ${error.message}`);
|
|
678
|
-
}
|
|
679
|
-
throw new Error("Failed to get server information: Unknown error");
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
case "insert": {
|
|
683
|
-
const { documents, ordered, writeConcern, bypassDocumentValidation } = request.params.arguments || {};
|
|
684
|
-
// Validate collection name to prevent access to system collections
|
|
685
|
-
if (collection.collectionName.startsWith("system.")) {
|
|
686
|
-
throw new Error("Access to system collections is not allowed");
|
|
687
|
-
}
|
|
688
|
-
// Validate documents array
|
|
689
|
-
if (!Array.isArray(documents)) {
|
|
690
|
-
throw new Error("Documents must be an array");
|
|
691
|
-
}
|
|
692
|
-
if (documents.length === 0) {
|
|
693
|
-
throw new Error("Documents array cannot be empty");
|
|
694
|
-
}
|
|
695
|
-
if (!documents.every((doc) => doc && typeof doc === "object" && !Array.isArray(doc))) {
|
|
696
|
-
throw new Error("Each document must be a valid MongoDB document object");
|
|
697
|
-
}
|
|
698
|
-
try {
|
|
699
|
-
// Prepare insert options
|
|
700
|
-
const options = {
|
|
701
|
-
ordered: ordered !== false, // default to true if not specified
|
|
702
|
-
writeConcern,
|
|
703
|
-
bypassDocumentValidation,
|
|
704
|
-
};
|
|
705
|
-
// Use insertMany for consistency, it works for single documents too
|
|
706
|
-
const result = await collection.insertMany(documents, options);
|
|
707
|
-
return {
|
|
708
|
-
content: [
|
|
709
|
-
{
|
|
710
|
-
type: "text",
|
|
711
|
-
text: JSON.stringify({
|
|
712
|
-
acknowledged: result.acknowledged,
|
|
713
|
-
insertedCount: result.insertedCount,
|
|
714
|
-
insertedIds: result.insertedIds,
|
|
715
|
-
}, null, 2),
|
|
716
|
-
},
|
|
717
|
-
],
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
catch (error) {
|
|
721
|
-
if (error instanceof Error) {
|
|
722
|
-
// Handle bulk write errors specially to provide more detail
|
|
723
|
-
if (error.name === "BulkWriteError") {
|
|
724
|
-
const bulkError = error;
|
|
725
|
-
return {
|
|
726
|
-
content: [
|
|
727
|
-
{
|
|
728
|
-
type: "text",
|
|
729
|
-
text: JSON.stringify({
|
|
730
|
-
error: "Bulk write error occurred",
|
|
731
|
-
writeErrors: bulkError.writeErrors,
|
|
732
|
-
insertedCount: bulkError.result?.nInserted || 0,
|
|
733
|
-
failedCount: bulkError.result?.nFailedInserts || 0,
|
|
734
|
-
}, null, 2),
|
|
735
|
-
},
|
|
736
|
-
],
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
throw new Error(`Failed to insert documents into collection ${collection.collectionName}: ${error.message}`);
|
|
740
|
-
}
|
|
741
|
-
throw new Error(`Failed to insert documents into collection ${collection.collectionName}: Unknown error`);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
case "createIndex": {
|
|
745
|
-
const { indexes, writeConcern, commitQuorum } = request.params.arguments || {};
|
|
746
|
-
// Validate collection name to prevent access to system collections
|
|
747
|
-
if (collection.collectionName.startsWith("system.")) {
|
|
748
|
-
throw new Error("Access to system collections is not allowed");
|
|
749
|
-
}
|
|
750
|
-
// Validate indexes array
|
|
751
|
-
if (!Array.isArray(indexes) || indexes.length === 0) {
|
|
752
|
-
throw new Error("Indexes must be a non-empty array");
|
|
753
|
-
}
|
|
754
|
-
// Validate writeConcern
|
|
755
|
-
if (writeConcern &&
|
|
756
|
-
(typeof writeConcern !== "object" || Array.isArray(writeConcern))) {
|
|
757
|
-
throw new Error("Write concern must be a valid MongoDB write concern object");
|
|
758
|
-
}
|
|
759
|
-
// Validate commitQuorum
|
|
760
|
-
if (commitQuorum &&
|
|
761
|
-
typeof commitQuorum !== "string" &&
|
|
762
|
-
typeof commitQuorum !== "number") {
|
|
763
|
-
throw new Error("Commit quorum must be a string or number");
|
|
764
|
-
}
|
|
765
|
-
try {
|
|
766
|
-
const result = await collection.createIndexes(indexes, {
|
|
767
|
-
writeConcern,
|
|
768
|
-
commitQuorum: typeof commitQuorum === "number" ? commitQuorum : undefined,
|
|
769
|
-
});
|
|
770
|
-
return {
|
|
771
|
-
content: [
|
|
772
|
-
{
|
|
773
|
-
type: "text",
|
|
774
|
-
text: JSON.stringify({
|
|
775
|
-
acknowledged: result.acknowledged,
|
|
776
|
-
createdIndexes: result.createdIndexes,
|
|
777
|
-
numIndexesBefore: result.numIndexesBefore,
|
|
778
|
-
numIndexesAfter: result.numIndexesAfter,
|
|
779
|
-
}, null, 2),
|
|
780
|
-
},
|
|
781
|
-
],
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
catch (error) {
|
|
785
|
-
if (error instanceof Error) {
|
|
786
|
-
throw new Error(`Failed to create indexes on collection ${collection.collectionName}: ${error.message}`);
|
|
787
|
-
}
|
|
788
|
-
throw new Error(`Failed to create indexes on collection ${collection.collectionName}: Unknown error`);
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
case "count": {
|
|
792
|
-
const args = request.params.arguments || {};
|
|
793
|
-
const { query } = args;
|
|
794
|
-
// Validate collection name to prevent access to system collections
|
|
795
|
-
if (collection.collectionName.startsWith("system.")) {
|
|
796
|
-
throw new Error("Access to system collections is not allowed");
|
|
797
|
-
}
|
|
798
|
-
// Validate and parse query
|
|
799
|
-
let countQuery = {};
|
|
800
|
-
if (query) {
|
|
801
|
-
if (typeof query === "string") {
|
|
802
|
-
try {
|
|
803
|
-
countQuery = JSON.parse(query);
|
|
804
|
-
}
|
|
805
|
-
catch (e) {
|
|
806
|
-
throw new Error("Invalid query format: must be a valid JSON object");
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
else if (typeof query === "object" &&
|
|
810
|
-
query !== null &&
|
|
811
|
-
!Array.isArray(query)) {
|
|
812
|
-
countQuery = query;
|
|
813
|
-
}
|
|
814
|
-
else {
|
|
815
|
-
throw new Error("Query must be a plain object");
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
try {
|
|
819
|
-
const options = {
|
|
820
|
-
limit: typeof args.limit === "number" ? args.limit : undefined,
|
|
821
|
-
skip: typeof args.skip === "number" ? args.skip : undefined,
|
|
822
|
-
hint: typeof args.hint === "object" && args.hint !== null
|
|
823
|
-
? args.hint
|
|
824
|
-
: undefined,
|
|
825
|
-
readConcern: typeof args.readConcern === "object" && args.readConcern !== null
|
|
826
|
-
? args.readConcern
|
|
827
|
-
: undefined,
|
|
828
|
-
maxTimeMS: typeof args.maxTimeMS === "number" ? args.maxTimeMS : undefined,
|
|
829
|
-
collation: typeof args.collation === "object" && args.collation !== null
|
|
830
|
-
? args.collation
|
|
831
|
-
: undefined,
|
|
832
|
-
};
|
|
833
|
-
// Remove undefined options
|
|
834
|
-
Object.keys(options).forEach((key) => options[key] === undefined && delete options[key]);
|
|
835
|
-
// Execute count operation
|
|
836
|
-
const count = await collection.countDocuments(countQuery, options);
|
|
837
|
-
return {
|
|
838
|
-
content: [
|
|
839
|
-
{
|
|
840
|
-
type: "text",
|
|
841
|
-
text: JSON.stringify({
|
|
842
|
-
count: count,
|
|
843
|
-
ok: 1,
|
|
844
|
-
}, null, 2),
|
|
845
|
-
},
|
|
846
|
-
],
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
catch (error) {
|
|
850
|
-
if (error instanceof Error) {
|
|
851
|
-
throw new Error(`Failed to count documents in collection ${collection.collectionName}: ${error.message}`);
|
|
852
|
-
}
|
|
853
|
-
throw new Error(`Failed to count documents in collection ${collection.collectionName}: Unknown error`);
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
case "listCollections": {
|
|
857
|
-
const { nameOnly, filter } = request.params.arguments || {};
|
|
858
|
-
try {
|
|
859
|
-
// Get the list of collections
|
|
860
|
-
const options = filter ? { filter } : {};
|
|
861
|
-
const collections = await db.listCollections(options).toArray();
|
|
862
|
-
// If nameOnly is true, return only the collection names
|
|
863
|
-
const result = nameOnly
|
|
864
|
-
? collections.map((collection) => collection.name)
|
|
865
|
-
: collections;
|
|
866
|
-
return {
|
|
867
|
-
content: [
|
|
868
|
-
{
|
|
869
|
-
type: "text",
|
|
870
|
-
text: JSON.stringify(result, null, 2),
|
|
871
|
-
},
|
|
872
|
-
],
|
|
873
|
-
};
|
|
874
|
-
}
|
|
875
|
-
catch (error) {
|
|
876
|
-
if (error instanceof Error) {
|
|
877
|
-
throw new Error(`Failed to list collections: ${error.message}`);
|
|
878
|
-
}
|
|
879
|
-
throw new Error("Failed to list collections: Unknown error");
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
default:
|
|
883
|
-
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
884
|
-
}
|
|
885
|
-
});
|
|
886
|
-
/**
|
|
887
|
-
* Handler that lists available prompts.
|
|
888
|
-
* Exposes prompts for analyzing collections.
|
|
889
|
-
*/
|
|
890
|
-
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
891
|
-
return {
|
|
892
|
-
prompts: [
|
|
893
|
-
{
|
|
894
|
-
name: "analyze_collection",
|
|
895
|
-
description: "Analyze a MongoDB collection structure and contents",
|
|
896
|
-
arguments: [
|
|
897
|
-
{
|
|
898
|
-
name: "collection",
|
|
899
|
-
description: "Name of the collection to analyze",
|
|
900
|
-
required: true,
|
|
901
|
-
},
|
|
902
|
-
],
|
|
903
|
-
},
|
|
904
|
-
],
|
|
905
|
-
};
|
|
906
|
-
});
|
|
907
|
-
/**
|
|
908
|
-
* Handler for collection analysis prompt.
|
|
909
|
-
* Returns a prompt that requests analysis of a collection's structure and data.
|
|
910
|
-
*/
|
|
911
|
-
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
912
|
-
if (request.params.name !== "analyze_collection") {
|
|
913
|
-
throw new Error("Unknown prompt");
|
|
914
|
-
}
|
|
915
|
-
const collectionName = request.params.arguments?.collection;
|
|
916
|
-
if (!collectionName) {
|
|
917
|
-
throw new Error("Collection name is required");
|
|
918
|
-
}
|
|
919
|
-
try {
|
|
920
|
-
const collection = db.collection(collectionName);
|
|
921
|
-
// Validate collection name to prevent access to system collections
|
|
922
|
-
if (collection.collectionName.startsWith("system.")) {
|
|
923
|
-
throw new Error("Access to system collections is not allowed");
|
|
924
|
-
}
|
|
925
|
-
const schema = await collection.findOne({});
|
|
926
|
-
// Get basic collection stats - just count in API v1
|
|
927
|
-
const stats = await collection
|
|
928
|
-
.aggregate([
|
|
929
|
-
{
|
|
930
|
-
$collStats: {
|
|
931
|
-
count: {},
|
|
932
|
-
},
|
|
933
|
-
},
|
|
934
|
-
])
|
|
935
|
-
.toArray();
|
|
936
|
-
// Also get a sample of documents to show data distribution
|
|
937
|
-
const sampleDocs = await collection.find({}).limit(5).toArray();
|
|
938
|
-
return {
|
|
939
|
-
messages: [
|
|
940
|
-
{
|
|
941
|
-
role: "user",
|
|
942
|
-
content: {
|
|
943
|
-
type: "text",
|
|
944
|
-
text: `Please analyze the following MongoDB collection:
|
|
945
|
-
Collection: ${collectionName}
|
|
946
|
-
|
|
947
|
-
Schema:
|
|
948
|
-
${JSON.stringify(schema, null, 2)}
|
|
949
|
-
|
|
950
|
-
Stats:
|
|
951
|
-
Document count: ${stats[0]?.count || "unknown"}
|
|
952
|
-
|
|
953
|
-
Sample documents:
|
|
954
|
-
${JSON.stringify(sampleDocs, null, 2)}`,
|
|
955
|
-
},
|
|
956
|
-
},
|
|
957
|
-
{
|
|
958
|
-
role: "user",
|
|
959
|
-
content: {
|
|
960
|
-
type: "text",
|
|
961
|
-
text: "Provide insights about the collection's structure, data types, and basic statistics.",
|
|
962
|
-
},
|
|
963
|
-
},
|
|
964
|
-
],
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
catch (error) {
|
|
968
|
-
if (error instanceof Error) {
|
|
969
|
-
throw new Error(`Failed to analyze collection ${collectionName}: ${error.message}`);
|
|
970
|
-
}
|
|
971
|
-
else {
|
|
972
|
-
throw new Error(`Failed to analyze collection ${collectionName}: Unknown error`);
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
});
|
|
976
|
-
/**
|
|
977
|
-
* Handler for listing templates.
|
|
978
|
-
* Exposes templates for constructing MongoDB queries.
|
|
979
|
-
*/
|
|
980
|
-
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
981
|
-
return {
|
|
982
|
-
resourceTemplates: [
|
|
983
|
-
{
|
|
984
|
-
name: "mongodb_query",
|
|
985
|
-
description: "Template for constructing MongoDB queries",
|
|
986
|
-
uriTemplate: "mongodb:///{collection}",
|
|
987
|
-
text: `To query MongoDB collections, you can use these operators:
|
|
988
|
-
|
|
989
|
-
Filter operators:
|
|
990
|
-
- $eq: Matches values equal to a specified value
|
|
991
|
-
- $gt/$gte: Matches values greater than (or equal to) a specified value
|
|
992
|
-
- $lt/$lte: Matches values less than (or equal to) a specified value
|
|
993
|
-
- $in: Matches any of the values in an array
|
|
994
|
-
- $nin: Matches none of the values in an array
|
|
995
|
-
- $ne: Matches values not equal to a specified value
|
|
996
|
-
- $exists: Matches documents that have the specified field
|
|
997
|
-
|
|
998
|
-
Example queries:
|
|
999
|
-
1. Find documents where age > 21:
|
|
1000
|
-
{ "age": { "$gt": 21 } }
|
|
1001
|
-
|
|
1002
|
-
2. Find documents with specific status:
|
|
1003
|
-
{ "status": { "$in": ["active", "pending"] } }
|
|
1004
|
-
|
|
1005
|
-
3. Find documents with existing email:
|
|
1006
|
-
{ "email": { "$exists": true } }
|
|
1007
|
-
|
|
1008
|
-
Use these patterns to construct MongoDB queries.`,
|
|
1009
|
-
},
|
|
1010
|
-
],
|
|
1011
|
-
};
|
|
1012
|
-
});
|
|
3
|
+
import { connectToMongoDB } from "./mongo.js";
|
|
4
|
+
import { createServer } from "./server.js";
|
|
5
|
+
// Declare a client variable in the global scope for cleanup handlers
|
|
6
|
+
let mongoClient = null; // TODO: Fix Typescript error
|
|
1013
7
|
/**
|
|
1014
8
|
* Start the server using stdio transport and initialize MongoDB connection.
|
|
1015
9
|
*/
|
|
1016
10
|
async function main() {
|
|
1017
11
|
const args = process.argv.slice(2);
|
|
12
|
+
// Default to environment variables
|
|
1018
13
|
let connectionUrl = "";
|
|
1019
|
-
let readOnlyMode = false;
|
|
1020
|
-
// Parse command line arguments
|
|
14
|
+
let readOnlyMode = process.env.MCP_MONGODB_READONLY === "true" || false;
|
|
15
|
+
// Parse command line arguments (these take precedence)
|
|
1021
16
|
for (let i = 0; i < args.length; i++) {
|
|
1022
17
|
if (args[i] === "--read-only" || args[i] === "-r") {
|
|
1023
18
|
readOnlyMode = true;
|
|
@@ -1026,29 +21,54 @@ async function main() {
|
|
|
1026
21
|
connectionUrl = args[i];
|
|
1027
22
|
}
|
|
1028
23
|
}
|
|
24
|
+
// If no connection URL from command line, use environment variable
|
|
25
|
+
if (!connectionUrl) {
|
|
26
|
+
connectionUrl = process.env.MCP_MONGODB_URI || "";
|
|
27
|
+
}
|
|
1029
28
|
if (!connectionUrl) {
|
|
1030
|
-
console.error("Please provide a MongoDB connection URL
|
|
29
|
+
console.error("Please provide a MongoDB connection URL via command-line argument or MCP_MONGODB_URI environment variable");
|
|
1031
30
|
console.error("Usage: command <mongodb-url> [--read-only|-r]");
|
|
31
|
+
console.error(" or: MCP_MONGODB_URI=<mongodb-url> [MCP_MONGODB_READONLY=true] command");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
// Ensure connection URL has the correct prefix
|
|
35
|
+
if (!connectionUrl.startsWith("mongodb://") &&
|
|
36
|
+
!connectionUrl.startsWith("mongodb+srv://")) {
|
|
37
|
+
console.error("Invalid MongoDB connection URL. URL must start with 'mongodb://' or 'mongodb+srv://'");
|
|
1032
38
|
process.exit(1);
|
|
1033
39
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
40
|
+
try {
|
|
41
|
+
const { client, db, isConnected, isReadOnlyMode } = await connectToMongoDB(connectionUrl, readOnlyMode);
|
|
42
|
+
// Store client in global variable for cleanup
|
|
43
|
+
mongoClient = client;
|
|
44
|
+
if (!isConnected || !client || !db) {
|
|
45
|
+
console.error("Failed to connect to MongoDB");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
// Pass db instead of client to createServer
|
|
49
|
+
const server = createServer(client, db, isReadOnlyMode);
|
|
50
|
+
const transport = new StdioServerTransport();
|
|
51
|
+
await server.connect(transport);
|
|
52
|
+
console.warn("Server connected successfully");
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error("Failed to connect to MongoDB:", error);
|
|
56
|
+
if (mongoClient) {
|
|
57
|
+
await mongoClient.close();
|
|
58
|
+
}
|
|
1037
59
|
process.exit(1);
|
|
1038
60
|
}
|
|
1039
|
-
const transport = new StdioServerTransport();
|
|
1040
|
-
await server.connect(transport);
|
|
1041
61
|
}
|
|
1042
62
|
// Handle cleanup
|
|
1043
63
|
process.on("SIGINT", async () => {
|
|
1044
|
-
if (
|
|
1045
|
-
await
|
|
64
|
+
if (mongoClient) {
|
|
65
|
+
await mongoClient.close();
|
|
1046
66
|
}
|
|
1047
67
|
process.exit(0);
|
|
1048
68
|
});
|
|
1049
69
|
process.on("SIGTERM", async () => {
|
|
1050
|
-
if (
|
|
1051
|
-
await
|
|
70
|
+
if (mongoClient) {
|
|
71
|
+
await mongoClient.close();
|
|
1052
72
|
}
|
|
1053
73
|
process.exit(0);
|
|
1054
74
|
});
|