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