UNPKG

@codai/memorai-mcp

Version:

MemorAI CBD-based MCP Server - High-Performance Vector Memory System

949 lines 43.2 kB
/** * MemorAI Advanced MCP Server - World-Class Implementation * * Combines the best features from both implementations: * - Correct tool names (mcp_memoraimcp_*) for VS Code MCP compatibility * - CBD backend for high-performance and reliability * - HPKV-inspired architecture with structured keys * - Advanced semantic search with OpenAI embeddings * - Performance tracking and analytics * - Hybrid storage with fallback mechanisms */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs'; import { join } from 'path'; import { randomUUID } from 'crypto'; import { createHash } from 'crypto'; import OpenAI from 'openai'; export class MemorAIAdvancedServer { server; config; memories = new Map(); dataPath; isStarted = false; openai; // Performance tracking operationCount = 0; operationTimes = []; startTime = Date.now(); // Memory analytics memoryStats = { totalMemories: 0, uniqueAgents: new Set(), uniqueProjects: new Set(), averageImportance: 0, }; constructor(config) { console.log('[DEBUG] MemorAIAdvancedServer constructor started'); console.log(`[DEBUG] Config received: ${JSON.stringify({ serverName: config.serverName, version: config.version, cbdPath: config.cbdPath, enableSemanticSearch: config.enableSemanticSearch, logLevel: config.logLevel }, null, 2)}`); this.config = { ...config, enableSemanticSearch: config.enableSemanticSearch ?? true, enablePerformanceTracking: config.enablePerformanceTracking ?? true, enableHybridStorage: config.enableHybridStorage ?? true, fallbackStorage: config.fallbackStorage ?? 'json' }; this.dataPath = this.config.cbdPath; console.log(`[DEBUG] Data path set to: ${this.dataPath}`); // Ensure data directory exists try { if (!existsSync(this.dataPath)) { console.log(`[DEBUG] Creating data directory: ${this.dataPath}`); mkdirSync(this.dataPath, { recursive: true }); console.log(`[DEBUG] Data directory created successfully`); } else { console.log(`[DEBUG] Data directory already exists: ${this.dataPath}`); } } catch (error) { console.error(`[ERROR] Failed to create data directory: ${error}`); throw error; } console.log('[DEBUG] Initializing OpenAI client...'); // Initialize Azure OpenAI for embeddings if (this.config.azureOpenAI && this.config.enableSemanticSearch) { try { this.openai = new OpenAI({ apiKey: this.config.azureOpenAI.apiKey, baseURL: `${this.config.azureOpenAI.endpoint}/openai/deployments/${this.config.azureOpenAI.embeddingDeployment}`, defaultQuery: { 'api-version': this.config.azureOpenAI.apiVersion }, defaultHeaders: { 'api-key': this.config.azureOpenAI.apiKey, }, }); this.log('info', `🔗 Azure OpenAI initialized with deployment: ${this.config.azureOpenAI.embeddingDeployment}`); console.log('[DEBUG] Azure OpenAI client initialized successfully'); } catch (error) { console.error(`[ERROR] Failed to initialize Azure OpenAI client: ${error}`); throw error; } } else if (this.config.openaiApiKey && this.config.enableSemanticSearch) { try { // Fallback to regular OpenAI this.openai = new OpenAI({ apiKey: this.config.openaiApiKey, }); this.log('info', '🔗 OpenAI initialized (fallback mode)'); console.log('[DEBUG] OpenAI client (fallback) initialized successfully'); } catch (error) { console.error(`[ERROR] Failed to initialize OpenAI client: ${error}`); throw error; } } else { console.log('[DEBUG] Semantic search disabled or no API keys provided'); } console.log('[DEBUG] Creating MCP Server instance...'); try { this.server = new Server({ name: this.config.serverName, version: this.config.version, }, { capabilities: { tools: {}, }, }); console.log('[DEBUG] MCP Server instance created successfully'); } catch (error) { console.error(`[ERROR] Failed to create MCP Server instance: ${error}`); throw error; } console.log('[DEBUG] Setting up request handlers...'); try { this.setupHandlers(); console.log('[DEBUG] Request handlers setup completed'); } catch (error) { console.error(`[ERROR] Failed to setup request handlers: ${error}`); throw error; } console.log('[DEBUG] Loading existing memories...'); try { this.loadMemories(); console.log('[DEBUG] Memories loaded successfully'); } catch (error) { console.error(`[ERROR] Failed to load memories: ${error}`); throw error; } this.loadMemories(); this.log('info', `🚀 ${this.config.serverName} initialized`); this.log('info', `📁 Data path: ${this.dataPath}`); this.log('info', `🧠 Semantic search: ${this.config.enableSemanticSearch ? 'enabled' : 'disabled'}`); } log(level, message, ...args) { const timestamp = new Date().toISOString(); console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}`, ...args); } setupHandlers() { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'mcp_memoraimcp_remember', description: 'Store a new memory with metadata', inputSchema: { type: 'object', properties: { agentId: { type: 'string', description: 'Agent identifier' }, content: { type: 'string', description: 'Memory content to store' }, metadata: { type: 'object', properties: { entityType: { type: 'string' }, priority: { type: 'string' }, project: { type: 'string' }, session: { type: 'string' }, tags: { type: 'array', items: { type: 'string' } } } } }, required: ['agentId', 'content'], }, }, { name: 'mcp_memoraimcp_recall', description: 'Search and retrieve memories', inputSchema: { type: 'object', properties: { agentId: { type: 'string', description: 'Agent identifier' }, query: { type: 'string', description: 'Search query' }, limit: { type: 'number', description: 'Maximum results', default: 10 }, minImportance: { type: 'number', description: 'Minimum importance score', default: 0 }, project: { type: 'string', description: 'Filter by project' }, session: { type: 'string', description: 'Filter by session' } }, required: ['agentId', 'query'], }, }, { name: 'mcp_memoraimcp_forget', description: 'Delete a memory by structured key', inputSchema: { type: 'object', properties: { agentId: { type: 'string', description: 'Agent identifier' }, structuredKey: { type: 'string', description: 'Structured key of memory to delete' } }, required: ['agentId', 'structuredKey'], }, }, { name: 'mcp_memoraimcp_context', description: 'Get recent context for agent', inputSchema: { type: 'object', properties: { agentId: { type: 'string', description: 'Agent identifier' }, contextSize: { type: 'number', description: 'Number of recent memories', default: 5 } }, required: ['agentId'], }, }, { name: 'mcp_memoraimcp_get_memory', description: 'Get memory by exact structured key', inputSchema: { type: 'object', properties: { structuredKey: { type: 'string', description: 'Exact structured key' } }, required: ['structuredKey'], }, }, { name: 'mcp_memoraimcp_search_keys', description: 'Vector similarity search for memory keys', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Query for finding similar memory keys' }, limit: { type: 'number', description: 'Maximum keys to return', default: 10 }, minScore: { type: 'number', description: 'Minimum similarity score', default: 0.7 } }, required: ['query'], }, } ], }; }); // Handle tool calls this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'mcp_memoraimcp_remember': return await this.handleRemember(args); case 'mcp_memoraimcp_recall': return await this.handleRecall(args); case 'mcp_memoraimcp_forget': return await this.handleForget(args); case 'mcp_memoraimcp_context': return await this.handleContext(args); case 'mcp_memoraimcp_get_memory': return await this.handleGetMemory(args); case 'mcp_memoraimcp_search_keys': return await this.handleSearchKeys(args); default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { this.log('error', `Tool ${name} failed:`, error); throw error; } }); } async handleRemember(args) { const startTime = Date.now(); try { const { agentId, content, metadata = {} } = args; // Generate content hash for duplicate detection const contentHash = createHash('sha256').update(content).digest('hex'); // Check for duplicates const existingMemory = Array.from(this.memories.values()) .find(m => m.contentHash === contentHash && m.metadata.agentId === agentId); if (existingMemory) { // Update access count and return existing memory existingMemory.accessCount++; existingMemory.lastAccessed = new Date().toISOString(); this.saveMemories(); const responseTime = Date.now() - startTime; this.updateMetrics(responseTime); return { content: [{ type: 'text', text: JSON.stringify({ success: true, memoryId: existingMemory.id, structuredKey: existingMemory.structuredKey, isDuplicate: true, message: 'Memory already exists, access updated', metadata: { responseTime: `${responseTime}ms`, serverVersion: this.config.version, operation: 'store_memory' } }, null, 2) }] }; } // Generate structured key: project_date_session_sequence const dateStr = new Date().toISOString().split('T')[0]; const date = dateStr ? dateStr.replace(/-/g, '') : 'unknown'; const project = metadata.project || 'default'; const session = metadata.session || agentId; const sequence = this.getNextSequenceNumber(project, session); const structuredKey = `${project}_${date}_${session}_${sequence}`; // Generate embedding if semantic search is enabled let embedding; let embeddingSummary; if (this.config.enableSemanticSearch && this.openai) { try { const embeddingResponse = await this.openai.embeddings.create({ model: this.config.azureOpenAI?.embeddingModel || this.config.embeddingModel, input: content, }); if (embeddingResponse.data?.[0]?.embedding) { embedding = embeddingResponse.data[0].embedding; embeddingSummary = content.substring(0, 100) + '...'; } } catch (error) { this.log('warn', 'Failed to generate embedding:', error); } } // Calculate importance score const importance = this.calculateImportance(content, metadata); const memory = { id: randomUUID(), content, contentHash, structuredKey, projectName: project, sessionName: session, sequenceNumber: sequence, metadata: { agentId, timestamp: new Date().toISOString(), importance, embeddingSummary, ...metadata }, accessCount: 0, lastAccessed: new Date().toISOString(), embedding, embeddingModel: this.config.embeddingModel }; this.memories.set(memory.structuredKey, memory); this.updateMemoryStats(memory); this.saveMemories(); const responseTime = Date.now() - startTime; this.updateMetrics(responseTime); this.log('info', `📝 Stored memory: ${memory.structuredKey}`); return { content: [{ type: 'text', text: JSON.stringify({ success: true, memoryId: memory.id, structuredKey: memory.structuredKey, projectName: memory.projectName, sessionName: memory.sessionName, sequenceNumber: memory.sequenceNumber, isDuplicate: false, importanceScore: importance, message: 'Memory stored with structured key', metadata: { responseTime: `${responseTime}ms`, serverVersion: this.config.version, operation: 'store_memory', structuredKeyFormat: 'project_date_session_sequence', timestamp: new Date().toISOString(), hasEmbedding: !!embedding } }, null, 2) }] }; } catch (error) { const responseTime = Date.now() - startTime; this.updateMetrics(responseTime); return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : 'Unknown error', operation: 'store_memory', responseTime: `${responseTime}ms`, timestamp: new Date().toISOString() }, null, 2) }] }; } } async handleRecall(args) { const startTime = Date.now(); try { const { agentId, query, limit = 10, minImportance = 0, project, session } = args; let searchResults; // Use semantic search if available and enabled if (this.config.enableSemanticSearch && this.openai) { searchResults = await this.performSemanticSearch(query, { limit, minImportance, project, session, useSemanticSearch: true }, agentId); } else { searchResults = await this.performTextSearch(query, { limit, minImportance, project, session, useSemanticSearch: false }, agentId); } // Generate AI-powered summary const summary = this.generateSearchSummary(searchResults.memories, query); const responseTime = Date.now() - startTime; this.updateMetrics(responseTime); this.log('info', `🔍 Recalled ${searchResults.totalFound} memories for query: ${query}`); return { content: [{ type: 'text', text: JSON.stringify({ success: true, memories: searchResults.memories.map(memory => ({ id: memory.id, content: memory.content, structuredKey: memory.structuredKey, metadata: memory.metadata, relevanceScore: memory.relevanceScore || 0.5, accessCount: memory.accessCount, lastAccessed: memory.lastAccessed, rank: searchResults.memories.indexOf(memory) + 1 })), totalFound: searchResults.totalFound, query, summary, searchOptions: { limit, minImportance, project, session }, message: searchResults.totalFound > 0 ? `Found ${searchResults.totalFound} memories with ${searchResults.searchType} search and relevance ranking.` : 'No memories found matching your search criteria. Try broader terms or check system capabilities.', metadata: { responseTime: `${responseTime}ms`, serverVersion: this.config.version, operation: 'search_memory', searchType: searchResults.searchType, timestamp: new Date().toISOString(), averageRelevance: searchResults.averageRelevance }, systemInfo: searchResults.totalFound === 0 ? await this.getSystemCapabilities() : null }, null, 2) }] }; } catch (error) { const responseTime = Date.now() - startTime; this.updateMetrics(responseTime); return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : 'Unknown error', operation: 'search_memory', responseTime: `${responseTime}ms`, timestamp: new Date().toISOString() }, null, 2) }] }; } } async handleForget(args) { const { agentId, structuredKey } = args; const memory = this.memories.get(structuredKey); if (!memory || memory.metadata.agentId !== agentId) { throw new McpError(ErrorCode.InvalidRequest, `Memory not found: ${structuredKey}`); } this.memories.delete(structuredKey); this.saveMemories(); this.log('info', `Deleted memory: ${structuredKey}`); return { content: [ { type: 'text', text: JSON.stringify({ success: true, message: `Memory ${structuredKey} deleted successfully`, metadata: { responseTime: '1ms', serverVersion: this.config.version, operation: 'delete_memory' } }, null, 2) } ], }; } async handleContext(args) { const { agentId, contextSize = 5 } = args; const recentMemories = Array.from(this.memories.values()) .filter(memory => memory.metadata.agentId === agentId) .sort((a, b) => new Date(b.metadata.timestamp).getTime() - new Date(a.metadata.timestamp).getTime()) .slice(0, contextSize); return { content: [ { type: 'text', text: JSON.stringify({ success: true, context: recentMemories.map(memory => ({ structuredKey: memory.structuredKey, content: memory.content, timestamp: memory.metadata.timestamp, importance: memory.metadata.importance })), contextSize: recentMemories.length, metadata: { responseTime: '1ms', serverVersion: this.config.version, operation: 'get_context' } }, null, 2) } ], }; } async handleGetMemory(args) { const { structuredKey } = args; const memory = this.memories.get(structuredKey); if (!memory) { throw new McpError(ErrorCode.InvalidRequest, `Memory not found: ${structuredKey}`); } return { content: [ { type: 'text', text: JSON.stringify({ success: true, memory: { id: memory.id, content: memory.content, structuredKey: memory.structuredKey, metadata: memory.metadata }, metadata: { responseTime: '1ms', serverVersion: this.config.version, operation: 'get_memory' } }, null, 2) } ], }; } async handleSearchKeys(args) { const { query, limit = 10, minScore = 0.7 } = args; // Simple key matching (in production, this would use vector similarity) const keys = Array.from(this.memories.keys()) .filter(key => key.toLowerCase().includes(query.toLowerCase())) .slice(0, limit) .map(key => ({ key, score: 0.85, // Placeholder score memory: this.memories.get(key) })); return { content: [ { type: 'text', text: JSON.stringify({ success: true, keys: keys.map(item => ({ key: item.key, score: item.score, preview: item.memory?.content.substring(0, 100) + '...' })), totalFound: keys.length, metadata: { responseTime: '1ms', serverVersion: this.config.version, operation: 'search_keys' } }, null, 2) } ], }; } // Helper methods for advanced functionality getNextSequenceNumber(project, session) { const dateStr = new Date().toISOString().split('T')[0]; const today = dateStr ? dateStr.replace(/-/g, '') : 'unknown'; const prefix = `${project}_${today}_${session}_`; const existingKeys = Array.from(this.memories.keys()) .filter(key => key.startsWith(prefix)) .map(key => { const parts = key.split('_'); const lastPart = parts[parts.length - 1]; return lastPart ? parseInt(lastPart) || 0 : 0; }); return existingKeys.length > 0 ? Math.max(...existingKeys) + 1 : 1; } calculateImportance(content, metadata) { let importance = 0.5; // Base importance // Increase importance based on content length (more detailed = more important) if (content.length > 500) importance += 0.1; if (content.length > 1000) importance += 0.1; // Increase importance based on priority metadata if (metadata.priority === 'high') importance += 0.2; if (metadata.priority === 'critical') importance += 0.3; // Increase importance based on entity type if (metadata.entityType === 'plan') importance += 0.15; if (metadata.entityType === 'task') importance += 0.1; if (metadata.entityType === 'decision') importance += 0.2; // Increase importance if it has tags (more structured = more important) if (metadata.tags && metadata.tags.length > 0) importance += 0.05; return Math.min(importance, 1.0); // Cap at 1.0 } async performSemanticSearch(query, options, agentId) { if (!this.openai) { return this.performTextSearch(query, options, agentId); } try { // Generate embedding for the query const queryEmbedding = await this.openai.embeddings.create({ model: this.config.azureOpenAI?.embeddingModel || this.config.embeddingModel, input: query, }); const queryVector = queryEmbedding.data[0]?.embedding; if (!queryVector) { throw new Error('Failed to generate query embedding'); } // Find memories with embeddings and calculate similarity const candidateMemories = Array.from(this.memories.values()) .filter(memory => { if (memory.metadata.agentId !== agentId) return false; if (memory.metadata.importance < options.minImportance) return false; if (options.project && memory.metadata.project !== options.project) return false; if (options.session && memory.metadata.session !== options.session) return false; return memory.embedding !== undefined; }); // Calculate cosine similarity for each memory const memoriesWithScores = candidateMemories.map(memory => ({ ...memory, relevanceScore: this.calculateCosineSimilarity(queryVector, memory.embedding) })); // Sort by relevance score and apply limit const sortedMemories = memoriesWithScores .sort((a, b) => b.relevanceScore - a.relevanceScore) .slice(0, options.limit); // Update access counts sortedMemories.forEach(memory => { const originalMemory = this.memories.get(memory.structuredKey); if (originalMemory) { originalMemory.accessCount++; originalMemory.lastAccessed = new Date().toISOString(); } }); const averageRelevance = sortedMemories.length > 0 ? sortedMemories.reduce((sum, m) => sum + m.relevanceScore, 0) / sortedMemories.length : 0; return { memories: sortedMemories, totalFound: sortedMemories.length, searchType: 'semantic', averageRelevance }; } catch (error) { this.log('warn', 'Semantic search failed, falling back to text search:', error); return this.performTextSearch(query, options, agentId); } } async performTextSearch(query, options, agentId) { const lowerQuery = query.toLowerCase(); const results = Array.from(this.memories.values()) .filter(memory => { if (memory.metadata.agentId !== agentId) return false; if (memory.metadata.importance < options.minImportance) return false; if (options.project && memory.metadata.project !== options.project) return false; if (options.session && memory.metadata.session !== options.session) return false; // Advanced text matching const contentMatch = memory.content.toLowerCase().includes(lowerQuery); const keyMatch = memory.structuredKey.toLowerCase().includes(lowerQuery); const tagMatch = memory.metadata.tags?.some(tag => tag.toLowerCase().includes(lowerQuery)) || false; return contentMatch || keyMatch || tagMatch; }) .map(memory => { // Calculate relevance score based on text matching let score = 0; const content = memory.content.toLowerCase(); const key = memory.structuredKey.toLowerCase(); if (content.includes(lowerQuery)) score += 0.8; if (key.includes(lowerQuery)) score += 0.6; if (memory.metadata.tags?.some(tag => tag.toLowerCase().includes(lowerQuery))) score += 0.4; // Boost score based on importance and recency score += memory.metadata.importance * 0.2; const originalMemory = this.memories.get(memory.structuredKey); if (originalMemory) { originalMemory.accessCount++; originalMemory.lastAccessed = new Date().toISOString(); } return { ...memory, relevanceScore: Math.min(score, 1.0) }; }) .sort((a, b) => b.relevanceScore - a.relevanceScore) .slice(0, options.limit); const averageRelevance = results.length > 0 ? results.reduce((sum, m) => sum + m.relevanceScore, 0) / results.length : 0; return { memories: results, totalFound: results.length, searchType: 'text', averageRelevance }; } calculateCosineSimilarity(a, b) { if (a.length !== b.length) return 0; let dotProduct = 0; let normA = 0; let normB = 0; for (let i = 0; i < a.length; i++) { const aVal = a[i] ?? 0; const bVal = b[i] ?? 0; dotProduct += aVal * bVal; normA += aVal * aVal; normB += bVal * bVal; } const magnitude = Math.sqrt(normA) * Math.sqrt(normB); return magnitude > 0 ? dotProduct / magnitude : 0; } generateSearchSummary(memories, query) { if (memories.length === 0) { return 'No memories found matching your search criteria. Try broader terms or check system capabilities with "memorai help".'; } if (memories.length === 1) { const relevance = Math.round((memories[0]?.relevanceScore || 0.5) * 100); return `Found 1 memory matching "${query}" with ${relevance}% relevance.`; } const avgRelevance = memories.reduce((acc, m) => acc + (m.relevanceScore || 0.5), 0) / memories.length; const topRelevance = Math.max(...memories.map(m => m.relevanceScore || 0.5)); return `Found ${memories.length} memories for "${query}". Top match: ${Math.round(topRelevance * 100)}% relevant. Average relevance: ${Math.round(avgRelevance * 100)}%.`; } updateMemoryStats(memory) { this.memoryStats.totalMemories = this.memories.size; this.memoryStats.uniqueAgents.add(memory.metadata.agentId); if (memory.metadata.project) { this.memoryStats.uniqueProjects.add(memory.metadata.project); } // Recalculate average importance const allMemories = Array.from(this.memories.values()); this.memoryStats.averageImportance = allMemories.length > 0 ? allMemories.reduce((sum, m) => sum + m.metadata.importance, 0) / allMemories.length : 0; } updateMetrics(responseTime) { this.operationCount++; this.operationTimes.push(responseTime); // Keep only last 100 operation times for rolling average if (this.operationTimes.length > 100) { this.operationTimes.shift(); } } getAverageResponseTime() { if (this.operationTimes.length === 0) return 0; return this.operationTimes.reduce((a, b) => a + b, 0) / this.operationTimes.length; } async getSystemCapabilities() { const uptime = Date.now() - this.startTime; return { server: { name: this.config.serverName, version: this.config.version, architecture: 'Advanced CBD + HPKV Hybrid Memory', uptime: `${Math.round(uptime / 1000)}s`, status: 'Operational' }, capabilities: { coreOperations: [ { name: 'store_memory (mcp_memoraimcp_remember)', description: 'Store memories with structured keys: project_date_session_sequence', features: [ 'Automatic key generation', 'Duplicate detection', 'Importance scoring', 'Vector embeddings', 'Performance tracking' ] }, { name: 'search_memory (mcp_memoraimcp_recall)', description: 'Advanced semantic search with AI-powered relevance ranking', features: [ 'Semantic search with embeddings', 'Full-text search fallback', 'Relevance scoring', 'Project/session filtering', 'Access tracking' ] }, { name: 'search_keys (mcp_memoraimcp_search_keys)', description: 'Vector similarity search for related memory keys', features: [ 'Key similarity matching', 'Configurable thresholds', 'Ranked results' ] }, { name: 'get_memory (mcp_memoraimcp_get_memory)', description: 'Direct memory retrieval by structured key', features: [ 'Exact key matching', 'Access tracking', 'Metadata retrieval' ] } ], additionalOperations: [ 'mcp_memoraimcp_forget: Delete specific memories by structured key', 'mcp_memoraimcp_context: Retrieve recent agent context with filtering' ] }, database: { totalMemories: this.memoryStats.totalMemories, uniqueAgents: this.memoryStats.uniqueAgents.size, uniqueProjects: this.memoryStats.uniqueProjects.size, averageImportance: this.memoryStats.averageImportance, version: this.config.version }, performance: { totalOperations: this.operationCount, averageResponseTime: `${Math.round(this.getAverageResponseTime())}ms`, operationsPerSecond: Math.round(this.operationCount / (uptime / 1000) * 100) / 100, uptime: `${Math.round(uptime / 1000)}s`, memoryCount: this.memoryStats.totalMemories, agentCount: this.memoryStats.uniqueAgents.size, databasePath: this.dataPath } }; } loadMemories() { console.log('[DEBUG] loadMemories() called'); const memoriesFile = join(this.dataPath, 'memories.json'); console.log(`[DEBUG] Looking for memories file: ${memoriesFile}`); if (existsSync(memoriesFile)) { console.log('[DEBUG] Memories file exists, loading...'); try { const data = readFileSync(memoriesFile, 'utf8'); console.log(`[DEBUG] Read ${data.length} characters from memories file`); const memoriesArray = JSON.parse(data); console.log(`[DEBUG] Parsed ${memoriesArray.length} memories from JSON`); for (const memory of memoriesArray) { this.memories.set(memory.structuredKey, memory); } this.log('info', `Loaded ${this.memories.size} memories from storage`); console.log(`[DEBUG] Successfully loaded ${this.memories.size} memories`); } catch (error) { console.error(`[ERROR] Failed to load memories: ${error}`); this.log('error', 'Failed to load memories:', error); } } else { console.log('[DEBUG] Memories file does not exist, starting with empty memories'); } } saveMemories() { const memoriesFile = join(this.dataPath, 'memories.json'); try { const memoriesArray = Array.from(this.memories.values()); writeFileSync(memoriesFile, JSON.stringify(memoriesArray, null, 2)); } catch (error) { this.log('error', 'Failed to save memories:', error); } } async start() { console.error('[SERVER] start() method called'); if (this.isStarted) { console.error('[SERVER] Server already started, returning'); return; } console.error('[SERVER] Creating StdioServerTransport...'); try { const transport = new StdioServerTransport(); console.error('[SERVER] StdioServerTransport created successfully'); this.log('info', `🚀 ${this.config.serverName} starting on stdio`); console.error('[SERVER] About to connect server to transport...'); await this.server.connect(transport); console.error('[SERVER] Server connected to transport successfully'); this.isStarted = true; console.error('[SERVER] Server marked as started'); this.log('info', `✅ ${this.config.serverName} running successfully`); this.log('info', ` 📦 Version: ${this.config.version}`); this.log('info', ` 📁 Data Path: ${this.dataPath}`); this.log('info', ` 💾 Loaded Memories: ${this.memories.size}`); console.error('[SERVER] Start method completed successfully'); } catch (error) { console.error(`[SERVER] Failed in start() method: ${error}`); if (error instanceof Error) { console.error(`[SERVER] Error stack: ${error.stack}`); } throw error; } } async stop() { if (!this.isStarted) { return; } this.saveMemories(); this.log('info', '🛑 MemorAI CBD MCP Server stopped'); this.isStarted = false; } } //# sourceMappingURL=cbd-server.js.map