Skip to main content
Glama

Mnemosyne MCP

by MumuTW
index.js14.2 kB
#!/usr/bin/env node /** * Mnemosyne MCP Server Launcher * * This Node.js launcher handles Python environment setup and server execution * for the Mnemosyne Model Context Protocol server. */ import { spawn } from 'child_process'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import fs from 'fs'; import chalk from 'chalk'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); class MnemosyneMCPLauncher { constructor() { this.pythonPath = null; this.serverPath = join(__dirname, 'python', 'mcp_server.py'); this.debug = process.env.DEBUG === 'true' || process.argv.includes('--debug'); } log(message, type = 'info') { const timestamp = new Date().toISOString().slice(11, 23); const prefix = chalk.gray(`[${timestamp}]`); switch (type) { case 'info': console.log(`${prefix} ${chalk.blue('ℹ')} ${message}`); break; case 'success': console.log(`${prefix} ${chalk.green('✅')} ${message}`); break; case 'warning': console.log(`${prefix} ${chalk.yellow('⚠️')} ${message}`); break; case 'error': console.error(`${prefix} ${chalk.red('❌')} ${message}`); break; case 'debug': if (this.debug) { console.log(`${prefix} ${chalk.magenta('🔍')} ${chalk.dim(message)}`); } break; } } async findPython() { const candidates = [ 'python3', 'python', 'py', '/usr/bin/python3', '/usr/local/bin/python3', '/opt/homebrew/bin/python3', process.env.VIRTUAL_ENV ? join(process.env.VIRTUAL_ENV, 'bin', 'python3') : null, process.platform === 'win32' ? 'py.exe' : null ].filter(Boolean); this.log('Searching for Python interpreter...', 'debug'); for (const cmd of candidates) { try { const version = await this.execCommand(cmd, ['--version']); this.log(`Testing ${cmd}: ${version}`, 'debug'); // 檢查是否為 Python 3.9+ const match = version.match(/Python (\d+)\.(\d+)/); if (match) { const [, major, minor] = match; if (parseInt(major) === 3 && parseInt(minor) >= 9) { this.pythonPath = cmd; this.log(`Found compatible Python: ${cmd} (${version})`, 'success'); return true; } } } catch (error) { this.log(`${cmd} not available: ${error.message}`, 'debug'); continue; } } return false; } async checkPythonDependencies() { this.log('Checking Python dependencies...', 'debug'); const requiredPackages = [ 'fastmcp', 'pydantic', 'grpcio', 'structlog' ]; const missingPackages = []; for (const pkg of requiredPackages) { try { await this.execCommand(this.pythonPath, ['-c', `import ${pkg}`]); this.log(`${pkg}: available`, 'debug'); } catch (error) { this.log(`${pkg}: missing`, 'debug'); missingPackages.push(pkg); } } if (missingPackages.length > 0) { this.log(`Missing Python packages: ${missingPackages.join(', ')}`, 'warning'); return false; } this.log('All Python dependencies available', 'success'); return true; } async installPythonDeps() { // 尋找 pyproject.toml(當前目錄或父目錄) const currentDir = __dirname; const parentDir = join(__dirname, '..'); let projectRoot, pyprojectPath; if (fs.existsSync(join(currentDir, 'pyproject.toml'))) { projectRoot = currentDir; pyprojectPath = join(currentDir, 'pyproject.toml'); } else if (fs.existsSync(join(parentDir, 'pyproject.toml'))) { projectRoot = parentDir; pyprojectPath = join(parentDir, 'pyproject.toml'); } else { this.log('pyproject.toml not found, falling back to manual install', 'warning'); await this.fallbackInstall(); return; } this.log('Installing Python dependencies from pyproject.toml...', 'info'); try { // 嘗試使用 uv (推薦的現代工具) try { const output = await this.execCommand('uv', [ 'pip', 'install', '-e', projectRoot, '--user' ]); this.log('Dependencies installed successfully with uv', 'success'); this.log(output, 'debug'); return; } catch (uvError) { this.log('uv not available, falling back to pip', 'debug'); } // 回退到標準 pip 安裝,先嘗試當前 Python,再嘗試系統 Python let installSuccess = false; try { const output = await this.execCommand(this.pythonPath, [ '-m', 'pip', 'install', '-e', projectRoot, '--user' ]); this.log('Dependencies installed successfully with pip', 'success'); this.log(output, 'debug'); installSuccess = true; } catch (pipError) { this.log('Current Python pip failed, trying system Python...', 'debug'); // 嘗試系統 Python const systemPythons = ['/usr/bin/python3', '/opt/homebrew/bin/python3']; for (const sysPython of systemPythons) { try { await this.execCommand('test', ['-f', sysPython]); const output = await this.execCommand(sysPython, [ '-m', 'pip', 'install', '-e', projectRoot, '--user' ]); this.log(`Dependencies installed successfully with system Python: ${sysPython}`, 'success'); this.log(output, 'debug'); installSuccess = true; break; } catch (sysError) { this.log(`System Python ${sysPython} failed: ${sysError.message}`, 'debug'); continue; } } } if (installSuccess) return; } catch (error) { // 嘗試不使用 --user 標誌 try { await this.execCommand(this.pythonPath, [ '-m', 'pip', 'install', '-e', projectRoot ]); this.log('Dependencies installed successfully', 'success'); } catch (fallbackError) { throw new Error(`Failed to install dependencies: ${fallbackError.message}`); } } } async fallbackInstall() { // 只在找不到 pyproject.toml 時才使用的後備安裝方法 this.log('Installing minimal MCP dependencies...', 'info'); const minimalPackages = [ 'fastmcp>=0.1.0', // 與 pyproject.toml 保持一致 'pydantic>=2.5.0', 'grpcio>=1.60.0', 'structlog>=23.2.0' ]; try { for (const pkg of minimalPackages) { await this.execCommand(this.pythonPath, [ '-m', 'pip', 'install', pkg, '--user' ]); this.log(`Installed ${pkg}`, 'debug'); } this.log('Minimal dependencies installed successfully', 'success'); } catch (error) { throw new Error(`Failed to install minimal dependencies: ${error.message}`); } } async createPythonServer() { // 如果 Python 伺服器不存在,創建一個基本的 if (!fs.existsSync(this.serverPath)) { this.log('Creating Python MCP server...', 'info'); const serverCode = `#!/usr/bin/env python3 """ Mnemosyne MCP Server Entry Point """ import sys import os import asyncio from pathlib import Path # 添加專案路徑到 Python 路徑 project_root = Path(__file__).parent.parent / "src" sys.path.insert(0, str(project_root)) async def main(): try: from mnemosyne.mcp_adapter.server import create_mcp_server from mnemosyne.core.config import get_settings # 獲取配置 settings = get_settings() # 創建並啟動 MCP 伺服器 server = await create_mcp_server(settings) # 啟動伺服器 (stdio transport) server.run() except ImportError as e: print(f"Import error: {e}", file=sys.stderr) print("Please ensure Mnemosyne is properly installed", file=sys.stderr) sys.exit(1) except Exception as e: print(f"Server error: {e}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": asyncio.run(main()) `; fs.writeFileSync(this.serverPath, serverCode, 'utf8'); this.log('Python server created', 'success'); } } async checkDependencies() { this.log('Checking system dependencies...', 'info'); // 檢查 Python if (!await this.findPython()) { this.log('Python 3.9+ is required but not found', 'error'); this.log('Please install Python from https://python.org', 'info'); this.log('Or using your system package manager:', 'info'); this.log(' macOS: brew install python3', 'info'); this.log(' Ubuntu: sudo apt install python3 python3-pip', 'info'); this.log(' Windows: Download from python.org', 'info'); process.exit(1); } // 檢查 Python 包 const dependenciesOk = await this.checkPythonDependencies(); if (!dependenciesOk) { if (this.healthCheckMode) { this.log('Some Python dependencies missing (but continuing in health check mode)', 'warning'); } else { this.log('Missing Python dependencies detected', 'warning'); this.log('Please ensure the Mnemosyne backend is running:', 'info'); this.log(' 1. Git clone the Mnemosyne project', 'info'); this.log(' 2. Run: make deploy', 'info'); this.log(' 3. Ensure services are healthy at http://localhost:8000/health', 'info'); this.log('', 'info'); this.log('The MCP server will attempt to connect to the backend...', 'info'); } } // 確保 Python 伺服器存在 await this.createPythonServer(); } async startServer() { this.log('Starting Mnemosyne MCP Server...', 'info'); const env = { ...process.env, PYTHONPATH: join(__dirname, 'src'), GRPC_BACKEND_URL: process.env.GRPC_BACKEND_URL || 'localhost:50051', MCP_DEBUG: this.debug ? 'true' : 'false' }; this.log(`Python path: ${this.pythonPath}`, 'debug'); this.log(`Server path: ${this.serverPath}`, 'debug'); this.log(`Environment: ${JSON.stringify(env, null, 2)}`, 'debug'); const serverProcess = spawn(this.pythonPath, [this.serverPath], { stdio: ['inherit', 'inherit', 'inherit'], env }); serverProcess.on('error', (error) => { this.log(`Failed to start server: ${error.message}`, 'error'); process.exit(1); }); serverProcess.on('exit', (code, signal) => { if (signal) { this.log(`Server terminated by signal: ${signal}`, 'warning'); } else if (code !== 0) { this.log(`Server exited with code ${code}`, 'error'); process.exit(code); } }); // 優雅關閉處理 const shutdown = (signal) => { this.log(`Received ${signal}, shutting down server...`, 'warning'); serverProcess.kill('SIGINT'); setTimeout(() => { this.log('Force killing server process', 'warning'); serverProcess.kill('SIGKILL'); process.exit(1); }, 5000); }; process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGTERM', () => shutdown('SIGTERM')); } async execCommand(command, args, options = {}) { return new Promise((resolve, reject) => { const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'], ...options }); let stdout = ''; let stderr = ''; child.stdout?.on('data', (data) => { stdout += data.toString(); }); child.stderr?.on('data', (data) => { stderr += data.toString(); }); child.on('close', (code) => { if (code === 0) { resolve(stdout.trim()); } else { reject(new Error(stderr.trim() || `Command failed with code ${code}`)); } }); child.on('error', reject); }); } async handleArguments() { const args = process.argv.slice(2); if (args.includes('--help') || args.includes('-h')) { this.showHelp(); process.exit(0); } if (args.includes('--version') || args.includes('-v')) { const packageJson = JSON.parse( fs.readFileSync(join(__dirname, 'package.json'), 'utf8') ); console.log(packageJson.version); process.exit(0); } if (args.includes('--health-check')) { await this.healthCheck(); process.exit(0); } } showHelp() { const help = ` ${chalk.blue('Mnemosyne MCP Server')} Usage: mnemosyne-mcp-server [options] Options: -h, --help Show this help message -v, --version Show version number --debug Enable debug logging --health-check Check system health and exit Environment Variables: GRPC_BACKEND_URL gRPC backend URL (default: localhost:50051) DEBUG Enable debug mode (true/false) Examples: mnemosyne-mcp-server # Start the server mnemosyne-mcp-server --debug # Start with debug logging mnemosyne-mcp-server --health-check # Check system status For more information, visit: https://github.com/MumuTW/mnemosyne-mcp `; console.log(help); } async healthCheck() { this.log('Running health check...', 'info'); try { // 在健康檢查模式下,只檢查不安裝 this.healthCheckMode = true; await this.checkDependencies(); this.log('Health check passed - system ready', 'success'); } catch (error) { this.log(`Health check failed: ${error.message}`, 'error'); process.exit(1); } } async run() { try { // 處理命令列參數 await this.handleArguments(); // 檢查依賴 await this.checkDependencies(); // 啟動伺服器 await this.startServer(); } catch (error) { this.log(`Failed to start Mnemosyne MCP: ${error.message}`, 'error'); if (this.debug) { console.error(error.stack); } process.exit(1); } } } // 主程式入口 if (import.meta.url === `file://${process.argv[1]}`) { const launcher = new MnemosyneMCPLauncher(); launcher.run().catch(console.error); } export default MnemosyneMCPLauncher;

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/MumuTW/Mnemosyne-mcp'

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