UNPKG

mongodb-memory-bank-mcp

Version:

FIXED: MongoDB Memory Bank MCP with bulletproof error handling, smart operations, and session state management. Eliminates [object Object] errors and user confusion.

196 lines (195 loc) 8.14 kB
/** * Master Project Context Manager * Single source of truth for project detection and consistency * Solves the critical project mixing and inconsistency issues */ import { normalizeProjectName, detectProjectUniversally } from '../utils/project-name-normalizer.js'; /** * Master Project Context Manager * Ensures 100% project consistency across all operations */ export class ProjectContextManager { static instance; currentContext = null; sessionId; constructor() { this.sessionId = this.generateSessionId(); } static getInstance() { if (!ProjectContextManager.instance) { ProjectContextManager.instance = new ProjectContextManager(); } return ProjectContextManager.instance; } /** * Get current project context with automatic restoration * This is the SINGLE function used by ALL tools */ async getCurrentProjectContext(workingDirectory) { // If we have valid current context, return it if (this.currentContext && this.isContextValid(this.currentContext)) { await this.updateLastAccessed(this.currentContext.projectName); return this.currentContext; } // Try to restore from MongoDB persistence const restoredContext = await this.restoreFromPersistence(workingDirectory); if (restoredContext) { this.currentContext = restoredContext; console.log(`[PROJECT-CONTEXT] Restored from persistence: ${restoredContext.projectName}`); return restoredContext; } // Detect new project context const detectedContext = await this.detectProjectContext(workingDirectory); this.currentContext = detectedContext; // Persist for future sessions await this.persistContext(detectedContext); console.log(`[PROJECT-CONTEXT] New context detected: ${detectedContext.projectName}`); return detectedContext; } /** * Explicitly set project context (used by detect_project_context_secure) */ async setProjectContext(projectName, workingDirectory, detectionMethod = 'explicit') { const context = { projectName: normalizeProjectName(projectName), workingDirectory, detectionMethod, confidence: 100, timestamp: new Date(), sessionId: this.sessionId }; this.currentContext = context; await this.persistContext(context); console.log(`[PROJECT-CONTEXT] Explicitly set: ${context.projectName}`); } /** * Validate that operation is using correct project */ async validateProjectContext(expectedProject) { const currentContext = await this.getCurrentProjectContext(); if (expectedProject && expectedProject !== currentContext.projectName) { console.error(`[PROJECT-CONTEXT] Validation failed: expected ${expectedProject}, got ${currentContext.projectName}`); return false; } return true; } /** * Get project name for memory operations */ async getProjectName(workingDirectory) { const context = await this.getCurrentProjectContext(workingDirectory); return context.projectName; } /** * Clear context (for testing or explicit reset) */ clearContext() { this.currentContext = null; console.log(`[PROJECT-CONTEXT] Context cleared`); } // Private implementation methods async detectProjectContext(workingDirectory) { const detection = await detectProjectUniversally(workingDirectory); return { projectName: detection.projectName, workingDirectory: detection.workingDirectory, detectionMethod: detection.detectionMethod, confidence: detection.confidence, timestamp: new Date(), sessionId: this.sessionId }; } isContextValid(context) { // Context is valid if it's from current session and recent const isCurrentSession = context.sessionId === this.sessionId; const isRecent = Date.now() - context.timestamp.getTime() < 24 * 60 * 60 * 1000; // 24 hours return isCurrentSession && isRecent; } async persistContext(context) { try { const { MongoDBConnection } = await import('../../infra/mongodb/connection/mongodb-connection.js'); const db = await MongoDBConnection.getInstance().getDatabase(); const collection = db.collection('project_context'); const persistenceDoc = { ...context, lastAccessed: new Date() }; await collection.replaceOne({ sessionId: this.sessionId }, persistenceDoc, { upsert: true }); console.log(`[PROJECT-CONTEXT] Persisted: ${context.projectName}`); } catch (error) { console.warn(`[PROJECT-CONTEXT] Failed to persist: ${error}`); } } async restoreFromPersistence(workingDirectory) { try { const { MongoDBConnection } = await import('../../infra/mongodb/connection/mongodb-connection.js'); const db = await MongoDBConnection.getInstance().getDatabase(); const collection = db.collection('project_context'); // Try to find context for current session first let doc = await collection.findOne({ sessionId: this.sessionId }); // If no session context, try to find recent context for working directory if (!doc && workingDirectory) { doc = await collection.findOne({ workingDirectory }, { sort: { lastAccessed: -1 } }); } if (!doc) { return null; } // Check if context is still valid (not too old) const age = Date.now() - new Date(doc.lastAccessed).getTime(); if (age > 7 * 24 * 60 * 60 * 1000) { // 7 days console.log(`[PROJECT-CONTEXT] Persisted context too old, ignoring`); return null; } return { projectName: doc.projectName, workingDirectory: doc.workingDirectory, detectionMethod: doc.detectionMethod, confidence: doc.confidence, timestamp: new Date(doc.timestamp), sessionId: this.sessionId // Update to current session }; } catch (error) { console.warn(`[PROJECT-CONTEXT] Failed to restore: ${error}`); return null; } } async updateLastAccessed(projectName) { try { const { MongoDBConnection } = await import('../../infra/mongodb/connection/mongodb-connection.js'); const db = await MongoDBConnection.getInstance().getDatabase(); const collection = db.collection('project_context'); await collection.updateOne({ projectName, sessionId: this.sessionId }, { $set: { lastAccessed: new Date() } }); } catch (error) { // Non-critical, just log console.warn(`[PROJECT-CONTEXT] Failed to update last accessed: ${error}`); } } generateSessionId() { return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } } /** * Convenience function for getting project name in MCP tools * This replaces all the different detection functions */ export async function getProjectName(workingDirectory) { const manager = ProjectContextManager.getInstance(); return manager.getProjectName(workingDirectory); } /** * Convenience function for setting project context */ export async function setProjectContext(projectName, workingDirectory) { const manager = ProjectContextManager.getInstance(); return manager.setProjectContext(projectName, workingDirectory); } /** * Convenience function for validating project context */ export async function validateProjectContext(expectedProject) { const manager = ProjectContextManager.getInstance(); return manager.validateProjectContext(expectedProject); }