UNPKG

@genxis/whmrockstar

Version:

🎸 GenXis WHMRockStar - AI-powered multi-server WHM/cPanel management via Model Context Protocol (MCP). Enhanced with proper MCP stdio protocol support and multi-server management.

346 lines (311 loc) • 10.6 kB
#!/usr/bin/env node /** * GenXis WHMRockStar - Simplified MCP Server Implementation * Works with the current MCP configuration and provides proper stdio interface */ const WHMService = require('./lib/whm-service'); const logger = require('./lib/logger'); const fs = require('fs'); const path = require('path'); class WHMRockStarMCPServer { constructor() { this.servers = new Map(); this.loadServerConfigurations(); } loadServerConfigurations() { // Load server configurations from multiple sources const configDir = path.join(process.env.HOME || process.env.USERPROFILE, '.genxis-whmrockstar'); const serversConfigPath = path.join(configDir, 'servers.json'); // Load from servers.json if exists if (fs.existsSync(serversConfigPath)) { try { const serversConfig = JSON.parse(fs.readFileSync(serversConfigPath, 'utf8')); for (const [serverId, config] of Object.entries(serversConfig.servers || {})) { this.addServer(serverId, config); } } catch (error) { logger.warn(`Failed to load servers config: ${error.message}`); } } // Load from environment variables (legacy single server support) if (process.env.WHM_SERVER && process.env.WHM_API_TOKEN) { const serverId = process.env.WHM_SERVER_ID || 'default'; this.addServer(serverId, { server: process.env.WHM_SERVER, apiToken: process.env.WHM_API_TOKEN, port: process.env.WHM_PORT || '2087', name: process.env.WHM_SERVER_NAME || `WHM Server (${process.env.WHM_SERVER})` }); } // Load from legacy config.json const legacyConfigPath = path.join(configDir, 'config.json'); if (fs.existsSync(legacyConfigPath)) { try { const config = JSON.parse(fs.readFileSync(legacyConfigPath, 'utf8')); if (config.server && config.apiToken) { this.addServer('legacy', { ...config, name: config.name || `WHM Server (${config.server})` }); } } catch (error) { logger.warn(`Failed to load legacy config: ${error.message}`); } } if (this.servers.size === 0) { logger.error('No WHM server configurations found. Please run setup first.'); } else { logger.info(`Loaded ${this.servers.size} WHM server configuration(s)`); for (const [serverId, service] of this.servers) { logger.info(` - ${serverId}: ${service.config.name || service.config.server}`); } } } addServer(serverId, config) { try { const whmService = new WHMService(config); this.servers.set(serverId, { service: whmService, config: { ...config, id: serverId } }); logger.info(`Added WHM server: ${serverId} (${config.server})`); } catch (error) { logger.error(`Failed to add server ${serverId}: ${error.message}`); } } getServer(serverId) { if (!serverId && this.servers.size === 1) { // If no server specified and only one server, use it return Array.from(this.servers.values())[0]; } const server = this.servers.get(serverId); if (!server) { const availableServers = Array.from(this.servers.keys()).join(', '); throw new Error(`Server '${serverId}' not found. Available servers: ${availableServers}`); } return server; } async handleMessage(message) { try { const request = JSON.parse(message); if (request.method === 'initialize') { return { jsonrpc: "2.0", id: request.id, result: { protocolVersion: "2024-11-05", capabilities: { tools: {}, resources: {} }, serverInfo: { name: "genxis-whmrockstar", version: require('./package.json').version } } }; } if (request.method === 'tools/list') { return { jsonrpc: "2.0", id: request.id, result: { tools: [ { name: "whm_list_servers", description: "List all configured WHM servers", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "whm_list_accounts", description: "List all cPanel accounts on a WHM server", inputSchema: { type: "object", properties: { serverId: { type: "string", description: "Server ID (optional if only one server configured)" } }, required: [] } }, { name: "whm_server_status", description: "Get server status and system statistics", inputSchema: { type: "object", properties: { serverId: { type: "string", description: "Server ID (optional if only one server configured)" } }, required: [] } }, { name: "whm_call_api", description: "Call any WHM JSON API endpoint (full privileges via token)", inputSchema: { type: "object", properties: { serverId: { type: "string", description: "Server ID (optional if only one server configured)" }, endpoint: { type: "string", description: "WHM JSON API endpoint, e.g., 'listaccts', 'createacct'" }, method: { type: "string", enum: ["get", "post"], description: "HTTP method: get or post (default: get)" }, params: { type: "object", description: "Parameters object passed to WHM endpoint" } }, required: ["endpoint"] } } ] } }; } if (request.method === 'tools/call') { const { name, arguments: args } = request.params; let result; switch (name) { case 'whm_list_servers': result = await this.listServers(); break; case 'whm_list_accounts': result = await this.listAccounts(args?.serverId); break; case 'whm_server_status': result = await this.getServerStatus(args?.serverId); break; default: throw new Error(`Unknown tool: ${name}`); } return { jsonrpc: "2.0", id: request.id, result: { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] } }; } // Unknown method return { jsonrpc: "2.0", id: request.id, error: { code: -32601, message: "Method not found" } }; } catch (error) { logger.error(`Error handling message: ${error.message}`); return { jsonrpc: "2.0", id: request.id || null, error: { code: -32603, message: "Internal error", data: error.message } }; } } async listServers() { const servers = Array.from(this.servers.entries()).map(([id, { config }]) => ({ id, name: config.name || `WHM Server (${config.server})`, server: config.server, port: config.port || '2087' })); return { servers, total: servers.length, timestamp: new Date().toISOString() }; } async listAccounts(serverId) { try { const { service, config } = this.getServer(serverId); const result = await service.listAccounts(); return { server: { id: config.id, name: config.name, address: config.server }, accounts: result.acct || [], total: result.acct?.length || 0, timestamp: new Date().toISOString() }; } catch (error) { throw new Error(`Failed to list accounts: ${error.message}`); } } async getServerStatus(serverId) { try { const { service, config } = this.getServer(serverId); const result = await service.getServerStatus(); return { server: { id: config.id, name: config.name, address: config.server }, status: result, timestamp: new Date().toISOString() }; } catch (error) { throw new Error(`Failed to get server status: ${error.message}`); } } start() { logger.info('WHMRockStar MCP Server started (stdio mode)'); logger.info(`Managing ${this.servers.size} WHM server(s)`); // Handle stdin/stdout communication process.stdin.setEncoding('utf8'); let buffer = ''; process.stdin.on('data', async (chunk) => { buffer += chunk; // Process complete messages (separated by newlines) const lines = buffer.split('\n'); buffer = lines.pop() || ''; // Keep incomplete line in buffer for (const line of lines) { if (line.trim()) { try { const response = await this.handleMessage(line.trim()); process.stdout.write(JSON.stringify(response) + '\n'); } catch (error) { logger.error(`Error processing message: ${error.message}`); } } } }); process.stdin.on('end', () => { logger.info('MCP Server shutting down'); process.exit(0); }); // Send ready notification process.stdout.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + '\n'); } } // Start the server if (require.main === module) { try { const server = new WHMRockStarMCPServer(); server.start(); } catch (error) { logger.error(`Failed to start MCP server: ${error.message}`); process.exit(1); } } module.exports = WHMRockStarMCPServer;