#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
import crypto from "crypto";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Memory storage file path
const MEMORY_FILE = path.join(__dirname, "memories.json");
// In-memory cache
let memories = [];
// Initialize memory storage
async function initializeStorage() {
try {
const data = await fs.readFile(MEMORY_FILE, "utf-8");
memories = JSON.parse(data);
console.error(`Loaded ${memories.length} memories from storage`);
} catch (error) {
if (error.code === "ENOENT") {
// File doesn't exist yet, start with empty array
memories = [];
await saveMemories();
console.error("Initialized new memory storage");
} else {
console.error("Error loading memories:", error);
memories = [];
}
}
}
// Save memories to disk
async function saveMemories() {
try {
await fs.writeFile(MEMORY_FILE, JSON.stringify(memories, null, 2));
} catch (error) {
console.error("Error saving memories:", error);
throw error;
}
}
// Generate unique ID
function generateId() {
return crypto.randomBytes(16).toString("hex");
}
// Create the MCP server
const server = new Server(
{
name: "ai-memory-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
resources: {},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "store_memory",
description:
"Store a new memory or piece of knowledge. Use this to remember important information, facts, preferences, or context for future conversations.",
inputSchema: {
type: "object",
properties: {
content: {
type: "string",
description: "The content to remember",
},
tags: {
type: "array",
items: { type: "string" },
description: "Optional tags to categorize the memory",
},
metadata: {
type: "object",
description: "Optional metadata (key-value pairs)",
},
},
required: ["content"],
},
},
{
name: "search_memories",
description:
"Search through stored memories using keywords or tags. Returns relevant memories that match the search criteria.",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query (searches in content)",
},
tags: {
type: "array",
items: { type: "string" },
description: "Filter by specific tags",
},
limit: {
type: "number",
description: "Maximum number of results to return (default: 10)",
},
},
},
},
{
name: "list_memories",
description:
"List all stored memories with optional filtering by tags.",
inputSchema: {
type: "object",
properties: {
tags: {
type: "array",
items: { type: "string" },
description: "Filter by specific tags",
},
limit: {
type: "number",
description: "Maximum number of results to return (default: 50)",
},
},
},
},
{
name: "delete_memory",
description: "Delete a specific memory by its ID.",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "The ID of the memory to delete",
},
},
required: ["id"],
},
},
{
name: "clear_memories",
description:
"Clear all stored memories. Use with caution as this cannot be undone.",
inputSchema: {
type: "object",
properties: {
confirm: {
type: "boolean",
description: "Must be set to true to confirm deletion",
},
},
required: ["confirm"],
},
},
{
name: "get_memory_stats",
description:
"Get statistics about stored memories (total count, tags, etc.)",
inputSchema: {
type: "object",
properties: {},
},
},
],
};
});
// List available resources
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "memory://all",
mimeType: "application/json",
name: "All Memories",
description: "Complete list of all stored memories",
},
{
uri: "memory://stats",
mimeType: "application/json",
name: "Memory Statistics",
description: "Statistics about stored memories",
},
],
};
});
// Read resources
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const uri = request.params.uri;
if (uri === "memory://all") {
return {
contents: [
{
uri,
mimeType: "application/json",
text: JSON.stringify(memories, null, 2),
},
],
};
}
if (uri === "memory://stats") {
const stats = {
total: memories.length,
tags: [...new Set(memories.flatMap((m) => m.tags || []))],
oldest: memories[0]?.timestamp,
newest: memories[memories.length - 1]?.timestamp,
};
return {
contents: [
{
uri,
mimeType: "application/json",
text: JSON.stringify(stats, null, 2),
},
],
};
}
throw new Error(`Unknown resource: ${uri}`);
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "store_memory": {
const { content, tags = [], metadata = {} } = args;
const memory = {
id: generateId(),
content,
tags,
metadata,
timestamp: new Date().toISOString(),
};
memories.push(memory);
await saveMemories();
return {
content: [
{
type: "text",
text: JSON.stringify(
{
success: true,
message: "Memory stored successfully",
memory,
},
null,
2
),
},
],
};
}
case "search_memories": {
const { query = "", tags = [], limit = 10 } = args;
let results = memories;
// Filter by tags if provided
if (tags.length > 0) {
results = results.filter((m) =>
tags.some((tag) => m.tags?.includes(tag))
);
}
// Search in content if query provided
if (query) {
const lowerQuery = query.toLowerCase();
results = results.filter((m) =>
m.content.toLowerCase().includes(lowerQuery)
);
}
// Apply limit
results = results.slice(0, limit);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
success: true,
count: results.length,
memories: results,
},
null,
2
),
},
],
};
}
case "list_memories": {
const { tags = [], limit = 50 } = args;
let results = memories;
// Filter by tags if provided
if (tags.length > 0) {
results = results.filter((m) =>
tags.some((tag) => m.tags?.includes(tag))
);
}
// Apply limit
results = results.slice(0, limit);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
success: true,
count: results.length,
memories: results,
},
null,
2
),
},
],
};
}
case "delete_memory": {
const { id } = args;
const index = memories.findIndex((m) => m.id === id);
if (index === -1) {
return {
content: [
{
type: "text",
text: JSON.stringify({
success: false,
message: `Memory with ID ${id} not found`,
}),
},
],
};
}
const deleted = memories.splice(index, 1)[0];
await saveMemories();
return {
content: [
{
type: "text",
text: JSON.stringify(
{
success: true,
message: "Memory deleted successfully",
deleted,
},
null,
2
),
},
],
};
}
case "clear_memories": {
const { confirm } = args;
if (!confirm) {
return {
content: [
{
type: "text",
text: JSON.stringify({
success: false,
message: "Must confirm deletion by setting confirm to true",
}),
},
],
};
}
const count = memories.length;
memories = [];
await saveMemories();
return {
content: [
{
type: "text",
text: JSON.stringify({
success: true,
message: `Cleared ${count} memories`,
}),
},
],
};
}
case "get_memory_stats": {
const allTags = memories.flatMap((m) => m.tags || []);
const tagCounts = {};
allTags.forEach((tag) => {
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
});
const stats = {
total: memories.length,
tags: Object.keys(tagCounts).length,
tagCounts,
oldest: memories[0]?.timestamp,
newest: memories[memories.length - 1]?.timestamp,
};
return {
content: [
{
type: "text",
text: JSON.stringify(stats, null, 2),
},
],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
success: false,
error: error.message,
}),
},
],
isError: true,
};
}
});
// Start the server
async function main() {
await initializeStorage();
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("AI Memory MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});