MCP Memory Server

  • src
import { v4 as uuidv4 } from 'uuid'; import { Memory, CreateMemoryInput, UpdateMemoryInput, SearchResult, MemoryType, BuildMemoryStoreInput } from './types.js'; import { MemoryStorage } from './storage.js'; import { MemorySearch } from './search.js'; import fs from 'fs/promises'; import path from 'path'; export class MemoryService { private storage: MemoryStorage; private search: MemorySearch; constructor(memoryDir: string = './memory') { this.storage = new MemoryStorage(memoryDir); this.search = new MemorySearch(this.storage); } /** * Initialize the memory service */ async initialize(): Promise<void> { await this.storage.initialize(); await this.search.initialize(); } /** * Build a new memory store in the specified directory */ async buildMemoryStore(input: BuildMemoryStoreInput): Promise<void> { const { directory, overwrite = false } = input; try { // Check if directory exists try { await fs.access(directory); // If overwrite is false and directory exists, throw an error if (!overwrite) { throw new Error(`Directory ${directory} already exists. Use overwrite: true to force creation.`); } } catch (error) { // Directory doesn't exist, which is fine } // Create the directory structure await fs.mkdir(directory, { recursive: true }); await fs.mkdir(path.join(directory, 'entities'), { recursive: true }); await fs.mkdir(path.join(directory, 'concepts'), { recursive: true }); await fs.mkdir(path.join(directory, 'sessions'), { recursive: true }); // Create initial metadata const metadata = { lastUpdated: new Date().toISOString(), memoryCount: 0, indexVersion: 1 }; await fs.writeFile( path.join(directory, 'metadata.json'), JSON.stringify(metadata, null, 2) ); // Create an empty index file const emptyIndex = { index: {}, memories: {} }; await fs.writeFile( path.join(directory, 'index.json'), JSON.stringify(emptyIndex, null, 2) ); // Create a README file with instructions const readme = `# Memory Store This directory contains memories created by Claude using the MCP Memory Server. ## Directory Structure - /entities: Information about specific people, organizations, or objects - /concepts: Information about abstract ideas, processes, or knowledge - /sessions: Information about specific conversations or meetings - index.json: Search index for efficient memory retrieval - metadata.json: Metadata about the memory store ## File Format Each memory is stored as a markdown file with frontmatter metadata: \`\`\`markdown --- id: "unique-id" title: "Memory Title" type: "entity|concept|session" tags: ["tag1", "tag2"] created: "2023-06-15T14:30:00Z" updated: "2023-06-15T14:30:00Z" related: ["other-memory-id1", "other-memory-id2"] importance: 0.8 --- # Memory Title Content of the memory... \`\`\` Created on: ${new Date().toISOString()} `; await fs.writeFile( path.join(directory, 'README.md'), readme ); } catch (error) { throw new Error(`Failed to build memory store: ${error instanceof Error ? error.message : String(error)}`); } } /** * Create a new memory */ async createMemory(input: CreateMemoryInput): Promise<Memory> { const now = new Date().toISOString(); const memory: Memory = { id: uuidv4(), title: input.title, type: input.type, tags: input.tags || [], created: now, updated: now, related: input.related || [], importance: input.importance || 0.5, content: input.content }; await this.storage.saveMemory(memory); await this.search.addToIndex(memory); return memory; } /** * Get a memory by ID and type */ async getMemory(id: string, type: MemoryType): Promise<Memory | null> { return this.storage.getMemory(id, type); } /** * Update an existing memory */ async updateMemory(input: UpdateMemoryInput): Promise<Memory> { // Get the existing memory const existingMemory = await this.findMemoryById(input.id); if (!existingMemory) { throw new Error(`Memory ${input.id} not found`); } // Update the memory with new values const updatedMemory: Memory = { ...existingMemory, title: input.title || existingMemory.title, tags: input.tags || existingMemory.tags, related: input.related || existingMemory.related, importance: input.importance || existingMemory.importance, content: input.content || existingMemory.content, updated: new Date().toISOString() }; await this.storage.updateMemory(updatedMemory); await this.search.updateIndex(updatedMemory); return updatedMemory; } /** * Delete a memory */ async deleteMemory(id: string): Promise<void> { const memory = await this.findMemoryById(id); if (memory) { await this.storage.deleteMemory(id, memory.type); await this.search.removeFromIndex(id); } } /** * Search memories */ async searchMemories( query: string, options: { types?: MemoryType[], tags?: string[], limit?: number } = {} ): Promise<SearchResult[]> { return this.search.search(query, options); } /** * List memories with optional filtering */ async listMemories( options: { types?: MemoryType[], tags?: string[], limit?: number } = {} ): Promise<SearchResult[]> { return this.search.list(options); } /** * Add tags to a memory */ async addTags(id: string, tags: string[]): Promise<Memory> { const memory = await this.findMemoryById(id); if (!memory) { throw new Error(`Memory ${id} not found`); } // Add new tags (avoiding duplicates) const updatedTags = [...new Set([...memory.tags, ...tags])]; // Update the memory return this.updateMemory({ id, tags: updatedTags }); } /** * Remove tags from a memory */ async removeTags(id: string, tags: string[]): Promise<Memory> { const memory = await this.findMemoryById(id); if (!memory) { throw new Error(`Memory ${id} not found`); } // Remove specified tags const updatedTags = memory.tags.filter(tag => !tags.includes(tag)); // Update the memory return this.updateMemory({ id, tags: updatedTags }); } /** * Create relationships between memories */ async relateMemories(sourceId: string, targetIds: string[]): Promise<Memory> { const memory = await this.findMemoryById(sourceId); if (!memory) { throw new Error(`Memory ${sourceId} not found`); } // Add new relations (avoiding duplicates) const updatedRelations = [...new Set([...memory.related, ...targetIds])]; // Update the memory return this.updateMemory({ id: sourceId, related: updatedRelations }); } /** * Remove relationships between memories */ async unrelateMemories(sourceId: string, targetIds: string[]): Promise<Memory> { const memory = await this.findMemoryById(sourceId); if (!memory) { throw new Error(`Memory ${sourceId} not found`); } // Remove specified relations const updatedRelations = memory.related.filter(id => !targetIds.includes(id)); // Update the memory return this.updateMemory({ id: sourceId, related: updatedRelations }); } /** * Rebuild the search index */ async rebuildIndex(): Promise<void> { await this.search.rebuildIndex(); } /** * Find a memory by ID (searching all types) */ private async findMemoryById(id: string): Promise<Memory | null> { // Try each memory type for (const type of ['entity', 'concept', 'session'] as MemoryType[]) { const memory = await this.storage.getMemory(id, type); if (memory) { return memory; } } return null; } }