Skip to main content
Glama
index.ts9.28 kB
#!/usr/bin/env node /** * Memory MCP v2.0 - Smart monolith MCP server * Brain-inspired memory system with smart context loading */ import { homedir, platform } from 'os'; import { join, dirname } from 'path'; import { existsSync, mkdirSync } from 'fs'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } from '@modelcontextprotocol/sdk/types.js'; import type { DbDriver } from './database/db-driver.js'; import { getDatabase, closeDatabase } from './database/connection.js'; import { memoryStore } from './tools/memory-store.js'; import { memoryRecall } from './tools/memory-recall.js'; import { memoryForget } from './tools/memory-forget.js'; import type { MemoryInput, SearchOptions } from './types/index.js'; /** * Get platform-specific default database path * Ensures consistent location across Claude Desktop, Claude Code, and other clients */ function getDefaultDbPath(): string { const plat = platform(); let dbPath: string; if (plat === 'darwin') { // macOS: ~/.claude-memories/memory.db dbPath = join(homedir(), '.claude-memories', 'memory.db'); } else if (plat === 'win32') { // Windows: %APPDATA%/claude-memories/memory.db dbPath = join( process.env['APPDATA'] || join(homedir(), 'AppData', 'Roaming'), 'claude-memories', 'memory.db' ); } else { // Linux/other: XDG compliant ~/.local/share/claude-memories/memory.db const xdgData = process.env['XDG_DATA_HOME'] || join(homedir(), '.local', 'share'); dbPath = join(xdgData, 'claude-memories', 'memory.db'); } // Ensure directory exists const dbDir = dirname(dbPath); if (!existsSync(dbDir)) { mkdirSync(dbDir, { recursive: true }); } return dbPath; } /** * Configuration from environment or defaults */ const config = { databasePath: process.env['MEMORY_DB_PATH'] || getDefaultDbPath(), defaultTTLDays: parseInt(process.env['DEFAULT_TTL_DAYS'] || '90'), databaseDriver: process.env['MEMORY_DB_DRIVER'] || 'better-sqlite3', }; /** * Initialize MCP server */ const server = new Server( { name: '@whenmoon-afk/memory-mcp', version: '2.3.0', }, { capabilities: { tools: {}, }, } ); // Database instance (initialized in main) let db: DbDriver; /** * Tool definitions */ server.setRequestHandler(ListToolsRequestSchema, () => { return { tools: [ { name: 'memory_store', description: 'Store or update a memory. Provide "id" to update existing memory, omit to create new. Returns standard-formatted memory (no embeddings). Supports fact, entity, relationship, and self memory types.', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Memory ID to update (omit to create new memory)', }, content: { type: 'string', description: 'The memory content to store', }, type: { type: 'string', enum: ['fact', 'entity', 'relationship', 'self'], description: 'Type of memory: fact (discrete info), entity (people/places/things), relationship (connections), self (user preferences)', }, importance: { type: 'number', description: 'Importance score 0-10 (auto-calculated if not provided)', minimum: 0, maximum: 10, }, entities: { type: 'array', items: { type: 'string' }, description: 'Related entity names (auto-extracted if not provided)', }, tags: { type: 'array', items: { type: 'string' }, description: 'Tags for categorization', }, metadata: { type: 'object', description: 'Additional metadata', }, ttl_days: { type: 'number', description: 'Time-to-live in days (null for permanent)', }, expires_at: { type: 'string', description: 'Explicit expiration timestamp (ISO format)', }, provenance: { type: 'object', description: 'Provenance information (source, timestamp, context)', }, }, required: ['content', 'type'], }, }, { name: 'memory_recall', description: 'Search memories with intelligent token-aware loading. Always returns memory index (summaries) plus detailed content that fits within token budget. Skill-pattern design: see what exists, load details selectively.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Natural language search query', }, max_tokens: { type: 'number', description: 'Token budget for response (default: 1000). System auto-allocates: summaries first, then fills remaining budget with full content for top matches.', default: 1000, minimum: 100, maximum: 5000, }, type: { type: 'string', enum: ['fact', 'entity', 'relationship', 'self'], description: 'Optional: Filter by memory type', }, entities: { type: 'array', items: { type: 'string' }, description: 'Optional: Filter by related entities', }, limit: { type: 'number', description: 'Maximum number of results to return (default: 20, max: 50)', default: 20, maximum: 50, }, }, required: ['query'], }, }, { name: 'memory_forget', description: 'Soft-delete a memory by ID. Preserves provenance for audit trail. Memory can be recovered or permanently deleted later.', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Memory ID to forget', }, reason: { type: 'string', description: 'Reason for forgetting (stored in provenance)', }, }, required: ['id'], }, }, ], }; }); /** * Tool execution handler */ server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; switch (name) { case 'memory_store': { const result = await memoryStore(db, args as unknown as MemoryInput); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'memory_recall': { const result = await memoryRecall(db, args as unknown as SearchOptions); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } case 'memory_forget': { const { id, reason } = args as { id: string; reason?: string }; const result = await memoryForget(db, id, reason); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { if (error instanceof McpError) { throw error; } const message = error instanceof Error ? error.message : String(error); throw new McpError(ErrorCode.InternalError, message); } }); /** * Start server */ async function main() { try { // Initialize database console.error('Initializing database...'); db = getDatabase(config.databasePath); console.error('Database initialized successfully'); // Connect to transport const transport = new StdioServerTransport(); await server.connect(transport); // Log startup info to stderr (not stdout, which is used for MCP protocol) console.error('Memory MCP v2.0 server started'); console.error(`Database: ${config.databasePath}`); console.error(`Driver: ${config.databaseDriver}`); } catch (error) { console.error('Failed to start server:', error); throw error; } } /** * Cleanup on exit */ process.on('SIGINT', () => { console.error('Shutting down Memory MCP server...'); closeDatabase(); process.exit(0); }); process.on('SIGTERM', () => { console.error('Shutting down Memory MCP server...'); closeDatabase(); process.exit(0); }); // Start the server main().catch((error) => { console.error('Fatal error:', 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/WhenMoon-afk/claude-memory-mcp'

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