mcp-mongo-server 1.1.1 → 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/build/mongo.js ADDED
@@ -0,0 +1,33 @@
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
+ }
@@ -0,0 +1,507 @@
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
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Handles completion requests from the Model Context Protocol
3
+ * @param options The request options
4
+ * @returns Completion result with matching values
5
+ */
6
+ export async function handleCompletionRequest({ request, client, db, isReadOnlyMode, }) {
7
+ const { ref, argument } = request.params;
8
+ // Handle completions for prompts
9
+ if (ref.type === "ref/prompt") {
10
+ return handlePromptCompletion(client, db, isReadOnlyMode, ref.name, argument);
11
+ }
12
+ // Handle completions for resources
13
+ if (ref.type === "ref/resource") {
14
+ return handleResourceCompletion(client, db, isReadOnlyMode, ref.uri, argument);
15
+ }
16
+ // Default empty response if reference type is not supported
17
+ return emptyCompletionResult();
18
+ }
19
+ /**
20
+ * Handle completion requests for prompts
21
+ * @param client MongoDB client
22
+ * @param db MongoDB database
23
+ * @param isReadOnlyMode Whether the database is in read-only mode
24
+ * @param promptName Name of the prompt
25
+ * @param argument Argument being completed
26
+ * @returns Completion result
27
+ */
28
+ async function handlePromptCompletion(client, db, isReadOnlyMode, promptName, argument) {
29
+ if (!promptName) {
30
+ return emptyCompletionResult();
31
+ }
32
+ // Handle collection name completions
33
+ if (argument.name === "collection") {
34
+ return await completeCollectionNames(argument.value, db, isReadOnlyMode);
35
+ }
36
+ // Add other prompt completions here as needed
37
+ return emptyCompletionResult();
38
+ }
39
+ /**
40
+ * Handle completion requests for resources
41
+ * @param client MongoDB client
42
+ * @param db MongoDB database
43
+ * @param isReadOnlyMode Whether the database is in read-only mode
44
+ * @param uri Resource URI
45
+ * @param argument Argument being completed
46
+ * @returns Completion result
47
+ */
48
+ async function handleResourceCompletion(client, db, isReadOnlyMode, promptName, argument) {
49
+ if (!promptName) {
50
+ return emptyCompletionResult();
51
+ }
52
+ // Handle collection name completions
53
+ if (argument.name === "collection") {
54
+ return await completeCollectionNames(argument.value, db, isReadOnlyMode);
55
+ }
56
+ // Add other prompt completions here as needed
57
+ return emptyCompletionResult();
58
+ }
59
+ /**
60
+ * Get collection name completions for a partial value
61
+ * @param partialValue Partial collection name
62
+ * @param db MongoDB database
63
+ * @param isReadOnlyMode Whether the database is in read-only mode
64
+ * @returns Completion result with matching collection names
65
+ */
66
+ async function completeCollectionNames(partialValue, db, isReadOnlyMode) {
67
+ try {
68
+ console.warn(`Completing collection names with partial value: ${partialValue}`);
69
+ // Get list of collections
70
+ const collections = await db.listCollections().toArray();
71
+ // Filter collections by partial value (case insensitive)
72
+ const matchingCollections = collections
73
+ .map((c) => c.name)
74
+ .filter((name) =>
75
+ // Filter out system collections
76
+ !name.startsWith("system.") &&
77
+ // Match partial value
78
+ name.toLowerCase().includes(partialValue.toLowerCase()))
79
+ // Sort alphabetically
80
+ .sort();
81
+ console.warn(`Found ${matchingCollections.length} matching collections`);
82
+ // Limit to 100 items as per spec
83
+ const MAX_ITEMS = 100;
84
+ const limitedResults = matchingCollections.slice(0, MAX_ITEMS);
85
+ const hasMore = matchingCollections.length > MAX_ITEMS;
86
+ return {
87
+ completion: {
88
+ values: limitedResults,
89
+ total: matchingCollections.length,
90
+ hasMore,
91
+ },
92
+ };
93
+ }
94
+ catch (error) {
95
+ console.error("Error completing collection names:", error);
96
+ // Return empty result on error
97
+ return emptyCompletionResult();
98
+ }
99
+ }
100
+ /**
101
+ * Create an empty completion result
102
+ * @returns Empty completion result object
103
+ */
104
+ function emptyCompletionResult() {
105
+ return {
106
+ completion: {
107
+ values: [],
108
+ total: 0,
109
+ hasMore: false,
110
+ },
111
+ };
112
+ }
@@ -0,0 +1,20 @@
1
+ export async function handlePingRequest({ request, client, db, isReadOnlyMode, }) {
2
+ try {
3
+ // Check MongoDB connection
4
+ if (!client) {
5
+ throw new Error("MongoDB connection is not available");
6
+ }
7
+ // Ping MongoDB to verify connection
8
+ const pong = await db.command({ ping: 1 });
9
+ if (pong.ok !== 1) {
10
+ throw new Error(`MongoDB ping failed: ${pong.errmsg}`);
11
+ }
12
+ return {};
13
+ }
14
+ catch (error) {
15
+ if (error instanceof Error) {
16
+ throw new Error(`MongoDB ping failed: ${error.message}`);
17
+ }
18
+ throw new Error("MongoDB ping failed: Unknown error");
19
+ }
20
+ }