Skip to main content
Glama
rest-server.js13.6 kB
#!/usr/bin/env node /** * REST API Server for MCP Self-Learning Server * Provides HTTP/WebSocket access to all learning tools */ import express from 'express'; import cors from 'cors'; import { WebSocketServer } from 'ws'; import { createServer } from 'http'; import path from 'path'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import logger from '../lib/logger.js'; // Import the learning engine and classes import { LearningEngine, KnowledgeSynchronizer, SelfLearningMCPServer } from '../mcp-self-learning-server.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); class MCPRestAPIServer { constructor(options = {}) { this.port = options.port || process.env.MCP_LEARN_PORT || 8765; this.host = options.host || process.env.MCP_LEARN_HOST || 'localhost'; // Initialize Express app this.app = express(); this.server = createServer(this.app); // Initialize MCP server instance this.mcpServer = null; this.isInitialized = false; // WebSocket connections this.wsServer = null; this.wsConnections = new Set(); // Request tracking this.requestCount = 0; this.startTime = Date.now(); this.setupMiddleware(); this.setupRoutes(); this.setupWebSocket(); this.setupErrorHandling(); } setupMiddleware() { // CORS configuration this.app.use(cors({ origin: true, credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'] })); // Body parsing this.app.use(express.json({ limit: '10mb' })); this.app.use(express.urlencoded({ extended: true, limit: '10mb' })); // Request logging this.app.use((req, res, next) => { this.requestCount++; const startTime = Date.now(); res.on('finish', () => { const duration = Date.now() - startTime; logger.debug('API request completed', { method: req.method, url: req.url, status: res.statusCode, duration, userAgent: req.get('User-Agent') }); }); next(); }); } setupRoutes() { // Health check this.app.get('/health', (req, res) => { res.json({ status: 'healthy', uptime: Date.now() - this.startTime, requests: this.requestCount, memory: process.memoryUsage(), mcpServer: this.isInitialized ? 'running' : 'initializing' }); }); // API info this.app.get('/api', (req, res) => { res.json({ name: 'MCP Self-Learning Server REST API', version: '1.0.0', endpoints: { 'GET /health': 'Health check', 'POST /analyze': 'Analyze interaction pattern', 'GET /insights': 'Get learning insights', 'POST /learn': 'Trigger learning cycle', 'GET /export': 'Export knowledge', 'POST /import': 'Import knowledge', 'GET /optimize': 'Get optimization suggestions', 'POST /predict': 'Predict next actions', 'GET /metrics': 'Get performance metrics', 'GET /status': 'Get detailed status', 'WS /ws': 'WebSocket for real-time updates' } }); }); // Learning tools endpoints this.app.post('/analyze', this.handleAnalyzePattern.bind(this)); this.app.get('/insights', this.handleGetInsights.bind(this)); this.app.post('/learn', this.handleTriggerLearning.bind(this)); this.app.get('/export', this.handleExportKnowledge.bind(this)); this.app.post('/import', this.handleImportKnowledge.bind(this)); this.app.get('/optimize', this.handleOptimizeTool.bind(this)); this.app.post('/predict', this.handlePredictNextAction.bind(this)); this.app.get('/metrics', this.handleGetPerformanceMetrics.bind(this)); this.app.get('/status', this.handleGetStatus.bind(this)); // File serving for exports this.app.get('/download/:filename', (req, res) => { const filename = req.params.filename; const filepath = path.join(process.cwd(), filename); res.download(filepath, (err) => { if (err) { logger.warn('File download failed', { filename, error: err.message }); res.status(404).json({ error: 'File not found' }); } }); }); } setupWebSocket() { this.wsServer = new WebSocketServer({ server: this.server, path: '/ws' }); this.wsServer.on('connection', (ws, req) => { this.wsConnections.add(ws); logger.info('WebSocket connection established', { clientIP: req.socket.remoteAddress, connections: this.wsConnections.size }); // Send welcome message ws.send(JSON.stringify({ type: 'welcome', message: 'Connected to MCP Self-Learning Server', timestamp: new Date().toISOString() })); ws.on('close', () => { this.wsConnections.delete(ws); logger.debug('WebSocket connection closed', { connections: this.wsConnections.size }); }); ws.on('error', (error) => { logger.warn('WebSocket error', { error: error.message }); this.wsConnections.delete(ws); }); }); } setupErrorHandling() { // 404 handler this.app.use((req, res) => { res.status(404).json({ error: 'Not Found', message: `Endpoint ${req.method} ${req.path} not found`, availableEndpoints: '/api' }); }); // Error handler this.app.use((err, req, res, next) => { logger.error('API error', { error: err.message, stack: err.stack, url: req.url, method: req.method }); res.status(err.status || 500).json({ error: 'Internal Server Error', message: err.message }); }); } async initialize() { if (this.isInitialized) return; try { // Initialize MCP server instance this.mcpServer = new SelfLearningMCPServer(); // MCP server auto-initializes in constructor this.isInitialized = true; logger.info('MCP server components initialized for REST API'); } catch (error) { logger.error('Failed to initialize MCP server for REST API', { error: error.message }); throw error; } } // API endpoint handlers async handleAnalyzePattern(req, res) { try { const { interaction } = req.body; if (!interaction) { return res.status(400).json({ error: 'Missing interaction data' }); } const result = await this.mcpServer.handleAnalyzePattern({ interaction }); // Notify WebSocket clients this.broadcastToWebSockets({ type: 'pattern_analyzed', data: result, timestamp: new Date().toISOString() }); res.json(result); } catch (error) { logger.error('Pattern analysis failed', { error: error.message }); res.status(500).json({ error: error.message }); } } async handleGetInsights(req, res) { try { const result = await this.mcpServer.handleGetInsights(); res.json(result); } catch (error) { logger.error('Get insights failed', { error: error.message }); res.status(500).json({ error: error.message }); } } async handleTriggerLearning(req, res) { try { const result = await this.mcpServer.handleTriggerLearning(); // Notify WebSocket clients this.broadcastToWebSockets({ type: 'learning_cycle_completed', data: result, timestamp: new Date().toISOString() }); res.json(result); } catch (error) { logger.error('Trigger learning failed', { error: error.message }); res.status(500).json({ error: error.message }); } } async handleExportKnowledge(req, res) { try { const { format = 'json' } = req.query; const result = await this.mcpServer.handleExportKnowledge({ format }); res.json({ ...result, downloadUrl: `/download/${path.basename(result.path)}` }); } catch (error) { logger.error('Export knowledge failed', { error: error.message }); res.status(500).json({ error: error.message }); } } async handleImportKnowledge(req, res) { try { const args = req.body; const result = await this.mcpServer.handleImportKnowledge(args); res.json(result); } catch (error) { logger.error('Import knowledge failed', { error: error.message }); res.status(500).json({ error: error.message }); } } async handleOptimizeTool(req, res) { try { const { tool_name } = req.query; const result = await this.mcpServer.handleOptimizeTool({ tool_name }); res.json(result); } catch (error) { logger.error('Optimize tool failed', { error: error.message }); res.status(500).json({ error: error.message }); } } async handlePredictNextAction(req, res) { try { const args = req.body; const result = await this.mcpServer.handlePredictNextAction(args); res.json(result); } catch (error) { logger.error('Predict next action failed', { error: error.message }); res.status(500).json({ error: error.message }); } } async handleGetPerformanceMetrics(req, res) { try { const { tool_name } = req.query; const result = await this.mcpServer.handleGetPerformanceMetrics({ tool_name }); res.json(result); } catch (error) { logger.error('Get performance metrics failed', { error: error.message }); res.status(500).json({ error: error.message }); } } async handleGetStatus(req, res) { try { const status = { running: this.isInitialized, uptime: Date.now() - this.startTime, requests: this.requestCount, websocketConnections: this.wsConnections.size, memory: process.memoryUsage(), learning: null, persistence: null }; if (this.isInitialized && this.mcpServer) { // Get learning engine status if (this.mcpServer.learningEngine) { status.learning = { active: true, patterns: this.mcpServer.learningEngine.patterns.size, knowledge: this.mcpServer.learningEngine.knowledge.size, cycles: this.mcpServer.learningEngine.metrics.learningCycles, memoryBuffer: this.mcpServer.learningEngine.memoryBuffer.length }; } // Get persistence status status.persistence = { enabled: this.mcpServer.learningEngine?.persistenceEnabled || false, lastSave: this.mcpServer.learningEngine?.lastSave || null }; } res.json(status); } catch (error) { logger.error('Get status failed', { error: error.message }); res.status(500).json({ error: error.message }); } } broadcastToWebSockets(message) { const messageStr = JSON.stringify(message); this.wsConnections.forEach(ws => { if (ws.readyState === ws.OPEN) { try { ws.send(messageStr); } catch (error) { logger.warn('Failed to send WebSocket message', { error: error.message }); this.wsConnections.delete(ws); } } }); } async start() { try { // Initialize MCP components first await this.initialize(); // Start HTTP server await new Promise((resolve, reject) => { this.server.listen(this.port, this.host, (err) => { if (err) reject(err); else resolve(); }); }); logger.info('MCP Self-Learning REST API Server started', { host: this.host, port: this.port, endpoints: `http://${this.host}:${this.port}/api` }); console.log(`🌐 MCP Self-Learning REST API Server`); console.log(` 📍 Listening on: http://${this.host}:${this.port}`); console.log(` 📋 API Info: http://${this.host}:${this.port}/api`); console.log(` 🏥 Health Check: http://${this.host}:${this.port}/health`); console.log(` 🔌 WebSocket: ws://${this.host}:${this.port}/ws`); } catch (error) { logger.error('Failed to start REST API server', { error: error.message }); throw error; } } async stop() { try { // Close WebSocket server if (this.wsServer) { this.wsServer.close(); } // Close HTTP server await new Promise((resolve) => { this.server.close(resolve); }); logger.info('REST API server stopped'); } catch (error) { logger.error('Error stopping REST API server', { error: error.message }); } } } // Start server if run directly async function main() { const server = new MCPRestAPIServer(); // Handle graceful shutdown const shutdown = async (signal) => { logger.info(`Received ${signal}, shutting down REST API server...`); await server.stop(); process.exit(0); }; process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGTERM', () => shutdown('SIGTERM')); try { await server.start(); } catch (error) { logger.error('Failed to start REST API server', { error: error.message }); process.exit(1); } } if (import.meta.url === `file://${process.argv[1]}`) { await main(); } export default MCPRestAPIServer;

Implementation Reference

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/saralegui-solutions/mcp-self-learning-server'

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