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);
});