#!/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;