@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
JavaScript
/**
* 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;