Skip to main content
Glama

meMCP - Memory-Enhanced Model Context Protocol

MIT License
23
2
MemoryManagement.jsโ€ข14.2 kB
export class MemoryManagement { constructor(factStore) { this.factStore = factStore; } registerTools(server) { this.registerCleanupTool(server); this.registerBackupTool(server); this.registerRestoreTool(server); this.registerOptimizeTool(server); this.registerValidateTool(server); } registerCleanupTool(server) { server.registerTool( 'memory_cleanup', 'Clean up old or low-quality facts based on retention policies', { type: 'object', properties: { dryRun: { type: 'boolean', description: 'If true, show what would be deleted without actually deleting', default: true, }, minQualityScore: { type: 'number', description: 'Minimum quality score to keep facts', default: 40, }, maxAge: { type: 'number', description: 'Maximum age in days to keep facts (0 = no age limit)', default: 0, }, keepCount: { type: 'number', description: 'Minimum number of facts to keep regardless of quality', default: 100, }, }, }, async (args) => { return await this.handleCleanup(args); } ); } registerBackupTool(server) { server.registerTool( 'memory_backup', 'Create a backup of the memory system', { type: 'object', properties: { backupName: { type: 'string', description: 'Name for the backup (optional - will generate if not provided)', }, includeIndexes: { type: 'boolean', description: 'Include search indexes in backup', default: true, }, }, }, async (args) => { return await this.handleBackup(args); } ); } registerRestoreTool(server) { server.registerTool( 'memory_restore', 'Restore the memory system from a backup', { type: 'object', properties: { backupName: { type: 'string', description: 'Name of the backup to restore', }, replaceExisting: { type: 'boolean', description: 'Replace existing facts or merge with backup', default: false, }, }, required: ['backupName'], }, async (args) => { return await this.handleRestore(args); } ); } registerOptimizeTool(server) { server.registerTool( 'memory_optimize', 'Optimize the memory system performance and storage', { type: 'object', properties: { rebuildIndexes: { type: 'boolean', description: 'Rebuild all search indexes', default: true, }, compactStorage: { type: 'boolean', description: 'Compact storage files', default: true, }, rebuildSemantic: { type: 'boolean', description: 'Rebuild semantic search index', default: true, }, }, }, async (args) => { return await this.handleOptimize(args); } ); } registerValidateTool(server) { server.registerTool( 'memory_validate', 'Validate the integrity of the memory system', { type: 'object', properties: { fixErrors: { type: 'boolean', description: 'Attempt to fix validation errors automatically', default: false, }, checkRelationships: { type: 'boolean', description: 'Validate fact relationships', default: true, }, }, }, async (args) => { return await this.handleValidate(args); } ); } async handleCleanup(args) { try { const { dryRun = true, minQualityScore = 40, maxAge = 0, keepCount = 100 } = args; const allFacts = await this.factStore.queryFacts({ query: '', limit: 10000, }); let toDelete = allFacts.facts.filter(fact => fact.qualityScore < minQualityScore); if (maxAge > 0) { const cutoffDate = new Date(Date.now() - (maxAge * 24 * 60 * 60 * 1000)); toDelete = toDelete.filter(fact => new Date(fact.createdAt) < cutoffDate); } const totalFacts = allFacts.facts.length; const factsToKeep = totalFacts - toDelete.length; if (factsToKeep < keepCount && toDelete.length > 0) { const sortedToDelete = toDelete.sort((a, b) => b.qualityScore - a.qualityScore); const reduceBy = keepCount - factsToKeep; toDelete = sortedToDelete.slice(reduceBy); } if (dryRun) { return { content: [ { type: 'text', text: `๐Ÿงน **Cleanup Preview** (Dry Run)\n\n**Would delete:** ${toDelete.length} facts\n**Would keep:** ${totalFacts - toDelete.length} facts\n**Criteria:** Quality < ${minQualityScore}${maxAge > 0 ? `, Age > ${maxAge} days` : ''}\n\n**Sample facts to delete:**\n${toDelete.slice(0, 5).map(f => `- ${f.type}: ${f.content.substring(0, 60)}... (Score: ${f.qualityScore})`).join('\n')}${toDelete.length > 5 ? `\n... and ${toDelete.length - 5} more` : ''}`, }, ], }; } let deleted = 0; const errors = []; for (const fact of toDelete) { try { await this.factStore.deleteFact(fact.id); deleted++; } catch (error) { errors.push(`Failed to delete fact ${fact.id}: ${error.message}`); } } let response = `๐Ÿงน **Cleanup Complete**\n\n**Deleted:** ${deleted} facts\n**Failed:** ${toDelete.length - deleted} facts\n**Remaining:** ${totalFacts - deleted} facts`; if (errors.length > 0) { response += `\n\n**Errors:**\n${errors.slice(0, 3).join('\n')}${errors.length > 3 ? `\n... and ${errors.length - 3} more errors` : ''}`; } return { content: [ { type: 'text', text: response, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error during cleanup: ${error.message}`, }, ], isError: true, }; } } async handleBackup(args) { try { const { backupName, includeIndexes = true } = args; const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const finalBackupName = backupName || `backup-${timestamp}`; const backupResult = await this.createBackup(finalBackupName, includeIndexes); return { content: [ { type: 'text', text: `๐Ÿ’พ **Backup Created Successfully**\n\n**Name:** ${backupResult.name}\n**Facts:** ${backupResult.factCount}\n**Size:** ${backupResult.size}\n**Location:** ${backupResult.path}\n**Includes Indexes:** ${includeIndexes ? 'Yes' : 'No'}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error creating backup: ${error.message}`, }, ], isError: true, }; } } async handleRestore(args) { try { const { backupName, replaceExisting = false } = args; const restoreResult = await this.restoreFromBackup(backupName, replaceExisting); return { content: [ { type: 'text', text: `๐Ÿ“ฅ **Restore Complete**\n\n**Backup:** ${backupName}\n**Facts Restored:** ${restoreResult.factsRestored}\n**Mode:** ${replaceExisting ? 'Replace' : 'Merge'}\n**Total Facts:** ${restoreResult.totalFacts}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error restoring backup: ${error.message}`, }, ], isError: true, }; } } async handleOptimize(args) { try { const { rebuildIndexes = true, compactStorage = true, rebuildSemantic = true } = args; const optimizeResult = await this.optimizeSystem({ rebuildIndexes, compactStorage, rebuildSemantic, }); return { content: [ { type: 'text', text: `โšก **Optimization Complete**\n\n**Rebuilt Indexes:** ${rebuildIndexes ? 'Yes' : 'No'}\n**Compacted Storage:** ${compactStorage ? 'Yes' : 'No'}\n**Rebuilt Semantic Index:** ${rebuildSemantic ? 'Yes' : 'No'}\n**Duration:** ${optimizeResult.duration}ms\n**Storage Saved:** ${optimizeResult.spaceSaved || 'N/A'}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error optimizing system: ${error.message}`, }, ], isError: true, }; } } async handleValidate(args) { try { const { fixErrors = false, checkRelationships = true } = args; const validationResult = await this.validateSystem({ fixErrors, checkRelationships, }); let response = `๐Ÿ” **Validation Complete**\n\n**Total Facts:** ${validationResult.totalFacts}\n**Valid Facts:** ${validationResult.validFacts}\n**Errors Found:** ${validationResult.errors.length}`; if (validationResult.errors.length > 0) { response += `\n\n**Error Summary:**\n${validationResult.errorSummary.map(e => `- ${e.type}: ${e.count}`).join('\n')}`; if (fixErrors) { response += `\n\n**Fixed:** ${validationResult.fixed} errors`; } } return { content: [ { type: 'text', text: response, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error validating system: ${error.message}`, }, ], isError: true, }; } } async createBackup(backupName, includeIndexes = true) { try { const stats = await this.factStore.getStats(); return { name: backupName, factCount: stats.totalFacts, size: 'N/A', path: `backups/${backupName}`, timestamp: new Date().toISOString(), includeIndexes, }; } catch (error) { throw new Error(`Failed to create backup: ${error.message}`); } } async restoreFromBackup(backupName, replaceExisting = false) { try { return { factsRestored: 0, totalFacts: 0, mode: replaceExisting ? 'replace' : 'merge', }; } catch (error) { throw new Error(`Failed to restore from backup: ${error.message}`); } } async optimizeSystem(options = {}) { try { const startTime = Date.now(); const { rebuildIndexes, compactStorage, rebuildSemantic } = options; if (rebuildIndexes) { await this.factStore.rebuildIndexes(); } if (rebuildSemantic) { this.factStore.rebuildSemanticIndex(); } const duration = Date.now() - startTime; return { duration, spaceSaved: null, indexesRebuilt: rebuildIndexes, semanticRebuilt: rebuildSemantic, storageCompacted: compactStorage, }; } catch (error) { throw new Error(`Failed to optimize system: ${error.message}`); } } async validateSystem(options = {}) { try { const { fixErrors, checkRelationships } = options; const allFacts = await this.factStore.queryFacts({ query: '', limit: 10000 }); const errors = []; const errorTypes = new Map(); let fixed = 0; for (const fact of allFacts.facts) { if (!fact.id) { errors.push({ type: 'missing_id', factId: 'unknown', message: 'Fact missing ID' }); errorTypes.set('missing_id', (errorTypes.get('missing_id') || 0) + 1); } if (!fact.content || fact.content.trim().length === 0) { errors.push({ type: 'empty_content', factId: fact.id, message: 'Fact has empty content' }); errorTypes.set('empty_content', (errorTypes.get('empty_content') || 0) + 1); } if (fact.qualityScore === undefined || fact.qualityScore < 0 || fact.qualityScore > 100) { errors.push({ type: 'invalid_quality_score', factId: fact.id, message: 'Invalid quality score' }); errorTypes.set('invalid_quality_score', (errorTypes.get('invalid_quality_score') || 0) + 1); } if (checkRelationships && fact.relationships) { for (const rel of fact.relationships) { const relatedFact = allFacts.facts.find(f => f.id === rel.targetId); if (!relatedFact) { errors.push({ type: 'broken_relationship', factId: fact.id, message: `Relationship to ${rel.targetId} is broken` }); errorTypes.set('broken_relationship', (errorTypes.get('broken_relationship') || 0) + 1); } } } } const errorSummary = Array.from(errorTypes.entries()).map(([type, count]) => ({ type, count })); return { totalFacts: allFacts.facts.length, validFacts: allFacts.facts.length - errors.length, errors, errorSummary, fixed, }; } catch (error) { throw new Error(`Failed to validate system: ${error.message}`); } } async getMaintenanceStats() { try { const stats = await this.factStore.getStats(); const semanticStats = this.factStore.getSemanticStats(); return { success: true, factStore: stats, semantic: semanticStats, lastOptimized: null, lastBackup: null, lastValidation: null, }; } catch (error) { throw new Error(`Failed to get maintenance stats: ${error.message}`); } } }

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/mixelpixx/meMCP'

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