UNPKG

mongodocs-mcp

Version:

Lightning-fast semantic search for MongoDB documentation via Model Context Protocol. 10,000+ documents, <500ms search.

148 lines 5.15 kB
/** * MongoDB Atlas Connection Manager */ import { MongoClient } from 'mongodb'; export class MongoDBClient { static instance; client = null; db = null; constructor() { } static getInstance() { if (!MongoDBClient.instance) { MongoDBClient.instance = new MongoDBClient(); } return MongoDBClient.instance; } async connect() { if (this.client && this.db) { return; // Already connected } const uri = process.env.MONGODB_URI; if (!uri) { throw new Error('MONGODB_URI environment variable is not set'); } try { this.client = new MongoClient(uri, { maxPoolSize: 10, minPoolSize: 2, }); await this.client.connect(); await this.client.db('admin').command({ ping: 1 }); const dbName = process.env.MONGODB_DATABASE || 'mongodb_semantic_docs'; this.db = this.client.db(dbName); console.error(`Connected to MongoDB Atlas (database: ${dbName})`); } catch (error) { console.error('Failed to connect to MongoDB:', error); // Clean up on error if (this.client) { await this.client.close().catch(() => { }); this.client = null; this.db = null; } throw error; } } async disconnect() { if (this.client) { await this.client.close(); this.client = null; this.db = null; } } getDatabase() { if (!this.db) { throw new Error('Not connected to MongoDB'); } return this.db; } getVectorsCollection() { const collectionName = process.env.MONGODB_COLLECTION || 'documents'; return this.getDatabase().collection(collectionName); } getDocumentsCollection() { return this.getDatabase().collection('documents'); } getAnalyticsCollection() { return this.getDatabase().collection('analytics'); } async checkConnection() { try { if (!this.client) { return false; } await this.client.db('admin').command({ ping: 1 }); return true; } catch { return false; } } async createVectorSearchIndex() { const collection = this.getVectorsCollection(); // Ensure collection exists try { const collectionName = process.env.MONGODB_COLLECTION || 'documents'; await this.getDatabase().createCollection(collectionName); } catch (error) { // Collection might already exist, that's fine if (error.code !== 48) { // 48 = NamespaceExists console.error('Warning: Could not create collection:', error.message); } } // Check if index exists const indexes = await collection.listSearchIndexes().toArray(); if (!indexes.find((i) => i.name === 'semantic_search')) { console.error('Creating vector search index...'); await collection.createSearchIndex({ name: 'semantic_search', type: 'vectorSearch', definition: { fields: [ { type: 'vector', path: 'embedding', numDimensions: 2048, // DOUBLED for maximum performance! similarity: 'cosine', }, ], }, }); console.error('Vector search index created successfully'); // Wait for index to be ready await this.waitForIndexReady('semantic_search'); } else { console.error('Vector search index already exists'); } } async waitForIndexReady(indexName, maxWaitTime = 60000) { const startTime = Date.now(); const collection = this.getVectorsCollection(); while (Date.now() - startTime < maxWaitTime) { const indexes = await collection.listSearchIndexes().toArray(); const index = indexes.find((i) => i.name === indexName); if (index && index.status === 'READY') { console.error(`Index ${indexName} is ready`); return; } await new Promise(resolve => setTimeout(resolve, 2000)); } throw new Error(`Index ${indexName} did not become ready within ${maxWaitTime}ms`); } async getCollectionStats() { const collection = this.getVectorsCollection(); const [count, stats] = await Promise.all([ collection.countDocuments(), collection.stats(), ]); return { documentCount: count, storageSize: stats.storageSize, indexSize: stats.totalIndexSize, avgDocumentSize: stats.avgObjSize, }; } } //# sourceMappingURL=mongodb-client.js.map