Skip to main content
Glama

MongoDB Lens

#!/usr/bin/env node import { spawn } from 'child_process' import { dirname, join } from 'path' import { fileURLToPath } from 'url' import { existsSync } from 'fs' import mongodb from 'mongodb' const { MongoClient, ObjectId } = mongodb const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) const runTests = async () => { parseCommandLineArgs() if (process.argv.includes('--list')) return listAllTests() try { logHeader('MongoDB Lens Test Suite', 'margin:bottom') await initialize() logHeader('Testing Tools', 'margin:top,bottom') for (const group of TEST_GROUPS) { if (group.name !== 'Resources' && group.name !== 'Prompts') { await runTestGroup(group.name, group.tests) } } logHeader('Testing Resources', 'margin:top,bottom') const resourcesGroup = TEST_GROUPS.find(g => g.name === 'Resources') if (resourcesGroup) await runTestGroup(resourcesGroup.name, resourcesGroup.tests) logHeader('Testing Prompts', 'margin:top,bottom') const promptsGroup = TEST_GROUPS.find(g => g.name === 'Prompts') if (promptsGroup) await runTestGroup(promptsGroup.name, promptsGroup.tests) } finally { await cleanup() displayTestSummary() } } const runTestGroup = async (groupName, tests) => { let anyTestsInGroup = false for (const test of tests) { if (shouldRunTest(test.name, groupName)) { anyTestsInGroup = true break } } if (!anyTestsInGroup) return console.log(`${COLORS.yellow}Skipping group: ${groupName} - No matching tests${COLORS.reset}`) console.log(`${COLORS.blue}Running tests in group: ${groupName}${COLORS.reset}`) for (const test of tests) { if (shouldRunTest(test.name, groupName)) { await runTest(test.name, test.fn) } else { skipTest(test.name, 'Not selected for execution') } } } const shouldRunTest = (testName, groupName) => { if (!testFilters.length && !groupFilters.length && !patternFilters.length) return true if (testFilters.includes(testName)) return true if (groupFilters.includes(groupName)) return true for (const pattern of patternFilters) { const regexPattern = pattern.replace(/\*/g, '.*') const regex = new RegExp(regexPattern, 'i') if (regex.test(testName)) return true } return false } const listAllTests = () => { logHeader('Available Test Groups & Tests', 'margin:bottom') TEST_GROUPS.forEach(group => { console.log(`${COLORS.yellow}${group.name}:${COLORS.reset}`) group.tests.forEach(test => { console.log(`- ${test.name}`) }) console.log('') }) console.log(`${COLORS.yellow}Usage:${COLORS.reset}`) console.log(' --test=<test-name> Run specific test(s), comma separated') console.log(' --group=<group-name> Run all tests in specific group(s), comma separated') console.log(' --pattern=<pattern> Run tests matching pattern (glob style, e.g. *collection*)') console.log(' --list List all available tests without running them') process.exit(0) } const parseCommandLineArgs = () => { for (let i = 2; i < process.argv.length; i++) { const arg = process.argv[i] if (arg.startsWith('--test=')) { const tests = arg.replace('--test=', '').split(',') testFilters.push(...tests.map(t => t.trim())) } else if (arg.startsWith('--group=')) { const groups = arg.replace('--group=', '').split(',') groupFilters.push(...groups.map(g => g.trim())) } else if (arg.startsWith('--pattern=')) { const patterns = arg.replace('--pattern=', '').split(',') patternFilters.push(...patterns.map(p => p.trim())) } } if (testFilters.length || groupFilters.length || patternFilters.length) { console.log(`${COLORS.blue}Running with filters:${COLORS.reset}`) if (testFilters.length) console.log(`${COLORS.blue}Tests: ${testFilters.join(', ')}${COLORS.reset}`) if (groupFilters.length) console.log(`${COLORS.blue}Groups: ${groupFilters.join(', ')}${COLORS.reset}`) if (patternFilters.length) console.log(`${COLORS.blue}Patterns: ${patternFilters.join(', ')}${COLORS.reset}`) } } const initialize = async () => { mongoUri = await setupMongoUri() await connectToMongo() await setupTestEnvironment() } const setupMongoUri = async () => { const uri = process.env.CONFIG_MONGO_URI if (uri === 'mongodb-memory-server') return await startInMemoryMongo() if (!uri) { console.error(`${COLORS.red}No MongoDB URI provided. Please set CONFIG_MONGO_URI environment variable.${COLORS.reset}`) console.error(`${COLORS.yellow}Example: CONFIG_MONGO_URI=mongodb://localhost:27017 node mongodb-lens.test.js${COLORS.reset}`) console.error(`${COLORS.yellow}Example: CONFIG_MONGO_URI=mongodb-memory-server node mongodb-lens.test.js${COLORS.reset}`) process.exit(1) } return uri } const startInMemoryMongo = async () => { console.log(`${COLORS.yellow}In-memory MongoDB requested. Checking if mongodb-memory-server is available…${COLORS.reset}`) try { const { MongoMemoryServer } = await import('mongodb-memory-server') const mongod = await MongoMemoryServer.create() const uri = mongod.getUri() console.log(`${COLORS.green}In-memory MongoDB instance started at: ${uri}${COLORS.reset}`) process.on('exit', async () => { console.log(`${COLORS.yellow}Stopping in-memory MongoDB server…${COLORS.reset}`) await mongod.stop() }) return uri } catch (err) { console.error(`${COLORS.red}Failed to start in-memory MongoDB: ${err.message}${COLORS.reset}`) console.error(`${COLORS.yellow}Install with: npm install mongodb-memory-server --save-dev${COLORS.reset}`) process.exit(1) } } const connectToMongo = async () => { console.log(`${COLORS.blue}Connecting to MongoDB: ${obfuscateMongoUri(mongoUri)}${COLORS.reset}`) if (!existsSync(MONGODB_LENS_PATH)) { console.error(`${COLORS.red}MongoDB Lens script not found at: ${MONGODB_LENS_PATH}${COLORS.reset}`) process.exit(1) } directMongoClient = new MongoClient(mongoUri, { retryWrites: true }) await directMongoClient.connect() console.log(`${COLORS.green}Connected to MongoDB successfully.${COLORS.reset}`) } const obfuscateMongoUri = uri => { if (!uri || typeof uri !== 'string') return uri try { if (uri.includes('@') && uri.includes('://')) { const parts = uri.split('@') const authPart = parts[0] const restPart = parts.slice(1).join('@') const authIndex = authPart.lastIndexOf('://') if (authIndex !== -1) { const protocol = authPart.substring(0, authIndex + 3) const credentials = authPart.substring(authIndex + 3) if (credentials.includes(':')) { const [username] = credentials.split(':') return `${protocol}${username}:********@${restPart}` } } } return uri } catch (error) { return uri } } const setupTestEnvironment = async () => { await checkServerCapabilities() testDb = directMongoClient.db(TEST_DB_NAME) await cleanupTestDatabase() await setupTestData() console.log(`${COLORS.blue}Running tests against MongoDB Lens…${COLORS.reset}`) } const checkServerCapabilities = async () => { try { const adminDb = directMongoClient.db('admin') const replSetStatus = await adminDb.command({ replSetGetStatus: 1 }).catch(() => null) isReplSet = !!replSetStatus console.log(`${COLORS.blue}MongoDB instance ${isReplSet ? 'is' : 'is not'} a replica set.${COLORS.reset}`) const listShards = await adminDb.command({ listShards: 1 }).catch(() => null) isSharded = !!listShards console.log(`${COLORS.blue}MongoDB instance ${isSharded ? 'is' : 'is not'} a sharded cluster.${COLORS.reset}`) } catch (error) { console.log(`${COLORS.yellow}Not a replica set: ${error.message}${COLORS.reset}`) } } const setupTestData = async () => { try { testCollection = testDb.collection(TEST_COLLECTION_NAME) await createTestDocuments() await createTestIndexes() await createTestGeospatialData() await createTestUser() console.log(`${COLORS.green}Test data setup complete.${COLORS.reset}`) } catch (err) { console.error(`${COLORS.red}Error setting up test data: ${err.message}${COLORS.reset}`) } } const createTestDocuments = async () => { const testDocuments = Array(50).fill(0).map((_, i) => ({ _id: new ObjectId(), name: `Test Document ${i}`, value: i, tags: [`tag${i % 5}`, `category${i % 3}`], isActive: i % 2 === 0, createdAt: new Date(Date.now() - i * 86400000) })) await testCollection.insertMany(testDocuments) const anotherCollection = testDb.collection(ANOTHER_TEST_COLLECTION) await anotherCollection.insertMany([ { name: 'Test 1', value: 10 }, { name: 'Test 2', value: 20 } ]) await testCollection.insertOne({ name: 'Date Test', startDate: new Date('2023-01-01'), endDate: new Date('2023-12-31') }) } const createTestIndexes = async () => { await testCollection.createIndex({ name: 1 }) await testCollection.createIndex({ value: -1 }) await testCollection.createIndex({ tags: 1 }) await testCollection.createIndex({ name: 'text', tags: 'text' }) await testCollection.createIndex({ location: '2dsphere' }) } const createTestGeospatialData = async () => { await testCollection.insertMany([ { name: 'Geo Test 1', location: { type: 'Point', coordinates: [-73.9857, 40.7484] } }, { name: 'Geo Test 2', location: { type: 'Point', coordinates: [-118.2437, 34.0522] } } ]) } const createTestUser = async () => { try { await testDb.command({ createUser: 'testuser', pwd: 'testpassword', roles: [{ role: 'read', db: TEST_DB_NAME }] }) } catch (e) { console.log(`${COLORS.yellow}Could not create test user: ${e.message}${COLORS.reset}`) } } const runTest = async (name, testFn) => { stats.total++ console.log(`${COLORS.blue}Running test: ${name}${COLORS.reset}`) try { const startTime = Date.now() await testFn() const duration = Date.now() - startTime console.log(`${COLORS.green}✓ PASS: ${name} (${duration}ms)${COLORS.reset}`) stats.passed++ } catch (err) { console.error(`${COLORS.red}✗ FAIL: ${name} - ${err.message}${COLORS.reset}`) console.error(`${COLORS.red}Stack: ${err.stack}${COLORS.reset}`) stats.failed++ } } const skipTest = (name, reason) => { stats.total++ stats.skipped++ console.log(`${COLORS.yellow}⚠ SKIP: ${name} - ${reason}${COLORS.reset}`) } const startLensServer = async () => { console.log('Starting MongoDB Lens server…') const env = { ...process.env, CONFIG_MONGO_URI: mongoUri, CONFIG_LOG_LEVEL: isDebugging ? 'verbose' : 'info', } if (testConfig.disableTokens) { env.CONFIG_DISABLE_DESTRUCTIVE_OPERATION_TOKENS = 'true' } lensProcess = spawn('node', [MONGODB_LENS_PATH], { env: env, stdio: ['pipe', 'pipe', 'pipe'] }) setupResponseHandling() return waitForServerStart() } const setupResponseHandling = () => { lensProcess.stdout.on('data', data => { const response = data.toString().trim() try { const parsed = JSON.parse(response) if (parsed.id && responseHandlers.has(parsed.id)) { const handler = responseHandlers.get(parsed.id) responseHandlers.delete(parsed.id) handler.resolve(parsed) } } catch (e) { console.error(`Parse error: ${e.message}`) console.error(`Response was: ${response.substring(0, 200)}${response.length > 200 ? '…' : ''}`) } }) lensProcess.stderr.on('data', data => { const output = data.toString().trim() if (isDebugging) console.log(`${COLORS.gray}[SERVER] ${output.split('\n').join(`\n[SERVER] `)}${COLORS.reset}`) }) } const waitForServerStart = () => { return new Promise((resolve, reject) => { const handler = data => { if (data.toString().includes('MongoDB Lens server running.')) { lensProcess.stderr.removeListener('data', handler) console.log('MongoDB Lens server started successfully.') resolve() } } lensProcess.stderr.on('data', handler) setTimeout(() => reject(new Error('Server startup timed out')), testConfig.serverStartupTimeout) }) } const runLensCommand = async ({ command, params = {} }) => { if (!lensProcess) await startLensServer() const requestId = nextRequestId++ const { method, methodParams } = mapToLensMethod(command, params) return new Promise((resolve, reject) => { const request = { jsonrpc: '2.0', id: requestId, method: method, params: methodParams } responseHandlers.set(requestId, { resolve, reject }) if (isDebugging) console.log(`Sending request #${requestId}:`, JSON.stringify(request)) lensProcess.stdin.write(JSON.stringify(request) + '\n') setTimeout(() => { if (responseHandlers.has(requestId)) { responseHandlers.delete(requestId) reject(new Error(`Request ${method} timed out after ${testConfig.requestTimeout/1000} seconds`)) } }, testConfig.requestTimeout) }) } const mapToLensMethod = (command, params) => { let method, methodParams switch(command) { case 'mcp.resource.get': method = 'resources/read' methodParams = { uri: params.uri } break case 'mcp.tool.invoke': method = 'tools/call' methodParams = { name: params.name, arguments: params.args || {} } break case 'mcp.prompt.start': method = 'prompts/get' methodParams = { name: params.name, arguments: params.args || {} } break case 'initialize': method = 'initialize' methodParams = params break default: method = command methodParams = params } return { method, methodParams } } const useTestDatabase = async () => { await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'use-database', args: { database: TEST_DB_NAME } } }) } const handleDestructiveOperationToken = async (tokenResponse, toolName, params) => { if (testConfig.disableTokens) { return assertToolSuccess(tokenResponse, 'has been permanently deleted') } assert(tokenResponse?.result?.content[0].text.includes('Confirmation code:'), 'Confirmation message not found') const tokenMatch = tokenResponse.result.content[0].text.match(/Confirmation code:\s+(\d+)/) assert(tokenMatch && tokenMatch[1], 'Confirmation code not found in text') const token = tokenMatch[1] const completeResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: toolName, args: { ...params, token } } }) return assertToolSuccess(completeResponse, 'has been permanently deleted') } const assertToolSuccess = (response, successIndicator) => { assert(response?.result?.content, 'No content in response') assert(Array.isArray(response.result.content), 'Content not an array') assert(response.result.content.length > 0, 'Empty content array') assert(response.result.content[0].text.includes(successIndicator), `Success message not found: "${successIndicator}"`) return response } const assertResourceSuccess = (response, successIndicator) => { assert(response?.result?.contents, 'No contents in response') assert(Array.isArray(response.result.contents), 'Contents not an array') assert(response.result.contents.length > 0, 'Empty contents array') assert(response.result.contents[0].text.includes(successIndicator), `Success message not found: "${successIndicator}"`) return response } const assertPromptSuccess = (response, successIndicator) => { assert(response?.result?.messages, 'No messages in response') assert(Array.isArray(response.result.messages), 'Messages not an array') assert(response.result.messages.length > 0, 'Empty messages array') assert(response.result.messages[0].content.text.includes(successIndicator), `Success message not found: "${successIndicator}"`) return response } const assert = (condition, message, context = null) => { if (condition) return if (context) console.error('CONTEXT:', JSON.stringify(context, null, 2)) throw new Error(message || 'Assertion failed') } const testConnectMongodbTool = async () => { const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'connect-mongodb', args: { uri: mongoUri, validateConnection: 'true' } } }) assertToolSuccess(response, 'Successfully connected') } const testConnectOriginalTool = async () => { await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'connect-mongodb', args: { uri: mongoUri, validateConnection: 'true' } } }) const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'connect-original', args: { validateConnection: 'true' } } }) assertToolSuccess(response, 'Successfully connected') } const testAddConnectionAliasTool = async () => { const aliasName = 'test_alias' const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'add-connection-alias', args: { alias: aliasName, uri: mongoUri } } }) assertToolSuccess(response, `Successfully added connection alias '${aliasName}'`) } const testListConnectionsTool = async () => { const aliasName = 'test_alias_for_list' await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'add-connection-alias', args: { alias: aliasName, uri: mongoUri } } }) const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'list-connections' } }) assertToolSuccess(response, aliasName) } const testListDatabasesTool = async () => { const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'list-databases' } }) assertToolSuccess(response, TEST_DB_NAME) } const testCurrentDatabaseTool = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'current-database' } }) assertToolSuccess(response, `Current database: ${TEST_DB_NAME}`) } const testCreateDatabaseTool = async () => { const testDbName = `test_create_db_${Date.now()}` const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'create-database', args: { name: testDbName, switch: 'true', validateName: 'true' } } }) assertToolSuccess(response, `Database '${testDbName}' created`) const dbs = await directMongoClient.db('admin').admin().listDatabases() const dbExists = dbs.databases.some(db => db.name === testDbName) assert(dbExists, `Created database '${testDbName}' not found in database list`) await directMongoClient.db(testDbName).dropDatabase() } const testUseDatabaseTool = async () => { const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'use-database', args: { database: TEST_DB_NAME } } }) assertToolSuccess(response, `Switched to database: ${TEST_DB_NAME}`) } const testDropDatabaseTool = async () => { const testDbToDrop = `test_drop_db_${Date.now()}` await directMongoClient.db(testDbToDrop).collection('test').insertOne({ test: 1 }) await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'use-database', args: { database: testDbToDrop } } }) const tokenResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'drop-database', args: { name: testDbToDrop } } }) await handleDestructiveOperationToken(tokenResponse, 'drop-database', { name: testDbToDrop }) const dbs = await directMongoClient.db('admin').admin().listDatabases() const dbExists = dbs.databases.some(db => db.name === testDbToDrop) assert(!dbExists, `Dropped database '${testDbToDrop}' still exists`) } const testCreateUserTool = async () => { const username = `test_user_${Date.now()}` await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'create-user', args: { username: username, password: 'test_password', roles: JSON.stringify([{ role: 'read', db: TEST_DB_NAME }]) } } }) assertToolSuccess(response, `User '${username}' created`) const usersInfo = await testDb.command({ usersInfo: username }) assert(usersInfo.users.length > 0, `Created user '${username}' not found`) } const testDropUserTool = async () => { const username = `test_drop_user_${Date.now()}` await useTestDatabase() await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'create-user', args: { username: username, password: 'test_password', roles: JSON.stringify([{ role: 'read', db: TEST_DB_NAME }]) } } }) const tokenResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'drop-user', args: { username: username } } }) let dropped = false if (testConfig.disableTokens) { assertToolSuccess(tokenResponse, 'dropped successfully') dropped = true } else { assert(tokenResponse?.result?.content[0].text.includes('Confirmation code:'), 'Confirmation message not found') const tokenMatch = tokenResponse.result.content[0].text.match(/Confirmation code:\s+(\d+)/) assert(tokenMatch && tokenMatch[1], 'Confirmation code not found in text') const token = tokenMatch[1] const dropResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'drop-user', args: { username: username, token } } }) assertToolSuccess(dropResponse, 'dropped successfully') dropped = true } if (dropped) { const usersInfo = await testDb.command({ usersInfo: username }) assert(usersInfo.users.length === 0, `Dropped user '${username}' still exists`) } } const testListCollectionsTool = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'list-collections' } }) assertToolSuccess(response, TEST_COLLECTION_NAME) } const testCreateCollectionTool = async () => { const collectionName = `test_create_coll_${Date.now()}` await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'create-collection', args: { name: collectionName, options: JSON.stringify({}) } } }) assertToolSuccess(response, `Collection '${collectionName}' created`) const collections = await testDb.listCollections().toArray() const collExists = collections.some(coll => coll.name === collectionName) assert(collExists, `Created collection '${collectionName}' not found`) } const testDropCollectionTool = async () => { const collectionName = `test_drop_coll_${Date.now()}` await useTestDatabase() await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'create-collection', args: { name: collectionName, options: JSON.stringify({}) } } }) const tokenResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'drop-collection', args: { name: collectionName } } }) await handleDestructiveOperationToken(tokenResponse, 'drop-collection', { name: collectionName }) const collections = await testDb.listCollections().toArray() const collExists = collections.some(coll => coll.name === collectionName) assert(!collExists, `Dropped collection '${collectionName}' still exists`) } const testRenameCollectionTool = async () => { const oldName = `test_rename_old_${Date.now()}` const newName = `test_rename_new_${Date.now()}` await useTestDatabase() await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'create-collection', args: { name: oldName, options: JSON.stringify({}) } } }) const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'rename-collection', args: { oldName: oldName, newName: newName, dropTarget: 'false' } } }) assertToolSuccess(response, `renamed to '${newName}'`) const collections = await testDb.listCollections().toArray() const oldExists = collections.some(coll => coll.name === oldName) const newExists = collections.some(coll => coll.name === newName) assert(!oldExists, `Old collection '${oldName}' still exists`) assert(newExists, `New collection '${newName}' not found`) } const testValidateCollectionTool = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'validate-collection', args: { collection: TEST_COLLECTION_NAME, full: 'false' } } }) assertToolSuccess(response, 'Validation Results') const content = response.result.content[0].text assert(content.includes('Collection'), 'Collection name not found') assert(content.includes('Valid:') || content.includes('Records Validated:'), 'Validation results not found') } const testDistinctValuesTool = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'distinct-values', args: { collection: TEST_COLLECTION_NAME, field: 'tags', filter: '{}' } } }) assertToolSuccess(response, 'Distinct values for field') const content = response.result.content[0].text assert(content.includes('tag') || content.includes('category'), 'Expected distinct tag values not found') } const testFindDocumentsTool = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'find-documents', args: { collection: TEST_COLLECTION_NAME, filter: '{"value": {"$lt": 10}}', limit: 5 } } }) const content = response.result.content[0].text assert(content.includes('"value":'), 'Value field not found') assert(content.includes('"name":'), 'Name field not found') } const testCountDocumentsTool = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'count-documents', args: { collection: TEST_COLLECTION_NAME, filter: '{"isActive": true}' } } }) assertToolSuccess(response, 'Count:') assert(/Count: \d+ document/.test(response.result.content[0].text), 'Count number not found') } const testInsertDocumentTool = async () => { await useTestDatabase() const testDoc = { name: 'Test Insert', value: 999, tags: ['test', 'insert'], isActive: true, createdAt: new Date().toISOString() } const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'insert-document', args: { collection: TEST_COLLECTION_NAME, document: JSON.stringify(testDoc) } } }) assertToolSuccess(response, 'inserted successfully') const countResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'count-documents', args: { collection: TEST_COLLECTION_NAME, filter: '{"value": 999}' } } }) assert(countResponse.result.content[0].text.includes('Count: 1'), 'Inserted document not found') } const testUpdateDocumentTool = async () => { await useTestDatabase() const testDoc = { name: 'Update Test', value: 888, tags: ['update'], isActive: true } await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'insert-document', args: { collection: TEST_COLLECTION_NAME, document: JSON.stringify(testDoc) } } }) const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'update-document', args: { collection: TEST_COLLECTION_NAME, filter: '{"value": 888}', update: '{"$set": {"name": "Updated Test", "tags": ["updated"]}}' } } }) assertToolSuccess(response, 'Matched:') assert(response.result.content[0].text.includes('Modified:'), 'Modified count not found') const findResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'find-documents', args: { collection: TEST_COLLECTION_NAME, filter: '{"value": 888}' } } }) const responseText = findResponse.result.content[0].text assert(responseText.includes('"name"') && responseText.includes('Updated Test'), 'Updated name not found') assert(responseText.includes('"tags"') && responseText.includes('updated'), 'Updated tags not found') } const testDeleteDocumentTool = async () => { await useTestDatabase() const testDoc = { name: 'Delete Test', value: 777, tags: ['delete'], isActive: true } await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'insert-document', args: { collection: TEST_COLLECTION_NAME, document: JSON.stringify(testDoc) } } }) const countBeforeResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'count-documents', args: { collection: TEST_COLLECTION_NAME, filter: '{"value": 777}' } } }) assert(countBeforeResponse.result.content[0].text.includes('Count: 1'), 'Test document not found before delete') const tokenResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'delete-document', args: { collection: TEST_COLLECTION_NAME, filter: '{"value": 777}', many: 'false' } } }) if (testConfig.disableTokens) { assertToolSuccess(tokenResponse, 'Successfully deleted') } else { assert(tokenResponse?.result?.content[0].text.includes('Confirmation code:'), 'Confirmation message not found') const tokenMatch = tokenResponse.result.content[0].text.match(/Confirmation code:\s+(\d+)/) assert(tokenMatch && tokenMatch[1], 'Confirmation code not found in text') const token = tokenMatch[1] const deleteResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'delete-document', args: { collection: TEST_COLLECTION_NAME, filter: '{"value": 777}', many: 'false', token } } }) assertToolSuccess(deleteResponse, 'Successfully deleted') } const countResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'count-documents', args: { collection: TEST_COLLECTION_NAME, filter: '{"value": 777}' } } }) assert(countResponse.result.content[0].text.includes('Count: 0'), 'Document still exists after deletion') } const testAggregateDataTool = async () => { await useTestDatabase() const pipeline = [ { $match: { isActive: true } }, { $group: { _id: null, avgValue: { $avg: '$value' }, count: { $sum: 1 } } } ] const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'aggregate-data', args: { collection: TEST_COLLECTION_NAME, pipeline: JSON.stringify(pipeline) } } }) const content = response.result.content[0].text assert(content.includes('"avgValue":'), 'Average value not found') assert(content.includes('"count":'), 'Count not found') } const testCreateIndexTool = async () => { await useTestDatabase() const indexName = `test_index_${Date.now()}` const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'create-index', args: { collection: TEST_COLLECTION_NAME, keys: '{"createdAt": 1}', options: JSON.stringify({ name: indexName }) } } }) assertToolSuccess(response, 'Index created') assert(response.result.content[0].text.includes(indexName), 'Index name not found') } const testDropIndexTool = async () => { await useTestDatabase() const indexName = `test_drop_index_${Date.now()}` await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'create-index', args: { collection: TEST_COLLECTION_NAME, keys: '{"testField": 1}', options: JSON.stringify({ name: indexName }) } } }) const tokenResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'drop-index', args: { collection: TEST_COLLECTION_NAME, indexName: indexName } } }) let dropped = false if (testConfig.disableTokens) { assertToolSuccess(tokenResponse, 'dropped from collection') dropped = true } else { assert(tokenResponse?.result?.content[0].text.includes('Confirmation code:'), 'Confirmation message not found') const tokenMatch = tokenResponse.result.content[0].text.match(/Confirmation code:\s+(\d+)/) assert(tokenMatch && tokenMatch[1], 'Confirmation code not found in text') const token = tokenMatch[1] const dropResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'drop-index', args: { collection: TEST_COLLECTION_NAME, indexName: indexName, token } } }) assertToolSuccess(dropResponse, 'dropped from collection') dropped = true } if (dropped) { const indexes = await testDb.collection(TEST_COLLECTION_NAME).indexes() const indexStillExists = indexes.some(idx => idx.name === indexName) assert(!indexStillExists, `Dropped index '${indexName}' still exists`) } } const testAnalyzeSchemaTool = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'analyze-schema', args: { collection: TEST_COLLECTION_NAME, sampleSize: 20 } } }) assertToolSuccess(response, 'Schema for') const content = response.result.content[0].text assert(content.includes('name:'), 'Name field not found') assert(content.includes('value:'), 'Value field not found') assert(content.includes('tags:'), 'Tags field not found') } const testGenerateSchemaValidatorTool = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'generate-schema-validator', args: { collection: TEST_COLLECTION_NAME, strictness: 'moderate' } } }) assertToolSuccess(response, 'MongoDB JSON Schema Validator') const content = response.result.content[0].text assert(content.includes('$jsonSchema'), 'JSON Schema not found') assert(content.includes('properties'), 'Properties not found') } const testCompareSchemasTool = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'compare-schemas', args: { sourceCollection: TEST_COLLECTION_NAME, targetCollection: ANOTHER_TEST_COLLECTION, sampleSize: 10 } } }) assertToolSuccess(response, 'Schema Comparison') const content = response.result.content[0].text assert(content.includes('Source Collection'), 'Source info not found') assert(content.includes('Target Collection'), 'Target info not found') } const testExplainQueryTool = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'explain-query', args: { collection: TEST_COLLECTION_NAME, filter: '{"value": {"$gt": 10}}', verbosity: 'queryPlanner' } } }) assertToolSuccess(response, 'Query Explanation') assert(response.result.content[0].text.includes('Query Planner'), 'Query planner not found') } const testAnalyzeQueryPatternsTool = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'analyze-query-patterns', args: { collection: TEST_COLLECTION_NAME, duration: 1 } } }) assertToolSuccess(response, 'Query Pattern Analysis') const content = response.result.content[0].text assert( content.includes('Analysis') || content.includes('Recommendations') || content.includes('Queries') || content.includes('Patterns'), 'No analysis content found in response' ) } const testGetStatsTool = async () => { await useTestDatabase() const dbResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'get-stats', args: { target: 'database' } } }) assertToolSuccess(dbResponse, 'Statistics') const collResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'get-stats', args: { target: 'collection', name: TEST_COLLECTION_NAME } } }) assertToolSuccess(collResponse, 'Statistics') assert(collResponse.result.content[0].text.includes('Document Count'), 'Document count not found') } const testBulkOperationsTool = async () => { await useTestDatabase() const operations = [ { insertOne: { document: { name: 'Bulk Insert 1', value: 1001 } } }, { insertOne: { document: { name: 'Bulk Insert 2', value: 1002 } } }, { updateOne: { filter: { name: 'Bulk Insert 1' }, update: { $set: { updated: true } } } } ] const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'bulk-operations', args: { collection: TEST_COLLECTION_NAME, operations: JSON.stringify(operations), ordered: 'true' } } }) assertToolSuccess(response, 'Bulk Operations Results') assert(response.result.content[0].text.includes('Inserted:'), 'Insert count not found') } const testCreateTimeseriesCollectionTool = async () => { try { const adminDb = directMongoClient.db('admin') const serverInfo = await adminDb.command({ buildInfo: 1 }) const version = serverInfo.version.split('.').map(Number) if (version[0] < 5) { return skipTest('create-timeseries Tool', 'MongoDB version 5.0+ required for time series collections') } } catch (e) { return skipTest('create-timeseries Tool', `Could not determine MongoDB version: ${e.message}`) } await useTestDatabase() const collectionName = `timeseries_${Date.now()}` const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'create-timeseries', args: { name: collectionName, timeField: 'timestamp', metaField: 'metadata', granularity: 'seconds' } } }) assertToolSuccess(response, 'Time series collection') assert(response.result.content[0].text.includes('created'), 'Created confirmation not found') const collections = await testDb.listCollections({name: collectionName}).toArray() assert(collections.length > 0, `Timeseries collection '${collectionName}' not found`) assert(collections[0].options?.timeseries, 'Collection is not configured as timeseries') } const testCollationQueryTool = async () => { await useTestDatabase() const collationTestDocs = [ { name: 'café', language: 'French', rank: 1 }, { name: 'cafe', language: 'English', rank: 2 }, { name: 'CAFE', language: 'English', rank: 3 } ] await testDb.collection(TEST_COLLECTION_NAME).insertMany(collationTestDocs) console.log(`${COLORS.blue}Verifying collation test documents were inserted…${COLORS.reset}`) const testDocs = await testDb.collection(TEST_COLLECTION_NAME) .find({ name: { $in: ['café', 'cafe', 'CAFE'] } }) .toArray() console.log(`${COLORS.blue}Found ${testDocs.length} collation test documents${COLORS.reset}`) const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'collation-query', args: { collection: TEST_COLLECTION_NAME, filter: '{"name": "cafe"}', locale: 'en', strength: 1, caseLevel: 'false' } } }) const responseText = response.result.content[0].text assert( responseText.includes('café') || responseText.includes('cafe') || responseText.includes('CAFE') || responseText.includes('collation') || responseText.includes('Locale') || responseText.includes('Found'), 'Collation results not found' ) } const testTextSearchTool = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'text-search', args: { collection: TEST_COLLECTION_NAME, searchText: 'test', limit: 5 } } }) const responseText = response.result.content[0].text assert( responseText.includes('Found') || responseText.includes('No text index found'), 'Text search results or index message not found' ) } const testGeoQueryTool = async () => { await useTestDatabase() const geometry = { type: 'Point', coordinates: [-74, 40.7] } const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'geo-query', args: { collection: TEST_COLLECTION_NAME, operator: 'near', field: 'location', geometry: JSON.stringify(geometry), maxDistance: 2000000, limit: 10 } } }) assert(response?.result?.content, 'No content in response') assert(Array.isArray(response.result.content), 'Content not an array') assert(response.result.content.length > 0, 'Empty content array') const responseText = response.result.content[0].text assert( responseText.includes('coordinates') || responseText.includes('location') || responseText.includes('Geo Test'), 'Geospatial data not found in results' ) } const testTransactionTool = async () => { if (!isReplSet) { return skipTest('transaction Tool', 'MongoDB not in replica set mode - transactions require replica set') } await useTestDatabase() const operations = [ { operation: 'insert', collection: TEST_COLLECTION_NAME, document: { name: 'Transaction Test 1', value: 1 } }, { operation: 'insert', collection: TEST_COLLECTION_NAME, document: { name: 'Transaction Test 2', value: 2 } }, { operation: 'update', collection: TEST_COLLECTION_NAME, filter: { name: 'Transaction Test 1' }, update: { $set: { updated: true } } } ] const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'transaction', args: { operations: JSON.stringify(operations) } } }) assert(response?.result?.content, 'No content in response') assert(Array.isArray(response.result.content), 'Content not an array') assert(response.result.content.length > 0, 'Empty content array') const responseText = response.result.content[0].text assert( responseText.includes('Transaction') && (responseText.includes('Step') || responseText.includes('committed')), 'Transaction results not found' ) } const testWatchChangesTool = async () => { if (!isReplSet) { return skipTest('watch-changes Tool', 'MongoDB not in replica set mode - change streams require replica set') } await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'watch-changes', args: { collection: TEST_COLLECTION_NAME, operations: JSON.stringify(['insert', 'update', 'delete']), duration: 2, fullDocument: 'false' } } }) assert(response?.result?.content, 'No content in response') assert(Array.isArray(response.result.content), 'Content not an array') assert(response.result.content.length > 0, 'Empty content array') const responseText = response.result.content[0].text assert( responseText.includes('changes detected') || responseText.includes('No changes detected'), 'Change stream results not found' ) } const testGridFSOperationTool = async () => { await useTestDatabase() try { const bucket = new mongodb.GridFSBucket(testDb) const fileContent = Buffer.from('GridFS test file content') const uploadStream = bucket.openUploadStream('test-gridfs-file.txt') uploadStream.write(fileContent) uploadStream.end() await new Promise(resolve => uploadStream.on('finish', resolve)) } catch (e) { console.error(`Failed to create GridFS test file: ${e.message}`) } const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'gridfs-operation', args: { operation: 'list', bucket: 'fs', limit: 10 } } }) assert(response?.result?.content, 'No content in response') assert(Array.isArray(response.result.content), 'Content not an array') assert(response.result.content.length > 0, 'Empty content array') const responseText = response.result.content[0].text assert( responseText.includes('GridFS') || responseText.includes('Filename') || responseText.includes('Size:'), 'GridFS data not found in response' ) } const testClearCacheTool = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'clear-cache', args: { target: 'all' } } }) assertToolSuccess(response, 'cleared') } const testShardStatusTool = async () => { if (!isSharded) { return skipTest('shard-status Tool', 'MongoDB not in sharded cluster mode - sharding features require sharded deployment') } await useTestDatabase() const response = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'shard-status', args: { target: 'database' } } }) assert(response?.result?.content, 'No content in response') assert(Array.isArray(response.result.content), 'Content not an array') assert(response.result.content.length > 0, 'Empty content array') const responseText = response.result.content[0].text assert( responseText.includes('Sharding Status') || responseText.includes('sharded cluster') || responseText.includes('Sharding is not enabled') || responseText.includes('not a sharded cluster') || responseText.includes('not running with sharding'), 'Sharding status or message not found' ) } const testExportDataTool = async () => { await useTestDatabase() const jsonResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'export-data', args: { collection: TEST_COLLECTION_NAME, filter: '{"value": {"$lt": 10}}', format: 'json', limit: 5 } } }) assert(jsonResponse?.result?.content, 'No content in JSON export response') assert(Array.isArray(jsonResponse.result.content), 'JSON export content not an array') assert(jsonResponse.result.content.length > 0, 'Empty JSON export content array') console.log(`${COLORS.blue}JSON export response first 100 chars: ${jsonResponse.result.content[0].text.substring(0, 100)}${COLORS.reset}`) const jsonText = jsonResponse.result.content[0].text assert( jsonText.includes('{') && jsonText.includes('}'), 'JSON content not found in export' ) const csvResponse = await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'export-data', args: { collection: TEST_COLLECTION_NAME, filter: '{"value": {"$lt": 10}}', format: 'csv', fields: 'name,value,isActive', limit: 5 } } }) assert(csvResponse?.result?.content, 'No content in CSV export response') assert(Array.isArray(csvResponse.result.content), 'CSV export content not an array') assert(csvResponse.result.content.length > 0, 'Empty CSV export content array') console.log(`${COLORS.blue}CSV export response first 100 chars: ${csvResponse.result.content[0].text.substring(0, 100)}${COLORS.reset}`) assert(csvResponse.result.content[0].text.includes('name') && csvResponse.result.content[0].text.includes('value'), 'CSV headers not found') } const testDatabasesResource = async () => { const response = await runLensCommand({ command: 'mcp.resource.get', params: { uri: 'mongodb://databases' } }) assertResourceSuccess(response, 'Databases') assert(response.result.contents[0].text.includes(TEST_DB_NAME), 'Test database not found') } const testCollectionsResource = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.resource.get', params: { uri: 'mongodb://collections' } }) assertResourceSuccess(response, `Collections in ${TEST_DB_NAME}`) assert(response.result.contents[0].text.includes(TEST_COLLECTION_NAME), 'Test collection not found') } const testDatabaseUsersResource = async () => { const response = await runLensCommand({ command: 'mcp.resource.get', params: { uri: 'mongodb://database/users' } }) assert(response?.result?.contents, 'No contents in response') assert(Array.isArray(response.result.contents), 'Contents not an array') assert(response.result.contents.length > 0, 'Empty contents array') assert( response.result.contents[0].text.includes('Users in database') || response.result.contents[0].text.includes('Could not retrieve user information'), 'Users title or permission message not found' ) } const testDatabaseTriggersResource = async () => { const response = await runLensCommand({ command: 'mcp.resource.get', params: { uri: 'mongodb://database/triggers' } }) assert(response?.result?.contents, 'No contents in response') assert(Array.isArray(response.result.contents), 'Contents not an array') assert(response.result.contents.length > 0, 'Empty contents array') assert( response.result.contents[0].text.includes('Change Stream') || response.result.contents[0].text.includes('Trigger'), 'Triggers info not found' ) } const testStoredFunctionsResource = async () => { const response = await runLensCommand({ command: 'mcp.resource.get', params: { uri: 'mongodb://database/functions' } }) assert(response?.result?.contents, 'No contents in response') assert(Array.isArray(response.result.contents), 'Contents not an array') assert(response.result.contents.length > 0, 'Empty contents array') assert( response.result.contents[0].text.includes('Stored Functions') || response.result.contents[0].text.includes('No stored JavaScript functions'), 'Stored functions title or empty message not found' ) } const testCollectionSchemaResource = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.resource.get', params: { uri: `mongodb://collection/${TEST_COLLECTION_NAME}/schema` } }) assertResourceSuccess(response, `Schema for '${TEST_COLLECTION_NAME}'`) const content = response.result.contents[0].text assert(content.includes('name:'), 'Name field not found') assert(content.includes('value:'), 'Value field not found') assert(content.includes('tags:'), 'Tags field not found') } const testCollectionIndexesResource = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.resource.get', params: { uri: `mongodb://collection/${TEST_COLLECTION_NAME}/indexes` } }) assertResourceSuccess(response, 'Indexes') const content = response.result.contents[0].text assert(content.includes('name_1'), 'Name index not found') assert(content.includes('value_-1'), 'Value index not found') } const testCollectionStatsResource = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.resource.get', params: { uri: `mongodb://collection/${TEST_COLLECTION_NAME}/stats` } }) assertResourceSuccess(response, 'Statistics') assert(response.result.contents[0].text.includes('Document Count'), 'Document count not found') } const testCollectionValidationResource = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.resource.get', params: { uri: `mongodb://collection/${TEST_COLLECTION_NAME}/validation` } }) assert(response?.result?.contents, 'No contents in response') assert(Array.isArray(response.result.contents), 'Contents not an array') assert(response.result.contents.length > 0, 'Empty contents array') assert( response.result.contents[0].text.includes('Collection Validation Rules') || response.result.contents[0].text.includes('does not have any validation rules'), 'Validation rules title or empty message not found' ) } const testServerStatusResource = async () => { const response = await runLensCommand({ command: 'mcp.resource.get', params: { uri: 'mongodb://server/status' } }) assertResourceSuccess(response, 'MongoDB Server Status') assert(response.result.contents[0].text.includes('Version'), 'Version info not found') } const testReplicaStatusResource = async () => { const response = await runLensCommand({ command: 'mcp.resource.get', params: { uri: 'mongodb://server/replica' } }) assert(response?.result?.contents, 'No contents in response') assert(Array.isArray(response.result.contents), 'Contents not an array') assert(response.result.contents.length > 0, 'Empty contents array') assert( response.result.contents[0].text.includes('Replica Set') || response.result.contents[0].text.includes('not available'), 'Replica set info or message not found' ) } const testPerformanceMetricsResource = async () => { try { const originalTimeout = testConfig.requestTimeout testConfig.requestTimeout = 30000 const response = await runLensCommand({ command: 'mcp.resource.get', params: { uri: 'mongodb://server/metrics' } }) testConfig.requestTimeout = originalTimeout assert(response?.result?.contents, 'No contents in response') assert(Array.isArray(response.result.contents), 'Contents not an array') assert(response.result.contents.length > 0, 'Empty contents array') assert( response.result.contents[0].text.includes('MongoDB Performance Metrics') || response.result.contents[0].text.includes('Error getting performance metrics'), 'Performance metrics title or error message not found' ) } catch (error) { console.log(`${COLORS.yellow}Skipping performance metrics test due to complexity: ${error.message}${COLORS.reset}`) stats.skipped++ stats.total-- } } const testQueryBuilderPrompt = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.prompt.start', params: { name: 'query-builder', args: { collection: TEST_COLLECTION_NAME, condition: 'active documents with value greater than 20' } } }) assertPromptSuccess(response, TEST_COLLECTION_NAME) assert(response.result.messages[0].content.text.includes('active documents with value greater than 20'), 'Condition not found') } const testAggregationBuilderPrompt = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.prompt.start', params: { name: 'aggregation-builder', args: { collection: TEST_COLLECTION_NAME, goal: 'calculate average value by active status' } } }) assertPromptSuccess(response, TEST_COLLECTION_NAME) assert(response.result.messages[0].content.text.includes('calculate average value by active status'), 'Goal not found') } const testMongoShellPrompt = async () => { const response = await runLensCommand({ command: 'mcp.prompt.start', params: { name: 'mongo-shell', args: { operation: 'find documents with specific criteria', details: 'I want to find all documents where the value is greater than 10' } } }) assertPromptSuccess(response, 'find documents with specific criteria') assert(response.result.messages[0].content.text.includes('value is greater than 10'), 'Details not found') } const testSqlToMongodbPrompt = async () => { const response = await runLensCommand({ command: 'mcp.prompt.start', params: { name: 'sql-to-mongodb', args: { sqlQuery: 'SELECT * FROM users WHERE age > 25 ORDER BY name ASC LIMIT 10', targetCollection: 'users' } } }) assertPromptSuccess(response, 'SQL query') assert(response.result.messages[0].content.text.includes('SELECT * FROM users'), 'SQL statement not found') } const testSchemaAnalysisPrompt = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.prompt.start', params: { name: 'schema-analysis', args: { collection: TEST_COLLECTION_NAME } } }) assertPromptSuccess(response, TEST_COLLECTION_NAME) assert(response.result.messages[0].content.text.includes('schema'), 'Schema analysis not found') } const testDataModelingPrompt = async () => { const response = await runLensCommand({ command: 'mcp.prompt.start', params: { name: 'data-modeling', args: { useCase: 'E-commerce product catalog', requirements: 'Fast product lookup by category, efficient inventory tracking', existingData: 'Currently using SQL with products and categories tables' } } }) assertPromptSuccess(response, 'E-commerce product catalog') assert(response.result.messages[0].content.text.includes('Fast product lookup'), 'Requirements not found') } const testSchemaVersioningPrompt = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.prompt.start', params: { name: 'schema-versioning', args: { collection: TEST_COLLECTION_NAME, currentSchema: 'Documents with name, value, tags fields', plannedChanges: 'Add a new status field, make tags required', migrationConstraints: 'Zero downtime required' } } }) assertPromptSuccess(response, TEST_COLLECTION_NAME) assert(response.result.messages[0].content.text.includes('schema versioning'), 'Schema versioning not found') } const testMultiTenantDesignPrompt = async () => { const response = await runLensCommand({ command: 'mcp.prompt.start', params: { name: 'multi-tenant-design', args: { tenantIsolation: 'collection', estimatedTenants: '50', sharedFeatures: 'User profiles, product catalog', tenantSpecificFeatures: 'Orders, custom pricing', scalingPriorities: 'Read-heavy, occasional bulk writes' } } }) assertPromptSuccess(response, 'tenant isolation') assert(response.result.messages[0].content.text.includes('collection'), 'Collection isolation not found') } const testIndexRecommendationPrompt = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.prompt.start', params: { name: 'index-recommendation', args: { collection: TEST_COLLECTION_NAME, queryPattern: 'filtering by createdAt within a date range' } } }) assertPromptSuccess(response, TEST_COLLECTION_NAME) assert(response.result.messages[0].content.text.includes('filtering by createdAt'), 'Query pattern not found') } const testQueryOptimizerPrompt = async () => { await useTestDatabase() const response = await runLensCommand({ command: 'mcp.prompt.start', params: { name: 'query-optimizer', args: { collection: TEST_COLLECTION_NAME, query: '{"tags": "tag0", "value": {"$gt": 10}}', performance: 'Currently taking 500ms with 10,000 documents' } } }) assertPromptSuccess(response, TEST_COLLECTION_NAME) assert(response.result.messages[0].content.text.includes('$gt'), 'Query operator not found') } const testSecurityAuditPrompt = async () => { const response = await runLensCommand({ command: 'mcp.prompt.start', params: { name: 'security-audit', args: {} } }) assertPromptSuccess(response, 'security audit') } const testBackupStrategyPrompt = async () => { const response = await runLensCommand({ command: 'mcp.prompt.start', params: { name: 'backup-strategy', args: { databaseSize: '50GB', uptime: '99.9%', rpo: '1 hour', rto: '4 hours' } } }) assertPromptSuccess(response, 'backup') assert(response.result.messages[0].content.text.includes('50GB'), 'Database size not found') } const testMigrationGuidePrompt = async () => { const response = await runLensCommand({ command: 'mcp.prompt.start', params: { name: 'migration-guide', args: { sourceVersion: '4.4', targetVersion: '5.0', features: 'Time series collections, transactions, aggregation' } } }) assertPromptSuccess(response, 'migration') const content = response.result.messages[0].content.text assert(content.includes('4.4'), 'Source version not found') assert(content.includes('5.0'), 'Target version not found') } const testDatabaseHealthCheckPrompt = async () => { try { await useTestDatabase() const testDoc = { name: 'Health Check Test', value: 100, tags: ['test'], isActive: true } await runLensCommand({ command: 'mcp.tool.invoke', params: { name: 'insert-document', args: { collection: TEST_COLLECTION_NAME, document: JSON.stringify(testDoc) } } }) const originalTimeout = testConfig.requestTimeout testConfig.requestTimeout = 45000 console.log(`${COLORS.blue}Running database health check prompt (increased timeout to ${testConfig.requestTimeout/1000}s)${COLORS.reset}`) const response = await runLensCommand({ command: 'mcp.prompt.start', params: { name: 'database-health-check', args: { includePerformance: 'false', includeSchema: 'true', includeSecurity: 'false' } } }) testConfig.requestTimeout = originalTimeout assert(response?.result?.messages, 'No messages in response') assert(Array.isArray(response.result.messages), 'Messages not an array') assert(response.result.messages.length > 0, 'Empty messages array') const promptText = response.result.messages[0].content.text assert( promptText.includes('health') || promptText.includes('Health') || promptText.includes('assessment'), 'Health check content not found' ) } catch (error) { console.log(`${COLORS.yellow}Skipping database health check prompt test due to complexity: ${error.message}${COLORS.reset}`) stats.skipped++ stats.total-- } } const logHeader = (title, margin = 'none') => { if (margin.indexOf('top') !== -1) console.log('') console.log(`${COLORS.cyan}${DIVIDER}${COLORS.reset}`) console.log(`${COLORS.cyan}${title}${COLORS.reset}`) console.log(`${COLORS.cyan}${DIVIDER}${COLORS.reset}`) if (margin.indexOf('bottom') !== -1) console.log('') } const displayTestSummary = () => { logHeader('Test Summary', 'margin:top,bottom') console.log(`${COLORS.white}Total Tests: ${stats.total}${COLORS.reset}`) console.log(`${COLORS.green}Passed: ${stats.passed}${COLORS.reset}`) console.log(`${COLORS.red}Failed: ${stats.failed}${COLORS.reset}`) console.log(`${COLORS.yellow}Skipped: ${stats.skipped}${COLORS.reset}`) if (stats.failed > 0) { console.error(`${COLORS.red}Some tests failed.${COLORS.reset}`) process.exit(1) } else { console.log(`${COLORS.green}All tests passed!${COLORS.reset}`) process.exit(0) } } const cleanup = async () => { await cleanupTestDatabase() await directMongoClient.close() if (lensProcess) { lensProcess.kill('SIGKILL') await new Promise(resolve => lensProcess.on('exit', resolve)) } } const cleanupTestDatabase = async () => { try { await testDb.dropDatabase() console.log(`${COLORS.yellow}Test database cleaned up.${COLORS.reset}`) } catch (err) { console.error(`${COLORS.red}Error cleaning up test database: ${err.message}${COLORS.reset}`) } } process.on('exit', () => { console.log('Exiting test process…') if (lensProcess) { console.log('Shutting down MongoDB Lens server…') lensProcess.kill() } }) let testDb let mongoUri let testCollection let directMongoClient let isReplSet = false let isSharded = false let nextRequestId = 1 let lensProcess = null let responseHandlers = new Map() const testFilters = [] const groupFilters = [] const patternFilters = [] const isDebugging = process.env.DEBUG === 'true' const DIVIDER = '-'.repeat(30) const TEST_DB_NAME = 'mongodb_lens_test' const TEST_COLLECTION_NAME = 'test_collection' const ANOTHER_TEST_COLLECTION = 'another_collection' const MONGODB_LENS_PATH = join(__dirname, 'mongodb-lens.js') const stats = { total: 0, passed: 0, failed: 0, skipped: 0 } const COLORS = { red: '\x1b[31m', reset: '\x1b[0m', blue: '\x1b[34m', cyan: '\x1b[36m', gray: '\x1b[90m', green: '\x1b[32m', white: '\x1b[37m', yellow: '\x1b[33m', magenta: '\x1b[35m', } const testConfig = { requestTimeout: 15000, serverStartupTimeout: 20000, disableTokens: process.env.CONFIG_DISABLE_DESTRUCTIVE_OPERATION_TOKENS === 'true' } const TEST_GROUPS = [ { name: 'Connection Tools', tests: [ { name: 'connect-mongodb Tool', fn: testConnectMongodbTool }, { name: 'connect-original Tool', fn: testConnectOriginalTool }, { name: 'add-connection-alias Tool', fn: testAddConnectionAliasTool }, { name: 'list-connections Tool', fn: testListConnectionsTool } ] }, { name: 'Database Tools', tests: [ { name: 'list-databases Tool', fn: testListDatabasesTool }, { name: 'current-database Tool', fn: testCurrentDatabaseTool }, { name: 'create-database Tool', fn: testCreateDatabaseTool }, { name: 'use-database Tool', fn: testUseDatabaseTool }, { name: 'drop-database Tool', fn: testDropDatabaseTool } ] }, { name: 'User Tools', tests: [ { name: 'create-user Tool', fn: testCreateUserTool }, { name: 'drop-user Tool', fn: testDropUserTool } ] }, { name: 'Collection Tools', tests: [ { name: 'list-collections Tool', fn: testListCollectionsTool }, { name: 'create-collection Tool', fn: testCreateCollectionTool }, { name: 'drop-collection Tool', fn: testDropCollectionTool }, { name: 'rename-collection Tool', fn: testRenameCollectionTool }, { name: 'validate-collection Tool', fn: testValidateCollectionTool } ] }, { name: 'Document Tools', tests: [ { name: 'distinct-values Tool', fn: testDistinctValuesTool }, { name: 'find-documents Tool', fn: testFindDocumentsTool }, { name: 'count-documents Tool', fn: testCountDocumentsTool }, { name: 'insert-document Tool', fn: testInsertDocumentTool }, { name: 'update-document Tool', fn: testUpdateDocumentTool }, { name: 'delete-document Tool', fn: testDeleteDocumentTool } ] }, { name: 'Advanced Tools', tests: [ { name: 'aggregate-data Tool', fn: testAggregateDataTool }, { name: 'create-index Tool', fn: testCreateIndexTool }, { name: 'drop-index Tool', fn: testDropIndexTool }, { name: 'analyze-schema Tool', fn: testAnalyzeSchemaTool }, { name: 'generate-schema-validator Tool', fn: testGenerateSchemaValidatorTool }, { name: 'compare-schemas Tool', fn: testCompareSchemasTool }, { name: 'explain-query Tool', fn: testExplainQueryTool }, { name: 'analyze-query-patterns Tool', fn: testAnalyzeQueryPatternsTool }, { name: 'get-stats Tool', fn: testGetStatsTool }, { name: 'bulk-operations Tool', fn: testBulkOperationsTool }, { name: 'create-timeseries Tool', fn: testCreateTimeseriesCollectionTool }, { name: 'collation-query Tool', fn: testCollationQueryTool }, { name: 'text-search Tool', fn: testTextSearchTool }, { name: 'geo-query Tool', fn: testGeoQueryTool }, { name: 'transaction Tool', fn: testTransactionTool }, { name: 'watch-changes Tool', fn: testWatchChangesTool }, { name: 'gridfs-operation Tool', fn: testGridFSOperationTool }, { name: 'clear-cache Tool', fn: testClearCacheTool }, { name: 'shard-status Tool', fn: testShardStatusTool }, { name: 'export-data Tool', fn: testExportDataTool } ] }, { name: 'Resources', tests: [ { name: 'databases Resource', fn: testDatabasesResource }, { name: 'collections Resource', fn: testCollectionsResource }, { name: 'database-users Resource', fn: testDatabaseUsersResource }, { name: 'database-triggers Resource', fn: testDatabaseTriggersResource }, { name: 'stored-functions Resource', fn: testStoredFunctionsResource }, { name: 'collection-schema Resource', fn: testCollectionSchemaResource }, { name: 'collection-indexes Resource', fn: testCollectionIndexesResource }, { name: 'collection-stats Resource', fn: testCollectionStatsResource }, { name: 'collection-validation Resource', fn: testCollectionValidationResource }, { name: 'server-status Resource', fn: testServerStatusResource }, { name: 'replica-status Resource', fn: testReplicaStatusResource }, { name: 'performance-metrics Resource', fn: testPerformanceMetricsResource } ] }, { name: 'Prompts', tests: [ { name: 'query-builder Prompt', fn: testQueryBuilderPrompt }, { name: 'aggregation-builder Prompt', fn: testAggregationBuilderPrompt }, { name: 'mongo-shell Prompt', fn: testMongoShellPrompt }, { name: 'sql-to-mongodb Prompt', fn: testSqlToMongodbPrompt }, { name: 'schema-analysis Prompt', fn: testSchemaAnalysisPrompt }, { name: 'data-modeling Prompt', fn: testDataModelingPrompt }, { name: 'schema-versioning Prompt', fn: testSchemaVersioningPrompt }, { name: 'multi-tenant-design Prompt', fn: testMultiTenantDesignPrompt }, { name: 'index-recommendation Prompt', fn: testIndexRecommendationPrompt }, { name: 'query-optimizer Prompt', fn: testQueryOptimizerPrompt }, { name: 'security-audit Prompt', fn: testSecurityAuditPrompt }, { name: 'backup-strategy Prompt', fn: testBackupStrategyPrompt }, { name: 'migration-guide Prompt', fn: testMigrationGuidePrompt }, { name: 'database-health-check Prompt', fn: testDatabaseHealthCheckPrompt } ] } ] runTests().catch(err => { console.error(`${COLORS.red}Test runner error: ${err.message}${COLORS.reset}`) if (lensProcess) lensProcess.kill() process.exit(1) })

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/furey/mongodb-lens'

If you have feedback or need assistance with the MCP directory API, please join our Discord server