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.

967 lines (894 loc) • 33.6 kB
#!/usr/bin/env node /** * GenXis WHMRockStar - Proper MCP Server Implementation * Standard stdio-based MCP server for seamless integration */ const WHMService = require('./lib/whm-service'); const MultiServerManager = require('./lib/multi-server-manager'); const FileManager = require('./lib/file-manager'); const SSHManager = require('./lib/ssh-manager'); const logger = require('./lib/logger'); const path = require('path'); const fs = require('fs'); const crypto = require('crypto'); // Dynamic imports for ES modules let McpServer, StdioServerTransport, z; async function loadModules() { const mcpModule = await import("@modelcontextprotocol/sdk/server/index.js"); const stdioModule = await import("@modelcontextprotocol/sdk/server/stdio.js"); const zodModule = await import("zod"); McpServer = mcpModule.Server; StdioServerTransport = stdioModule.StdioServerTransport; z = zodModule.z; } class WHMRockStarMCPServer { constructor() { this.servers = new Map(); this.server = null; } async initialize() { await loadModules(); this.loadServerConfigurations(); // Create MCP server this.server = new McpServer({ name: "genxis-whmrockstar", version: require('./package.json').version }); this.setupTools(); // this.setupResources(); // REMOVED: not supported in current MCP SDK } audit(event) { try { const dir = path.join(process.env.HOME || process.env.USERPROFILE, '.genxis-whmrockstar'); fs.mkdirSync(dir, { recursive: true }); const safe = { ...event }; if (safe.params) { // no raw params in logs; only store digest safe.paramsDigest = crypto.createHash('sha256').update(JSON.stringify(safe.params)).digest('hex'); delete safe.params; } fs.appendFileSync(path.join(dir, 'audit.log'), JSON.stringify({ ts: new Date().toISOString(), ...safe }) + '\n'); } catch (e) { // ignore audit failures } } 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}`); } } getServerList() { return Array.from(this.servers.entries()).map(([id, { config }]) => ({ id, name: config.name || `WHM Server (${config.server})`, server: config.server, port: config.port || '2087' })); } 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; } setupTools() { // Zod schemas for each MCP method const whmListServersRequestSchema = z.object({ method: z.literal('whm_list_servers') }); const whmListServersResultSchema = z.object({ servers: z.array(z.object({ id: z.string(), name: z.string(), server: z.string(), port: z.string() })), total: z.number(), timestamp: z.string() }); // Server management handler this.server.setRequestHandler(whmListServersRequestSchema, async (request) => { const servers = this.getServerList(); this.audit({ tool: 'whm_list_servers', ok: true, total: servers.length }); return { servers, total: servers.length, timestamp: new Date().toISOString() }; }); // Account management schemas const whmListAccountsRequestSchema = z.object({ method: z.literal('whm_list_accounts'), serverId: z.string().optional() }); const whmListAccountsResultSchema = z.object({ server: z.object({ id: z.string(), name: z.string(), address: z.string() }), accounts: z.array(z.any()), total: z.number(), timestamp: z.string() }); // List accounts handler this.server.setRequestHandler(whmListAccountsRequestSchema, async ({ serverId }) => { try { const { service, config } = this.getServer(serverId); const result = await service.listAccounts(); const total = result.acct?.length || 0; this.audit({ tool: 'whm_list_accounts', ok: true, serverId: config.id, total }); return { server: { id: config.id, name: config.name, address: config.server }, accounts: result.acct || [], total: total, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to list accounts: ${error.message}`); this.audit({ tool: 'whm_list_accounts', ok: false, serverId, error: error.message }); throw new Error(`Error listing accounts: ${error.message}`); } }); // File management schemas const fileReadRequestSchema = z.object({ method: z.literal('file_read'), serverId: z.string().optional(), cpanelUser: z.string(), filePath: z.string() }); const fileWriteRequestSchema = z.object({ method: z.literal('file_write'), serverId: z.string().optional(), cpanelUser: z.string(), filePath: z.string(), content: z.string() }); const fileListRequestSchema = z.object({ method: z.literal('file_list'), serverId: z.string().optional(), cpanelUser: z.string(), dirPath: z.string().optional() }); const fileDeleteRequestSchema = z.object({ method: z.literal('file_delete'), serverId: z.string().optional(), cpanelUser: z.string(), filePath: z.string() }); const fileConfigCheckRequestSchema = z.object({ method: z.literal('file_config_check'), serverId: z.string().optional(), cpanelUser: z.string() }); // File operation handlers this.server.setRequestHandler(fileReadRequestSchema, async ({ serverId, cpanelUser, filePath }) => { try { const { config } = this.getServer(serverId); const fileManager = new FileManager(config); const content = await fileManager.readFile(cpanelUser, filePath); this.audit({ tool: 'file_read', ok: true, serverId: config.id, cpanelUser, filePath }); return { server: config.id, filePath, content, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to read file: ${error.message}`); this.audit({ tool: 'file_read', ok: false, serverId, cpanelUser, filePath, error: error.message }); throw new Error(`Error reading file: ${error.message}`); } }); this.server.setRequestHandler(fileWriteRequestSchema, async ({ serverId, cpanelUser, filePath, content }) => { try { const { config } = this.getServer(serverId); const fileManager = new FileManager(config); await fileManager.writeFile(cpanelUser, filePath, content); this.audit({ tool: 'file_write', ok: true, serverId: config.id, cpanelUser, filePath }); return { server: config.id, filePath, success: true, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to write file: ${error.message}`); this.audit({ tool: 'file_write', ok: false, serverId, cpanelUser, filePath, error: error.message }); throw new Error(`Error writing file: ${error.message}`); } }); this.server.setRequestHandler(fileListRequestSchema, async ({ serverId, cpanelUser, dirPath }) => { try { const { config } = this.getServer(serverId); const fileManager = new FileManager(config); const files = await fileManager.listDirectory(cpanelUser, dirPath); this.audit({ tool: 'file_list', ok: true, serverId: config.id, cpanelUser, dirPath }); return { server: config.id, dirPath: dirPath || `/home/${cpanelUser}`, files, total: files.length, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to list directory: ${error.message}`); this.audit({ tool: 'file_list', ok: false, serverId, cpanelUser, dirPath, error: error.message }); throw new Error(`Error listing directory: ${error.message}`); } }); this.server.setRequestHandler(fileDeleteRequestSchema, async ({ serverId, cpanelUser, filePath }) => { try { const { config } = this.getServer(serverId); const fileManager = new FileManager(config); await fileManager.deleteFile(cpanelUser, filePath); this.audit({ tool: 'file_delete', ok: true, serverId: config.id, cpanelUser, filePath }); return { server: config.id, filePath, success: true, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to delete file: ${error.message}`); this.audit({ tool: 'file_delete', ok: false, serverId, cpanelUser, filePath, error: error.message }); throw new Error(`Error deleting file: ${error.message}`); } }); this.server.setRequestHandler(fileConfigCheckRequestSchema, async ({ serverId, cpanelUser }) => { try { const { config } = this.getServer(serverId); const fileManager = new FileManager(config); const issues = await fileManager.findConfigIssues(cpanelUser); this.audit({ tool: 'file_config_check', ok: true, serverId: config.id, cpanelUser, issuesFound: issues.length }); return { server: config.id, cpanelUser, issues, total: issues.length, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to check config: ${error.message}`); this.audit({ tool: 'file_config_check', ok: false, serverId, cpanelUser, error: error.message }); throw new Error(`Error checking config: ${error.message}`); } }); // SSH management schemas const sshSetupRequestSchema = z.object({ method: z.literal('ssh_setup'), serverId: z.string().optional() }); const sshCommandRequestSchema = z.object({ method: z.literal('ssh_command'), serverId: z.string().optional(), command: z.string() }); const sshFileReadRequestSchema = z.object({ method: z.literal('ssh_file_read'), serverId: z.string().optional(), filePath: z.string() }); const sshFileWriteRequestSchema = z.object({ method: z.literal('ssh_file_write'), serverId: z.string().optional(), filePath: z.string(), content: z.string() }); const sshServiceRequestSchema = z.object({ method: z.literal('ssh_service'), serverId: z.string().optional(), action: z.enum(['restart', 'status', 'stop', 'start']), service: z.string() }); const sshSystemCheckRequestSchema = z.object({ method: z.literal('ssh_system_check'), serverId: z.string().optional() }); // SSH operation handlers this.server.setRequestHandler(sshSetupRequestSchema, async ({ serverId }) => { try { const { config } = this.getServer(serverId); const sshManager = new SSHManager({ ...config, serverId: config.id }); await sshManager.installSSHKey(); this.audit({ tool: 'ssh_setup', ok: true, serverId: config.id }); return { server: config.id, message: 'SSH key installed successfully', timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to setup SSH: ${error.message}`); this.audit({ tool: 'ssh_setup', ok: false, serverId, error: error.message }); throw new Error(`Error setting up SSH: ${error.message}`); } }); this.server.setRequestHandler(sshCommandRequestSchema, async ({ serverId, command }) => { try { const { config } = this.getServer(serverId); const sshManager = new SSHManager({ ...config, serverId: config.id }); const result = await sshManager.executeCommand(command); this.audit({ tool: 'ssh_command', ok: true, serverId: config.id, command }); return { server: config.id, command, output: result.output, error: result.error, exitCode: result.code, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to execute SSH command: ${error.message}`); this.audit({ tool: 'ssh_command', ok: false, serverId, command, error: error.message }); throw new Error(`Error executing SSH command: ${error.message}`); } }); this.server.setRequestHandler(sshFileReadRequestSchema, async ({ serverId, filePath }) => { try { const { config } = this.getServer(serverId); const sshManager = new SSHManager({ ...config, serverId: config.id }); const content = await sshManager.readFile(filePath); this.audit({ tool: 'ssh_file_read', ok: true, serverId: config.id, filePath }); return { server: config.id, filePath, content, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to read file via SSH: ${error.message}`); this.audit({ tool: 'ssh_file_read', ok: false, serverId, filePath, error: error.message }); throw new Error(`Error reading file via SSH: ${error.message}`); } }); this.server.setRequestHandler(sshFileWriteRequestSchema, async ({ serverId, filePath, content }) => { try { const { config } = this.getServer(serverId); const sshManager = new SSHManager({ ...config, serverId: config.id }); await sshManager.writeFile(filePath, content); this.audit({ tool: 'ssh_file_write', ok: true, serverId: config.id, filePath }); return { server: config.id, filePath, success: true, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to write file via SSH: ${error.message}`); this.audit({ tool: 'ssh_file_write', ok: false, serverId, filePath, error: error.message }); throw new Error(`Error writing file via SSH: ${error.message}`); } }); this.server.setRequestHandler(sshServiceRequestSchema, async ({ serverId, action, service }) => { try { const { config } = this.getServer(serverId); const sshManager = new SSHManager({ ...config, serverId: config.id }); let result; if (action === 'restart') { result = await sshManager.restartService(service); } else { const cmd = `systemctl ${action} ${service}`; result = await sshManager.executeCommand(cmd); } this.audit({ tool: 'ssh_service', ok: true, serverId: config.id, action, service }); return { server: config.id, service, action, success: result.success !== false, output: result.output, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to manage service via SSH: ${error.message}`); this.audit({ tool: 'ssh_service', ok: false, serverId, action, service, error: error.message }); throw new Error(`Error managing service via SSH: ${error.message}`); } }); this.server.setRequestHandler(sshSystemCheckRequestSchema, async ({ serverId }) => { try { const { config } = this.getServer(serverId); const sshManager = new SSHManager({ ...config, serverId: config.id }); const issues = await sshManager.findSystemConfigIssues(); this.audit({ tool: 'ssh_system_check', ok: true, serverId: config.id, issuesFound: issues.length }); return { server: config.id, issues, total: issues.length, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to check system via SSH: ${error.message}`); this.audit({ tool: 'ssh_system_check', ok: false, serverId, error: error.message }); throw new Error(`Error checking system via SSH: ${error.message}`); } }); // whm_create_account schemas const whmCreateAccountRequestSchema = z.object({ method: z.literal('whm_create_account'), serverId: z.string().optional(), username: z.string(), domain: z.string(), password: z.string(), email: z.string(), package: z.string().optional() }); const whmCreateAccountResultSchema = z.object({ server: z.object({ id: z.string(), name: z.string(), address: z.string() }), operation: z.string(), success: z.boolean(), result: z.any(), timestamp: z.string() }); // Create account handler this.server.setRequestHandler(whmCreateAccountRequestSchema, async ({ serverId, username, domain, password, email, package: pkg }) => { try { const { service, config } = this.getServer(serverId); const result = await service.createAccount({ username, domain, password, email, package: pkg }); return { server: { id: config.id, name: config.name, address: config.server }, operation: "create_account", success: result.status === 1, result, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to create account: ${error.message}`); throw new Error(`Error creating account: ${error.message}`); } }); // whm_account_summary schemas const whmAccountSummaryRequestSchema = z.object({ method: z.literal('whm_account_summary'), serverId: z.string().optional(), username: z.string() }); const whmAccountSummaryResultSchema = z.object({ server: z.object({ id: z.string(), name: z.string(), address: z.string() }), username: z.string(), summary: z.any(), timestamp: z.string() }); // Get account summary handler this.server.setRequestHandler(whmAccountSummaryRequestSchema, async ({ serverId, username }) => { try { const { service, config } = this.getServer(serverId); const result = await service.getAccountSummary(username); return { server: { id: config.id, name: config.name, address: config.server }, username, summary: result, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to get account summary: ${error.message}`); throw new Error(`Error getting account summary for ${username}: ${error.message}`); } }); // whm_suspend_account schemas const whmSuspendAccountRequestSchema = z.object({ method: z.literal('whm_suspend_account'), serverId: z.string().optional(), username: z.string(), reason: z.string() }); const whmSuspendAccountResultSchema = z.object({ server: z.object({ id: z.string(), name: z.string(), address: z.string() }), operation: z.string(), username: z.string(), reason: z.string(), success: z.boolean(), result: z.any(), timestamp: z.string() }); // Suspend account handler this.server.setRequestHandler(whmSuspendAccountRequestSchema, async ({ serverId, username, reason }) => { try { const { service, config } = this.getServer(serverId); const result = await service.suspendAccount(username, reason); return { server: { id: config.id, name: config.name, address: config.server }, operation: "suspend_account", username, reason, success: result.status === 1, result, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to suspend account: ${error.message}`); throw new Error(`Error suspending account ${username}: ${error.message}`); } }); // whm_unsuspend_account schemas const whmUnsuspendAccountRequestSchema = z.object({ method: z.literal('whm_unsuspend_account'), serverId: z.string().optional(), username: z.string() }); const whmUnsuspendAccountResultSchema = z.object({ server: z.object({ id: z.string(), name: z.string(), address: z.string() }), operation: z.string(), username: z.string(), success: z.boolean(), result: z.any(), timestamp: z.string() }); // Unsuspend account handler this.server.setRequestHandler(whmUnsuspendAccountRequestSchema, async ({ serverId, username }) => { try { const { service, config } = this.getServer(serverId); const result = await service.unsuspendAccount(username); return { server: { id: config.id, name: config.name, address: config.server }, operation: "unsuspend_account", username, success: result.status === 1, result, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to unsuspend account: ${error.message}`); throw new Error(`Error unsuspending account ${username}: ${error.message}`); } }); // whm_terminate_account schemas const whmTerminateAccountRequestSchema = z.object({ method: z.literal('whm_terminate_account'), serverId: z.string().optional(), username: z.string(), confirm: z.boolean() }); const whmTerminateAccountResultSchema = z.object({ server: z.object({ id: z.string(), name: z.string(), address: z.string() }), operation: z.string(), username: z.string(), success: z.boolean(), result: z.any(), timestamp: z.string(), warning: z.string().optional() }); // Terminate account handler this.server.setRequestHandler(whmTerminateAccountRequestSchema, async ({ serverId, username, confirm }) => { if (!confirm) { throw new Error("Account termination cancelled. Set confirm=true to proceed with permanent deletion."); } try { const { service, config } = this.getServer(serverId); const result = await service.terminateAccount(username); return { server: { id: config.id, name: config.name, address: config.server }, operation: "terminate_account", username, success: result.status === 1, result, timestamp: new Date().toISOString(), warning: "This account has been permanently deleted" }; } catch (error) { logger.error(`Failed to terminate account: ${error.message}`); throw new Error(`Error terminating account ${username}: ${error.message}`); } }); // Server monitoring tools // whm_server_status schemas const whmServerStatusRequestSchema = z.object({ method: z.literal('whm_server_status'), serverId: z.string().optional() }); const whmServerStatusResultSchema = z.object({ server: z.object({ id: z.string(), name: z.string(), address: z.string() }), status: z.any(), timestamp: z.string() }); // Server status handler this.server.setRequestHandler(whmServerStatusRequestSchema, async ({ 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) { logger.error(`Failed to get server status: ${error.message}`); throw new Error(`Error getting server status: ${error.message}`); } }); // whm_service_status schemas const whmServiceStatusRequestSchema = z.object({ method: z.literal('whm_service_status'), serverId: z.string().optional() }); const whmServiceStatusResultSchema = z.object({ server: z.object({ id: z.string(), name: z.string(), address: z.string() }), services: z.any(), timestamp: z.string() }); // Service status handler this.server.setRequestHandler(whmServiceStatusRequestSchema, async ({ serverId }) => { try { const { service, config } = this.getServer(serverId); const result = await service.getServiceStatus(); return { server: { id: config.id, name: config.name, address: config.server }, services: result, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to get service status: ${error.message}`); throw new Error(`Error getting service status: ${error.message}`); } }); // whm_restart_service schemas const whmRestartServiceRequestSchema = z.object({ method: z.literal('whm_restart_service'), serverId: z.string().optional(), service: z.string() }); const whmRestartServiceResultSchema = z.object({ server: z.object({ id: z.string(), name: z.string(), address: z.string() }), operation: z.string(), service: z.string(), result: z.any(), timestamp: z.string() }); // Restart service handler this.server.setRequestHandler(whmRestartServiceRequestSchema, async ({ serverId, service: serviceName }) => { try { const { service, config } = this.getServer(serverId); const result = await service.restartService(serviceName); this.audit({ tool: 'whm_restart_service', ok: true, serverId: config.id, service: serviceName }); return { server: { id: config.id, name: config.name, address: config.server }, operation: "restart_service", service: serviceName, result, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to restart service: ${error.message}`); this.audit({ tool: 'whm_restart_service', ok: false, serverId, service: serviceName, error: error.message }); throw new Error(`Error restarting service ${serviceName}: ${error.message}`); } }); // Domain management tools // whm_list_domains schemas const whmListDomainsRequestSchema = z.object({ method: z.literal('whm_list_domains'), serverId: z.string().optional(), username: z.string() }); const whmListDomainsResultSchema = z.object({ server: z.object({ id: z.string(), name: z.string(), address: z.string() }), username: z.string(), domains: z.any(), timestamp: z.string() }); // List domains handler this.server.setRequestHandler(whmListDomainsRequestSchema, async ({ serverId, username }) => { try { const { service, config } = this.getServer(serverId); const result = await service.listDomains(username); return { server: { id: config.id, name: config.name, address: config.server }, username, domains: result, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to list domains: ${error.message}`); throw new Error(`Error listing domains for ${username}: ${error.message}`); } }); // Generic passthrough tool // whm_call_api schemas const whmCallApiRequestSchema = z.object({ method: z.literal('whm_call_api'), serverId: z.string().optional(), endpoint: z.string(), method: z.enum(["get", "post"]).optional(), params: z.record(z.any()).optional() }); const whmCallApiResultSchema = z.object({ server: z.object({ id: z.string(), name: z.string(), address: z.string() }), operation: z.string(), endpoint: z.string(), method: z.string(), params: z.any(), result: z.any(), timestamp: z.string() }); // Generic passthrough handler this.server.setRequestHandler(whmCallApiRequestSchema, async ({ serverId, endpoint, method = 'get', params = {} }) => { try { const { service, config } = this.getServer(serverId); const result = await service.callApi({ method, endpoint, params }); this.audit({ tool: 'whm_call_api', ok: true, serverId: config.id, endpoint, method, params }); return { server: { id: config.id, name: config.name, address: config.server }, operation: "call_api", endpoint, method, params, result, timestamp: new Date().toISOString() }; } catch (error) { logger.error(`Failed to call WHM API: ${error.message}`); this.audit({ tool: 'whm_call_api', ok: false, serverId, endpoint, method, error: error.message }); throw new Error(`Error calling WHM API ${endpoint}: ${error.message}`); } }); } // Removed setupResources() — .resource() is not supported in MCP SDK v0.4+ async start() { if (!this.server) { await this.initialize(); } const transport = new StdioServerTransport(); await this.server.connect(transport); logger.info('WHMRockStar MCP Server started'); logger.info(`Managing ${this.servers.size} WHM server(s)`); } } // Start the server if (require.main === module) { (async () => { try { const server = new WHMRockStarMCPServer(); await server.start(); } catch (error) { logger.error(`Failed to start MCP server: ${error.message}`); process.exit(1); } })(); } module.exports = WHMRockStarMCPServer;