Skip to main content
Glama
index.ts9.88 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import fs from "fs/promises"; import path from "path"; import { z } from "zod"; // Define memory item structure interface MemoryItem { id: string; content: string; tags: string[]; createdAt: string; } // Main class for memory management class MemoryManager { private memoryPath: string; private memories: MemoryItem[] = []; constructor(storagePath: string) { // Create memory file path this.memoryPath = path.resolve(storagePath, "memories.json"); } // Initialize and load existing memories async initialize(): Promise<void> { try { // Ensure directory exists const dir = path.dirname(this.memoryPath); await fs.mkdir(dir, { recursive: true }); // Try to load existing memories try { const data = await fs.readFile(this.memoryPath, 'utf-8'); this.memories = JSON.parse(data); } catch (err) { // If file doesn't exist or is invalid, start with empty memories this.memories = []; await this.saveMemories(); // Create the initial file } } catch (error) { console.error("Failed to initialize memory storage:", error); throw error; } } // Save memories to disk private async saveMemories(): Promise<void> { try { await fs.writeFile(this.memoryPath, JSON.stringify(this.memories, null, 2), 'utf-8'); } catch (error) { console.error("Failed to save memories:", error); throw error; } } // Create a new memory async storeMemory(content: string, tags: string[] = []): Promise<MemoryItem> { const id = Date.now().toString(36) + Math.random().toString(36).substring(2, 7); const newMemory: MemoryItem = { id, content, tags, createdAt: new Date().toISOString() }; this.memories.push(newMemory); await this.saveMemories(); return newMemory; } // Retrieve memories by search term async searchMemories(searchTerm: string = "", tag?: string): Promise<MemoryItem[]> { let results = this.memories; // Filter by tag if provided if (tag && tag.trim() !== "") { results = results.filter(memory => memory.tags.some(t => t.toLowerCase() === tag.toLowerCase()) ); } // Filter by search term if provided if (searchTerm && searchTerm.trim() !== "") { const term = searchTerm.toLowerCase(); results = results.filter(memory => memory.content.toLowerCase().includes(term) ); } // Sort by date (newest first) return results.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() ); } // Get all memories async getAllMemories(): Promise<MemoryItem[]> { return [...this.memories].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() ); } // Delete a memory by ID async deleteMemory(id: string): Promise<boolean> { const initialLength = this.memories.length; this.memories = this.memories.filter(memory => memory.id !== id); if (this.memories.length !== initialLength) { await this.saveMemories(); return true; } return false; } // Get tags summary async getTagsSummary(): Promise<{tag: string, count: number}[]> { const tagCounts: Record<string, number> = {}; this.memories.forEach(memory => { memory.tags.forEach(tag => { tagCounts[tag] = (tagCounts[tag] || 0) + 1; }); }); return Object.entries(tagCounts) .map(([tag, count]) => ({ tag, count })) .sort((a, b) => b.count - a.count); } } // Start the MCP server async function main() { // Default to current directory if no path provided const storagePath = process.argv[2] || "."; console.error(`Using storage path: ${storagePath}`); // Initialize memory manager const memoryManager = new MemoryManager(storagePath); await memoryManager.initialize(); // Create server const server = new McpServer({ name: "memory-storage", version: "1.0.0" }); // Store memory tool server.tool( "store-memory", "Store a new memory", { content: z.string().describe("The memory content to store"), tags: z.array(z.string()).optional().describe("Optional tags to categorize the memory") }, async ({ content, tags = [] }) => { try { const memory = await memoryManager.storeMemory(content, tags); return { content: [ { type: "text", text: `Memory stored successfully with ID: ${memory.id}` } ] }; } catch (error) { return { isError: true, content: [ { type: "text", text: `Failed to store memory: ${(error as Error).message}` } ] }; } } ); // Search memories tool server.tool( "search-memories", "Search for stored memories", { searchTerm: z.string().optional().describe("Text to search for in memories"), tag: z.string().optional().describe("Filter memories by this tag") }, async ({ searchTerm = "", tag }) => { try { const memories = await memoryManager.searchMemories(searchTerm, tag); if (memories.length === 0) { return { content: [ { type: "text", text: "No memories found matching your criteria." } ] }; } const formattedMemories = memories.map(memory => ` ID: ${memory.id} Date: ${new Date(memory.createdAt).toLocaleString()} Tags: ${memory.tags.length > 0 ? memory.tags.join(", ") : "none"} Content: ${memory.content} --------------------------`).join("\n"); return { content: [ { type: "text", text: `Found ${memories.length} memories:\n${formattedMemories}` } ] }; } catch (error) { return { isError: true, content: [ { type: "text", text: `Failed to search memories: ${(error as Error).message}` } ] }; } } ); // Get all memories tool server.tool( "get-all-memories", "Retrieve all stored memories", {}, async () => { try { const memories = await memoryManager.getAllMemories(); if (memories.length === 0) { return { content: [ { type: "text", text: "No memories have been stored yet." } ] }; } const formattedMemories = memories.map(memory => ` ID: ${memory.id} Date: ${new Date(memory.createdAt).toLocaleString()} Tags: ${memory.tags.length > 0 ? memory.tags.join(", ") : "none"} Content: ${memory.content} --------------------------`).join("\n"); return { content: [ { type: "text", text: `Total memories: ${memories.length}\n${formattedMemories}` } ] }; } catch (error) { return { isError: true, content: [ { type: "text", text: `Failed to retrieve memories: ${(error as Error).message}` } ] }; } } ); // Delete memory tool server.tool( "delete-memory", "Delete a memory by ID", { id: z.string().describe("ID of the memory to delete") }, async ({ id }) => { try { const success = await memoryManager.deleteMemory(id); if (success) { return { content: [ { type: "text", text: `Memory with ID ${id} has been deleted.` } ] }; } else { return { content: [ { type: "text", text: `Memory with ID ${id} was not found.` } ] }; } } catch (error) { return { isError: true, content: [ { type: "text", text: `Failed to delete memory: ${(error as Error).message}` } ] }; } } ); // Get tags summary tool server.tool( "get-tags-summary", "Get a summary of all used tags", {}, async () => { try { const tags = await memoryManager.getTagsSummary(); if (tags.length === 0) { return { content: [ { type: "text", text: "No tags have been used yet." } ] }; } const formattedTags = tags.map(tag => `${tag.tag}: ${tag.count} memories`).join("\n"); return { content: [ { type: "text", text: `Tags summary:\n${formattedTags}` } ] }; } catch (error) { return { isError: true, content: [ { type: "text", text: `Failed to get tags summary: ${(error as Error).message}` } ] }; } } ); // Connect server to stdio transport const transport = new StdioServerTransport(); await server.connect(transport); console.error("Memory MCP Server running on stdio"); } main().catch(error => { console.error("Fatal error in Memory MCP Server:", error); process.exit(1); });

Latest Blog Posts

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/grizzlypeaksoftware/mcp-memory-server'

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