Skip to main content
Glama

Roo Code Memory Bank MCP Server

index.ts11.8 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, } from "@modelcontextprotocol/sdk/types.js"; import chalk from 'chalk'; import fs from 'fs/promises'; // Use promises for async operations import path from 'path'; // import { fileURLToPath } from 'url'; // Removed import.meta.url usage // --- Constants --- const MEMORY_BANK_DIR_NAME = "memory-bank"; // Use process.cwd() which should be the project root when the server is run const BASE_PATH = process.cwd(); const MEMORY_BANK_PATH = path.join(BASE_PATH, MEMORY_BANK_DIR_NAME); const INITIAL_FILES: { [key: string]: string } = { "productContext.md": `# Product Context\n\nThis file provides a high-level overview...\n\n*`, "activeContext.md": `# Active Context\n\nThis file tracks the project's current status...\n\n*`, "progress.md": `# Progress\n\nThis file tracks the project's progress...\n\n*`, "decisionLog.md": `# Decision Log\n\nThis file records architectural and implementation decisions...\n\n*`, "systemPatterns.md": `# System Patterns *Optional*\n\nThis file documents recurring patterns...\n\n*` }; // --- Helper Functions --- function getCurrentTimestamp(): string { return new Date().toISOString().replace('T', ' ').substring(0, 19); } async function ensureMemoryBankDir(): Promise<void> { try { await fs.access(MEMORY_BANK_PATH); } catch (error) { // Directory doesn't exist, create it await fs.mkdir(MEMORY_BANK_PATH, { recursive: true }); console.error(chalk.green(`Created memory bank directory: ${MEMORY_BANK_PATH}`)); } } // --- Tool Definitions --- const INITIALIZE_MEMORY_BANK_TOOL: Tool = { name: "initialize_memory_bank", description: "Creates the memory-bank directory and standard .md files with initial templates.", inputSchema: { type: "object", properties: { project_brief_content: { type: "string", description: "(Optional) Content from projectBrief.md to pre-fill productContext.md" } }, required: [] } // Output: Confirmation message (handled in implementation) }; const CHECK_MEMORY_BANK_STATUS_TOOL: Tool = { name: "check_memory_bank_status", description: "Checks if the memory-bank directory exists and lists the .md files within it.", inputSchema: { type: "object", properties: {} } // No input needed // Output: { exists: boolean, files: string[] } (handled in implementation) }; const READ_MEMORY_BANK_FILE_TOOL: Tool = { name: "read_memory_bank_file", description: "Reads the full content of a specified memory bank file.", inputSchema: { type: "object", properties: { file_name: { type: "string", description: "The name of the memory bank file (e.g., 'productContext.md')" } }, required: ["file_name"] } // Output: { content: string } (handled in implementation) }; const APPEND_MEMORY_BANK_ENTRY_TOOL: Tool = { name: "append_memory_bank_entry", description: "Appends a new, timestamped entry to a specified file, optionally under a specific markdown header.", inputSchema: { type: "object", properties: { file_name: { type: "string", description: "The name of the memory bank file to append to." }, entry: { type: "string", description: "The content of the entry to append." }, section_header: { type: "string", description: "(Optional) The exact markdown header (e.g., '## Decision') to append under." } }, required: ["file_name", "entry"] } // Output: Confirmation message (handled in implementation) }; const ALL_TOOLS = [ INITIALIZE_MEMORY_BANK_TOOL, CHECK_MEMORY_BANK_STATUS_TOOL, READ_MEMORY_BANK_FILE_TOOL, APPEND_MEMORY_BANK_ENTRY_TOOL ]; // --- Server Logic --- class RooMemoryBankServer { async initializeMemoryBank(input: any): Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }> { try { await ensureMemoryBankDir(); let initializationMessages: string[] = []; for (const [fileName, template] of Object.entries(INITIAL_FILES)) { const filePath = path.join(MEMORY_BANK_PATH, fileName); try { await fs.access(filePath); initializationMessages.push(`File ${fileName} already exists.`); } catch { // File doesn't exist, create it let content = template; // Add timestamp to initial content content = content.replace('YYYY-MM-DD HH:MM:SS', getCurrentTimestamp()); // Special handling for project brief in productContext.md if (fileName === "productContext.md" && input?.project_brief_content) { content = content.replace('...', `based on project brief:\n\n${input.project_brief_content}\n\n...`); } await fs.writeFile(filePath, content); initializationMessages.push(`Created file: ${fileName}`); } } return { content: [{ type: "text", text: JSON.stringify({ status: "success", messages: initializationMessages }, null, 2) }] }; } catch (error: any) { console.error(chalk.red("Error initializing memory bank:"), error); return { content: [{ type: "text", text: JSON.stringify({ status: "error", message: error.message }, null, 2) }], isError: true }; } } async checkMemoryBankStatus(): Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }> { try { await fs.access(MEMORY_BANK_PATH); const files = await fs.readdir(MEMORY_BANK_PATH); const mdFiles = files.filter(f => f.endsWith('.md')); return { content: [{ type: "text", text: JSON.stringify({ exists: true, files: mdFiles }, null, 2) }] }; } catch (error) { // If access fails, directory likely doesn't exist return { content: [{ type: "text", text: JSON.stringify({ exists: false, files: [] }, null, 2) }] }; } } async readMemoryBankFile(input: any): Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }> { const fileName = input?.file_name; if (!fileName || typeof fileName !== 'string') { return { content: [{ type: "text", text: JSON.stringify({ status: "error", message: "Missing or invalid 'file_name' parameter." }, null, 2) }], isError: true }; } const filePath = path.join(MEMORY_BANK_PATH, fileName); try { const fileContent = await fs.readFile(filePath, 'utf-8'); return { content: [{ type: "text", text: JSON.stringify({ content: fileContent }, null, 2) }] }; } catch (error: any) { console.error(chalk.red(`Error reading file ${fileName}:`), error); return { content: [{ type: "text", text: JSON.stringify({ status: "error", message: `Failed to read file ${fileName}: ${error.message}` }, null, 2) }], isError: true }; } } async appendMemoryBankEntry(input: any): Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }> { const { file_name: fileName, entry, section_header: sectionHeader } = input; if (!fileName || typeof fileName !== 'string') { return { content: [{ type: "text", text: JSON.stringify({ status: "error", message: "Missing or invalid 'file_name' parameter." }, null, 2) }], isError: true }; } if (!entry || typeof entry !== 'string') { return { content: [{ type: "text", text: JSON.stringify({ status: "error", message: "Missing or invalid 'entry' parameter." }, null, 2) }], isError: true }; } const filePath = path.join(MEMORY_BANK_PATH, fileName); const timestamp = getCurrentTimestamp(); const formattedEntry = `\n[${timestamp}] - ${entry}\n`; try { await ensureMemoryBankDir(); // Ensure directory exists before appending if (sectionHeader && typeof sectionHeader === 'string') { let fileContent = ""; try { fileContent = await fs.readFile(filePath, 'utf-8'); } catch (readError: any) { if (readError.code === 'ENOENT') { // File doesn't exist, create it console.warn(chalk.yellow(`File ${fileName} not found, creating.`)); // Use initial template if available, otherwise just the header and entry const initialTemplate = INITIAL_FILES[fileName] ? INITIAL_FILES[fileName].replace('YYYY-MM-DD HH:MM:SS', timestamp) : ''; fileContent = initialTemplate; } else { throw readError; // Re-throw other read errors } } const headerIndex = fileContent.indexOf(sectionHeader); if (headerIndex !== -1) { // Find the end of the section (next header or end of file) const nextHeaderIndex = fileContent.indexOf('\n##', headerIndex + sectionHeader.length); const insertIndex = (nextHeaderIndex !== -1) ? nextHeaderIndex : fileContent.length; const updatedContent = fileContent.slice(0, insertIndex).trimEnd() + '\n' + formattedEntry.trimStart() + fileContent.slice(insertIndex); await fs.writeFile(filePath, updatedContent); } else { // Header not found, append to the end with the header console.warn(chalk.yellow(`Header "${sectionHeader}" not found in ${fileName}. Appending header and entry to the end.`)); await fs.appendFile(filePath, `\n${sectionHeader}\n${formattedEntry}`); } } else { // No section header, just append to the end await fs.appendFile(filePath, formattedEntry); } return { content: [{ type: "text", text: JSON.stringify({ status: "success", message: `Appended entry to ${fileName}` }, null, 2) }] }; } catch (error: any) { console.error(chalk.red(`Error appending to file ${fileName}:`), error); return { content: [{ type: "text", text: JSON.stringify({ status: "error", message: `Failed to append to file ${fileName}: ${error.message}` }, null, 2) }], isError: true }; } } } // --- Server Setup --- const server = new Server( { name: "roo-memory-bank-mcp-server", version: "0.1.0", // Initial version }, { capabilities: { tools: {}, // Tools are dynamically listed }, } ); const memoryBankServer = new RooMemoryBankServer(); server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: ALL_TOOLS, })); server.setRequestHandler(CallToolRequestSchema, async (request) => { const toolName = request.params.name; const args = request.params.arguments; console.error(chalk.blue(`Received call for tool: ${toolName}`)); // console.error(chalk.gray(`Arguments: ${JSON.stringify(args)}`)); // Optional: Log arguments switch (toolName) { case "initialize_memory_bank": return memoryBankServer.initializeMemoryBank(args); case "check_memory_bank_status": return memoryBankServer.checkMemoryBankStatus(); case "read_memory_bank_file": return memoryBankServer.readMemoryBankFile(args); case "append_memory_bank_entry": return memoryBankServer.appendMemoryBankEntry(args); default: console.error(chalk.red(`Unknown tool requested: ${toolName}`)); return { content: [{ type: "text", text: JSON.stringify({ status: "error", message: `Unknown tool: ${toolName}` }, null, 2) }], isError: true }; } }); async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); console.error(chalk.green("Roo Memory Bank MCP Server running on stdio")); } runServer().catch((error) => { console.error(chalk.red("Fatal error running server:"), error); process.exit(1); });

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/IncomeStreamSurfer/roo-code-memory-bank-mcp-server'

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