UNPKG

@codai/memorai-mcp

Version:

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

977 lines (972 loc) 43.3 kB
#!/usr/bin/env node /** * MemorAI MCP Unified Server - Production-Ready Implementation * * Consolidated server combining the best features from all 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 * - Simplified configuration and startup */ 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, resolve } from 'path'; import { randomUUID, createHash } from 'crypto'; import { config } from 'dotenv'; import OpenAI from 'openai'; export class MemorAIUnifiedServer { 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) { this.config = { ...config, enableSemanticSearch: config.enableSemanticSearch ?? true, enablePerformanceTracking: config.enablePerformanceTracking ?? true, enableHybridStorage: config.enableHybridStorage ?? true, fallbackStorage: config.fallbackStorage ?? 'json' }; this.dataPath = this.config.cbdPath; // Ensure data directory exists if (!existsSync(this.dataPath)) { mkdirSync(this.dataPath, { recursive: true }); } // Initialize OpenAI client if (this.config.azureOpenAI && this.config.enableSemanticSearch) { 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}`); } else if (this.config.openaiApiKey && this.config.enableSemanticSearch) { this.openai = new OpenAI({ apiKey: this.config.openaiApiKey, }); this.log('info', '🔗 OpenAI initialized (fallback mode)'); } // Initialize MCP Server this.server = new Server({ name: this.config.serverName, version: this.config.version, }, { capabilities: { tools: {}, }, }); this.setupHandlers(); this.loadMemories(); this.log('info', `🚀 ${this.config.serverName} initialized`); } log(level, message, ...args) { const timestamp = new Date().toISOString(); console.error(`[${timestamp}] [${level.toUpperCase()}] ${message}`, ...args); } setupHandlers() { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: '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: '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: '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: '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: 'get_memory', description: 'Get memory by exact structured key', inputSchema: { type: 'object', properties: { structuredKey: { type: 'string', description: 'Exact structured key' } }, required: ['structuredKey'], }, }, { name: '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 'remember': return await this.handleRemember(args); case 'recall': return await this.handleRecall(args); case 'forget': return await this.handleForget(args); case 'context': return await this.handleContext(args); case 'get_memory': return await this.handleGetMemory(args); case '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) { 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() { const memoriesFile = join(this.dataPath, 'memories.json'); if (existsSync(memoriesFile)) { try { const data = readFileSync(memoriesFile, 'utf8'); const memoriesArray = JSON.parse(data); for (const memory of memoriesArray) { this.memories.set(memory.structuredKey, memory); } this.log('info', `Loaded ${this.memories.size} memories from storage`); } catch (error) { this.log('error', 'Failed to load memories:', error); } } } 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() { if (this.isStarted) { return; } try { const transport = new StdioServerTransport(); this.log('info', `🚀 ${this.config.serverName} starting on stdio`); await this.server.connect(transport); this.isStarted = true; 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}`); } catch (error) { this.log('error', 'Failed to start server:', error); throw error; } } async stop() { if (!this.isStarted) { return; } this.saveMemories(); this.log('info', '🛑 MemorAI MCP Server stopped'); this.isStarted = false; } } // Main execution logic async function main() { // Check for help/version first if (process.argv.includes('--help') || process.argv.includes('-h')) { console.log(` MemorAI CBD MCP Server - Published Package Usage: npx -y @codai/memorai-mcp@latest Environment Variables: DOTENV_CONFIG_PATH Path to .env file (default: .env) AZURE_OPENAI_ENDPOINT Azure OpenAI endpoint (required) AZURE_OPENAI_KEY Azure OpenAI API key (required) AZURE_OPENAI_API_VERSION Azure OpenAI API version (required) AZURE_OPENAI_EMBEDDING_ADA_DEPLOYMENT Embedding deployment name (required) MEMORAI_CBD_PATH CBD data directory (default: ./memorai-cbd-data) MEMORAI_LOG_LEVEL Log level (default: info) MEMORAI_CACHE_SIZE Memory cache size (default: 10000) MEMORAI_DIMENSIONS Embedding dimensions (default: 1536) Options: --help, -h Show this help message --version, -v Show version information Examples: # Use with custom .env file DOTENV_CONFIG_PATH="/path/to/.env" npx @codai/memorai-mcp@latest # Use with custom CBD path MEMORAI_CBD_PATH="/path/to/data" npx @codai/memorai-mcp@latest `); process.exit(0); } if (process.argv.includes('--version') || process.argv.includes('-v')) { console.log('@codai/memorai-mcp version 9.0.0'); process.exit(0); } // Simple environment configuration console.error('[INIT] Configuring environment...'); const envPath = process.env.DOTENV_CONFIG_PATH; if (envPath && existsSync(envPath)) { console.error(`[INIT] Loading .env from: ${envPath}`); config({ path: envPath }); } else { console.error('[INIT] Loading default .env...'); config(); } // Build configuration console.error('[INIT] Building server configuration...'); const memoraiCbdPath = process.env.MEMORAI_CBD_PATH || resolve(process.cwd(), 'memorai-cbd-data'); const azureEnvConfig = { endpoint: process.env.AZURE_OPENAI_ENDPOINT, apiKey: process.env.AZURE_OPENAI_KEY, apiVersion: process.env.AZURE_OPENAI_API_VERSION || '2024-02-01', embeddingDeployment: process.env.AZURE_OPENAI_EMBEDDING_ADA_DEPLOYMENT }; const azureConfig = azureEnvConfig.endpoint && azureEnvConfig.apiKey && azureEnvConfig.embeddingDeployment ? { endpoint: azureEnvConfig.endpoint, apiKey: azureEnvConfig.apiKey, apiVersion: azureEnvConfig.apiVersion || '2024-02-01', embeddingDeployment: azureEnvConfig.embeddingDeployment, embeddingModel: 'text-embedding-ada-002' } : undefined; const serverConfig = { serverName: 'MemorAI-CBD-MCP', version: '9.0.0', cbdPath: memoraiCbdPath, logLevel: 'debug', enableSemanticSearch: true, enablePerformanceTracking: true, enableHybridStorage: true, azureOpenAI: azureConfig, fallbackStorage: 'json', embeddingModel: 'text-embedding-ada-002', dimensions: 1536, cacheSize: 10000, maxMemories: 100000, nodeEnv: 'production' }; console.error(`[INIT] Server configured - CBD path: ${memoraiCbdPath}`); // Start the server try { console.error('[INIT] Creating server instance...'); const server = new MemorAIUnifiedServer(serverConfig); console.error('[INIT] Starting MCP server...'); await server.start(); console.error('✅ MemorAI MCP Server running successfully!'); // Handle graceful shutdown process.on('SIGINT', async () => { console.error('[SHUTDOWN] Graceful shutdown initiated...'); await server.stop(); process.exit(0); }); process.on('SIGTERM', async () => { console.error('[SHUTDOWN] Graceful shutdown initiated...'); await server.stop(); process.exit(0); }); } catch (error) { console.error('❌ Failed to start server:', error); process.exit(1); } } // Execute only if this is the main module if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.includes('server-unified.js') || process.argv[1]?.includes('@codai/memorai-mcp')) { console.error('[INIT] Starting unified server...'); main(); } //# sourceMappingURL=server-unified.js.map