UNPKG

cf-memory-mcp

Version:

Best-in-class MCP (Model Context Protocol) server for AI memory storage with MIRIX-Inspired Specialized Memory Types (Core, Episodic, Semantic, Procedural, Resource, Knowledge Vault), Progressive Disclosure, AI-Powered Summaries, Context Window Optimizati

333 lines (290 loc) 11.2 kB
#!/usr/bin/env node /** * CF Memory MCP - Portable MCP Server * * A portable MCP (Model Context Protocol) server for AI memory storage * using Cloudflare infrastructure. This executable connects to a deployed * Cloudflare Worker to provide memory storage capabilities for AI agents. * * Usage: npx cf-memory-mcp * * @author John Lam <johnlam90@gmail.com> * @license MIT */ const https = require('https'); const { URL } = require('url'); const os = require('os'); const process = require('process'); // Configuration const STREAMABLE_HTTP_URL = 'https://cf-memory-mcp.johnlam90.workers.dev/mcp'; const LEGACY_SERVER_URL = 'https://cf-memory-mcp.johnlam90.workers.dev/mcp/message'; const PACKAGE_VERSION = require('../package.json').version; const TIMEOUT_MS = 30000; const CONNECT_TIMEOUT_MS = 10000; // Get API key from environment variable (will be checked later) const API_KEY = process.env.CF_MEMORY_API_KEY; /** * Cross-platform MCP stdio bridge * Handles communication between MCP clients and the Cloudflare Worker */ class CFMemoryMCP { constructor() { this.streamableHttpUrl = STREAMABLE_HTTP_URL; this.legacyServerUrl = LEGACY_SERVER_URL; this.userAgent = `cf-memory-mcp/${PACKAGE_VERSION} (${os.platform()} ${os.arch()}; Node.js ${process.version})`; this.useStreamableHttp = true; // Try Streamable HTTP first // Handle process termination gracefully process.on('SIGINT', () => this.shutdown('SIGINT')); process.on('SIGTERM', () => this.shutdown('SIGTERM')); process.on('uncaughtException', (error) => { this.logError('Uncaught exception:', error); this.shutdown('ERROR'); }); // Set up stdio encoding process.stdin.setEncoding('utf8'); process.stdout.setEncoding('utf8'); this.logDebug('CF Memory MCP server starting...'); this.logDebug(`Streamable HTTP URL: ${this.streamableHttpUrl}`); this.logDebug(`Legacy Server URL: ${this.legacyServerUrl}`); this.logDebug(`User Agent: ${this.userAgent}`); } /** * Log debug messages to stderr (won't interfere with MCP communication) */ logDebug(message) { if (process.env.DEBUG || process.env.MCP_DEBUG) { process.stderr.write(`[DEBUG] ${new Date().toISOString()} ${message}\n`); } } /** * Log error messages to stderr */ logError(message, error = null) { const timestamp = new Date().toISOString(); process.stderr.write(`[ERROR] ${timestamp} ${message}\n`); if (error && error.stack) { process.stderr.write(`[ERROR] ${timestamp} ${error.stack}\n`); } } /** * Start listening for MCP messages on stdin */ async start() { try { // Skip connectivity test in MCP mode - it will be tested when first request is made this.logDebug('Starting MCP message processing...'); await this.processStdio(); } catch (error) { this.logError('Failed to start MCP server:', error); process.exit(1); } } /** * Test connectivity to the Cloudflare Worker */ async testConnectivity() { this.logDebug('Testing connectivity to Cloudflare Worker...'); const testMessage = { jsonrpc: '2.0', id: 'connectivity-test', method: 'initialize', params: { protocolVersion: '2025-03-26', capabilities: { tools: {} }, clientInfo: { name: 'cf-memory-mcp', version: PACKAGE_VERSION } } }; try { // Try Streamable HTTP first const response = await this.makeRequest(testMessage); if (response.error) { throw new Error(`Server responded with error: ${response.error.message}`); } this.logDebug('Connectivity test successful with Streamable HTTP'); } catch (error) { // If Streamable HTTP fails, try legacy endpoint this.logDebug('Streamable HTTP failed, trying legacy endpoint...'); this.useStreamableHttp = false; try { const response = await this.makeRequest(testMessage); if (response.error) { throw new Error(`Server responded with error: ${response.error.message}`); } this.logDebug('Connectivity test successful with legacy endpoint'); } catch (legacyError) { throw new Error(`Cannot connect to CF Memory server: ${error.message} (Legacy also failed: ${legacyError.message})`); } } } /** * Process stdio input/output for MCP communication */ async processStdio() { let buffer = ''; // Handle stdin data for await (const chunk of process.stdin) { buffer += chunk; // Process complete JSON-RPC messages (one per line) let newlineIndex; while ((newlineIndex = buffer.indexOf('\n')) !== -1) { const line = buffer.slice(0, newlineIndex).trim(); buffer = buffer.slice(newlineIndex + 1); if (line) { await this.handleMessage(line); } } } // Process any remaining buffer content if (buffer.trim()) { await this.handleMessage(buffer.trim()); } this.logDebug('Stdin closed, shutting down...'); } /** * Handle a single MCP message */ async handleMessage(messageStr) { try { const message = JSON.parse(messageStr); this.logDebug(`Processing message: ${message.method} (id: ${message.id})`); const response = await this.makeRequest(message); // Send response to stdout process.stdout.write(JSON.stringify(response) + '\n'); } catch (error) { this.logError('Error handling message:', error); // Send error response const errorResponse = { jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error', data: error.message } }; process.stdout.write(JSON.stringify(errorResponse) + '\n'); } } /** * Make HTTP request to the Cloudflare Worker */ async makeRequest(message) { return new Promise((resolve) => { const serverUrl = this.useStreamableHttp ? this.streamableHttpUrl : this.legacyServerUrl; const url = new URL(serverUrl); const postData = JSON.stringify(message); const options = { hostname: url.hostname, port: url.port || 443, path: url.pathname, method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Accept-Encoding': 'identity', 'User-Agent': this.userAgent, 'X-API-Key': API_KEY, 'Content-Length': Buffer.byteLength(postData) }, timeout: TIMEOUT_MS }; const req = https.request(options, (res) => { let body = ''; res.on('data', (chunk) => { body += chunk; }); res.on('end', () => { try { const response = JSON.parse(body); resolve(response); } catch (error) { resolve({ jsonrpc: '2.0', id: message.id || null, error: { code: -32603, message: 'Invalid JSON response from server', data: error.message } }); } }); }); req.on('error', (error) => { resolve({ jsonrpc: '2.0', id: message.id || null, error: { code: -32603, message: 'Network error', data: `Failed to connect to ${serverUrl}: ${error.message}` } }); }); req.on('timeout', () => { req.destroy(); resolve({ jsonrpc: '2.0', id: message.id || null, error: { code: -32603, message: 'Request timeout', data: `Server did not respond within ${TIMEOUT_MS}ms` } }); }); req.write(postData); req.end(); }); } /** * Graceful shutdown */ shutdown(reason = 'UNKNOWN') { this.logDebug(`Shutting down (reason: ${reason})`); process.exit(0); } } // Handle command line arguments if (process.argv.includes('--version') || process.argv.includes('-v')) { console.log(`cf-memory-mcp v${PACKAGE_VERSION}`); process.exit(0); } if (process.argv.includes('--help') || process.argv.includes('-h')) { console.log(` CF Memory MCP v${PACKAGE_VERSION} A portable MCP (Model Context Protocol) server for AI memory storage using Cloudflare infrastructure. Usage: npx cf-memory-mcp Start the MCP server npx cf-memory-mcp --version Show version npx cf-memory-mcp --help Show this help Environment Variables: CF_MEMORY_API_KEY=<key> Your CF Memory API key (required) DEBUG=1 Enable debug logging MCP_DEBUG=1 Enable MCP debug logging For more information, visit: https://github.com/johnlam90/cf-memory-mcp `); process.exit(0); } // Check API key before starting server if (!API_KEY) { console.error('Error: CF_MEMORY_API_KEY environment variable is required'); console.error(''); console.error('Please set your API key:'); console.error(' export CF_MEMORY_API_KEY="your-api-key-here"'); console.error(''); console.error('Or run with:'); console.error(' CF_MEMORY_API_KEY="your-api-key-here" npx cf-memory-mcp'); console.error(''); console.error('Get your API key from: https://cf-memory-mcp.johnlam90.workers.dev'); process.exit(1); } // Start the MCP server const server = new CFMemoryMCP(); server.start().catch(error => { console.error('Failed to start CF Memory MCP server:', error.message); process.exit(1); });