Skip to main content
Glama
ooples

MCP Console Automation Server

VirtualBoxProtocol.ts21.3 kB
import { spawn, ChildProcess } from 'child_process'; import { BaseProtocol } from '../core/BaseProtocol.js'; import { ConsoleSession, SessionOptions, ConsoleType, ConsoleOutput, } from '../types/index.js'; import { ProtocolCapabilities, SessionState, ErrorContext, ProtocolHealthStatus, ErrorRecoveryResult, ResourceUsage, } from '../core/IProtocol.js'; // VirtualBox Protocol connection options interface VirtualBoxConnectionOptions extends SessionOptions { // Basic VM Configuration vmName?: string; vmUuid?: string; operation?: | 'start' | 'stop' | 'pause' | 'resume' | 'reset' | 'poweroff' | 'savestate' | 'list' | 'showvminfo' | 'clone' | 'snapshot' | 'import' | 'export' | 'manage' | 'console' | 'connect'; // VM Control Options vmType?: 'gui' | 'headless' | 'separate' | 'sdl'; startupDelay?: number; enableVrde?: boolean; vrdePort?: number; vrdeAddress?: string; vrdeAuthType?: 'null' | 'external' | 'guest'; // VM Configuration osType?: string; memory?: number; cpus?: number; vram?: number; enableAccel3d?: boolean; enableAccel2d?: boolean; // Storage Configuration hddPath?: string; hddSize?: number; hddType?: 'dynamic' | 'fixed'; enableSata?: boolean; enableIde?: boolean; enableScsi?: boolean; enableNvme?: boolean; // Network Configuration networkAdapter?: number; networkType?: | 'none' | 'null' | 'nat' | 'natnetwork' | 'bridged' | 'intnet' | 'hostonly' | 'generic'; networkName?: string; macAddress?: string; enableCable?: boolean; // Audio Configuration audioController?: 'none' | 'ac97' | 'hda' | 'sb16'; audioDriver?: | 'none' | 'pulse' | 'alsa' | 'oss' | 'coreaudio' | 'directsound' | 'was'; // USB Configuration enableUsb?: boolean; usbVersion?: '1.0' | '2.0' | '3.0'; usbFilters?: string[]; // Shared Folders sharedFolders?: Array<{ name: string; hostPath: string; readonly?: boolean; automount?: boolean; }>; // Advanced Settings biosSettings?: Record<string, string>; extraData?: Record<string, string>; enableIoApic?: boolean; enablePae?: boolean; enableLongMode?: boolean; enableVtx?: boolean; enableNestedPaging?: boolean; // Guest Additions enableGuestAdditions?: boolean; guestAdditionsIso?: string; enableClipboard?: boolean; clipboardMode?: 'disabled' | 'hosttoguest' | 'guesttohost' | 'bidirectional'; enableDragDrop?: boolean; dragDropMode?: 'disabled' | 'hosttoguest' | 'guesttohost' | 'bidirectional'; // Snapshots snapshotName?: string; snapshotDescription?: string; enableSnapshots?: boolean; deleteSnapshots?: boolean; // Groups groupName?: string; enableGroups?: boolean; // VirtualBox Management vboxmanagePath?: string; vboxheadlessPath?: string; vboxsdlPath?: string; // Automation automateInstall?: boolean; unattendedScript?: string; kickstartFile?: string; preseedFile?: string; // Security enableEncryption?: boolean; encryptionPassword?: string; enableSecureBoot?: boolean; // Performance enableHardwareVirt?: boolean; enableLargePages?: boolean; enablePageFusion?: boolean; paravirtProvider?: | 'none' | 'default' | 'legacy' | 'minimal' | 'hyperv' | 'kvm'; // Remote Display enableVnc?: boolean; vncPort?: number; vncPassword?: string; enableSpice?: boolean; spicePort?: number; // Cloud Integration enableCloudSync?: boolean; cloudProvider?: string; cloudProfile?: string; // Debugging enableDebugger?: boolean; debuggerType?: 'builtin' | 'gdb'; enableTracing?: boolean; tracingConfig?: string; // Import/Export importPath?: string; exportPath?: string; exportFormat?: 'ovf' | 'ova'; enableManifest?: boolean; // Extension Packs extensionPacks?: string[]; enableExtensionPack?: boolean; // Host Integration enableHostOnlyNetwork?: boolean; hostOnlyAdapter?: string; enableNatNetwork?: boolean; natNetworkName?: string; // Resource Limits cpuExecutionCap?: number; memoryBalloon?: number; enableLimitCpuTime?: boolean; cpuTimeLimit?: number; // Logging enableVmLogging?: boolean; logLevel?: 'disabled' | 'error' | 'warning' | 'info' | 'debug' | 'trace'; logFile?: string; // Backup and Recovery enableBackup?: boolean; backupPath?: string; backupFrequency?: 'daily' | 'weekly' | 'monthly'; // Guest OS Features guestOsFeatures?: { enableSymlinks?: boolean; enableHardlinks?: boolean; enableStatfs?: boolean; hostTimeSync?: boolean; }; // API Configuration enableWebservice?: boolean; webservicePort?: number; webserviceAuth?: boolean; enableSoap?: boolean; soapTimeout?: number; // Networking Advanced portForwarding?: Array<{ name: string; protocol: 'tcp' | 'udp'; hostPort: number; guestPort: number; hostIp?: string; guestIp?: string; }>; // Custom Properties customProperties?: Record<string, string>; vmDescription?: string; vmTags?: string[]; // Power Management powerSettings?: { acpiEnabled?: boolean; ioapicEnabled?: boolean; utcClock?: boolean; hwClockUtc?: boolean; }; // Display Settings displaySettings?: { monitorCount?: number; resolutionWidth?: number; resolutionHeight?: number; colorDepth?: number; enableAcceleration?: boolean; }; } /** * VirtualBox Protocol Implementation * * Provides VirtualBox virtual machine management and console access * Supports VM lifecycle, headless operations, VRDE/VNC remote access, and comprehensive virtualization control */ export class VirtualBoxProtocol extends BaseProtocol { public readonly type: ConsoleType = 'virtualization'; public readonly capabilities: ProtocolCapabilities; private vboxProcesses = new Map<string, ChildProcess>(); // Compatibility property for old ProtocolFactory interface public get healthStatus(): ProtocolHealthStatus { return { isHealthy: this.isInitialized, lastChecked: new Date(), errors: [], warnings: [], metrics: { activeSessions: this.sessions.size, totalSessions: this.sessions.size, averageLatency: 0, successRate: 100, uptime: 0, }, dependencies: {}, }; } constructor() { super('virtualbox'); this.capabilities = { supportsStreaming: true, supportsFileTransfer: true, supportsX11Forwarding: true, supportsPortForwarding: true, supportsAuthentication: true, supportsEncryption: true, supportsCompression: true, supportsMultiplexing: true, supportsKeepAlive: true, supportsReconnection: true, supportsBinaryData: true, supportsCustomEnvironment: true, supportsWorkingDirectory: true, supportsSignals: true, supportsResizing: true, supportsPTY: true, maxConcurrentSessions: 100, // VirtualBox can run many VMs simultaneously defaultTimeout: 60000, // VM operations can take time supportedEncodings: ['utf-8'], supportedAuthMethods: ['password', 'key-file'], platformSupport: { windows: true, linux: true, macos: true, freebsd: true, }, }; } async initialize(): Promise<void> { if (this.isInitialized) return; try { // Check if VirtualBox is available await this.checkVirtualBoxAvailability(); this.isInitialized = true; this.logger.info( 'VirtualBox protocol initialized with virtualization features' ); } catch (error: any) { this.logger.error('Failed to initialize VirtualBox protocol', error); throw error; } } async createSession(options: SessionOptions): Promise<ConsoleSession> { const sessionId = `vbox-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; return await this.createSessionWithTypeDetection(sessionId, options); } async dispose(): Promise<void> { await this.cleanup(); } async executeCommand( sessionId: string, command: string, args?: string[] ): Promise<void> { const fullCommand = args && args.length > 0 ? `${command} ${args.join(' ')}` : command; await this.sendInput(sessionId, fullCommand + '\n'); } async sendInput(sessionId: string, input: string): Promise<void> { const vboxProcess = this.vboxProcesses.get(sessionId); const session = this.sessions.get(sessionId); if (!vboxProcess || !vboxProcess.stdin || !session) { throw new Error(`No active VirtualBox session: ${sessionId}`); } vboxProcess.stdin.write(input); session.lastActivity = new Date(); this.emit('input-sent', { sessionId, input, timestamp: new Date(), }); this.logger.debug( `Sent input to VirtualBox session ${sessionId}: ${input.substring(0, 100)}` ); } async closeSession(sessionId: string): Promise<void> { try { const vboxProcess = this.vboxProcesses.get(sessionId); if (vboxProcess) { // Try graceful shutdown first vboxProcess.kill('SIGTERM'); // Force kill after timeout setTimeout(() => { if (vboxProcess && !vboxProcess.killed) { vboxProcess.kill('SIGKILL'); } }, 10000); this.vboxProcesses.delete(sessionId); } // Clean up base protocol session this.sessions.delete(sessionId); this.logger.info(`VirtualBox session ${sessionId} closed`); this.emit('session-closed', sessionId); } catch (error) { this.logger.error( `Error closing VirtualBox session ${sessionId}:`, error ); throw error; } } async doCreateSession( sessionId: string, options: SessionOptions, sessionState: SessionState ): Promise<ConsoleSession> { if (!this.isInitialized) { await this.initialize(); } const vboxOptions = options as VirtualBoxConnectionOptions; // Validate VM parameters if (!vboxOptions.vmName && !vboxOptions.vmUuid) { throw new Error('VM name or UUID is required for VirtualBox protocol'); } // Build VirtualBox command const vboxCommand = this.buildVirtualBoxCommand(vboxOptions); // Spawn VirtualBox process const vboxProcess = spawn(vboxCommand[0], vboxCommand.slice(1), { stdio: ['pipe', 'pipe', 'pipe'], cwd: options.cwd || process.cwd(), env: { ...process.env, ...this.buildEnvironment(vboxOptions), ...options.env, }, }); // Set up output handling vboxProcess.stdout?.on('data', (data) => { const output: ConsoleOutput = { sessionId, type: 'stdout', data: data.toString(), timestamp: new Date(), }; this.addToOutputBuffer(sessionId, output); }); vboxProcess.stderr?.on('data', (data) => { const output: ConsoleOutput = { sessionId, type: 'stderr', data: data.toString(), timestamp: new Date(), }; this.addToOutputBuffer(sessionId, output); }); vboxProcess.on('error', (error) => { this.logger.error( `VirtualBox process error for session ${sessionId}:`, error ); this.emit('session-error', { sessionId, error }); }); vboxProcess.on('close', (code) => { this.logger.info( `VirtualBox process closed for session ${sessionId} with code ${code}` ); this.markSessionComplete(sessionId, code || 0); }); // Store the process this.vboxProcesses.set(sessionId, vboxProcess); // Create session object const session: ConsoleSession = { id: sessionId, command: vboxCommand[0], args: vboxCommand.slice(1), cwd: options.cwd || process.cwd(), env: { ...process.env, ...this.buildEnvironment(vboxOptions), ...options.env, }, createdAt: new Date(), lastActivity: new Date(), status: 'running', type: this.type, streaming: options.streaming, executionState: 'idle', activeCommands: new Map(), pid: vboxProcess.pid, }; this.sessions.set(sessionId, session); this.logger.info( `VirtualBox session ${sessionId} created for VM ${vboxOptions.vmName || vboxOptions.vmUuid}` ); this.emit('session-created', { sessionId, type: 'virtualization', session, }); return session; } // Override getOutput to satisfy old ProtocolFactory interface (returns string) async getOutput(sessionId: string, since?: Date): Promise<any> { const outputs = await super.getOutput(sessionId, since); return outputs.map((output) => output.data).join(''); } // Missing IProtocol methods for compatibility getAllSessions(): ConsoleSession[] { return Array.from(this.sessions.values()); } getActiveSessions(): ConsoleSession[] { return Array.from(this.sessions.values()).filter( (session) => session.status === 'running' ); } getSessionCount(): number { return this.sessions.size; } async getSessionState(sessionId: string): Promise<SessionState> { const session = this.sessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } return { sessionId, status: session.status, isOneShot: false, // VirtualBox sessions are typically persistent isPersistent: true, createdAt: session.createdAt, lastActivity: session.lastActivity, pid: session.pid, metadata: {}, }; } async handleError( error: Error, context: ErrorContext ): Promise<ErrorRecoveryResult> { this.logger.error( `Error in VirtualBox session ${context.sessionId}: ${error.message}` ); return { recovered: false, strategy: 'none', attempts: 0, duration: 0, error: error.message, }; } async recoverSession(sessionId: string): Promise<boolean> { const vboxProcess = this.vboxProcesses.get(sessionId); return (vboxProcess && !vboxProcess.killed) || false; } getResourceUsage(): ResourceUsage { const memUsage = process.memoryUsage(); const cpuUsage = process.cpuUsage(); return { memory: { used: memUsage.heapUsed, available: memUsage.heapTotal, peak: memUsage.heapTotal, }, cpu: { usage: cpuUsage.user + cpuUsage.system, load: [0, 0, 0], }, network: { bytesIn: 0, bytesOut: 0, connectionsActive: this.vboxProcesses.size, }, storage: { bytesRead: 0, bytesWritten: 0, }, sessions: { active: this.sessions.size, total: this.sessions.size, peak: this.sessions.size, }, }; } async getHealthStatus(): Promise<ProtocolHealthStatus> { const baseStatus = await super.getHealthStatus(); try { await this.checkVirtualBoxAvailability(); return { ...baseStatus, dependencies: { virtualbox: { available: true }, }, }; } catch (error) { return { ...baseStatus, isHealthy: false, errors: [...baseStatus.errors, `VirtualBox not available: ${error}`], dependencies: { virtualbox: { available: false }, }, }; } } private async checkVirtualBoxAvailability(): Promise<void> { return new Promise((resolve, reject) => { const testProcess = spawn('VBoxManage', ['--version'], { stdio: 'pipe' }); testProcess.on('close', (code) => { if (code === 0) { resolve(); } else { reject( new Error( 'VirtualBox VBoxManage tool not found. Please install VirtualBox.' ) ); } }); testProcess.on('error', () => { reject( new Error( 'VirtualBox VBoxManage tool not found. Please install VirtualBox.' ) ); }); }); } private buildVirtualBoxCommand( options: VirtualBoxConnectionOptions ): string[] { const command = []; // VirtualBox executable if (options.vboxmanagePath) { command.push(options.vboxmanagePath); } else { command.push('VBoxManage'); } // Operation type switch (options.operation) { case 'start': command.push('startvm'); if (options.vmName) { command.push(options.vmName); } else if (options.vmUuid) { command.push(options.vmUuid); } // VM type if (options.vmType) { command.push('--type', options.vmType); } // VRDE settings if (options.enableVrde) { command.push('--vrde', 'on'); if (options.vrdePort) { command.push('--vrdeport', options.vrdePort.toString()); } } break; case 'stop': command.push('controlvm'); if (options.vmName) { command.push(options.vmName); } else if (options.vmUuid) { command.push(options.vmUuid); } command.push('poweroff'); break; case 'pause': command.push('controlvm'); if (options.vmName) { command.push(options.vmName); } else if (options.vmUuid) { command.push(options.vmUuid); } command.push('pause'); break; case 'resume': command.push('controlvm'); if (options.vmName) { command.push(options.vmName); } else if (options.vmUuid) { command.push(options.vmUuid); } command.push('resume'); break; case 'reset': command.push('controlvm'); if (options.vmName) { command.push(options.vmName); } else if (options.vmUuid) { command.push(options.vmUuid); } command.push('reset'); break; case 'savestate': command.push('controlvm'); if (options.vmName) { command.push(options.vmName); } else if (options.vmUuid) { command.push(options.vmUuid); } command.push('savestate'); break; case 'list': command.push('list', 'vms'); break; case 'showvminfo': command.push('showvminfo'); if (options.vmName) { command.push(options.vmName); } else if (options.vmUuid) { command.push(options.vmUuid); } break; case 'snapshot': command.push('snapshot'); if (options.vmName) { command.push(options.vmName); } else if (options.vmUuid) { command.push(options.vmUuid); } command.push('take'); if (options.snapshotName) { command.push(options.snapshotName); } break; case 'clone': command.push('clonevm'); if (options.vmName) { command.push(options.vmName); } else if (options.vmUuid) { command.push(options.vmUuid); } break; case 'import': command.push('import'); if (options.importPath) { command.push(options.importPath); } break; case 'export': command.push('export'); if (options.vmName) { command.push(options.vmName); } else if (options.vmUuid) { command.push(options.vmUuid); } if (options.exportPath) { command.push('--output', options.exportPath); } break; default: // Default to VM management operations command.push('list', 'vms'); break; } return command; } private buildEnvironment( options: VirtualBoxConnectionOptions ): Record<string, string> { const env: Record<string, string> = {}; // VirtualBox environment variables if (options.vboxmanagePath) { env.VBOX_MANAGE_PATH = options.vboxmanagePath; } // VM configuration if (options.vmName) { env.VBOX_VM_NAME = options.vmName; } if (options.vmUuid) { env.VBOX_VM_UUID = options.vmUuid; } // VRDE settings if (options.enableVrde) { env.VBOX_VRDE_ENABLED = 'true'; if (options.vrdePort) { env.VBOX_VRDE_PORT = options.vrdePort.toString(); } } // Custom properties if (options.customProperties) { Object.entries(options.customProperties).forEach(([key, value]) => { env[`VBOX_${key.toUpperCase()}`] = value; }); } return env; } async cleanup(): Promise<void> { this.logger.info('Cleaning up VirtualBox protocol'); // Close all VirtualBox processes for (const [sessionId, process] of Array.from(this.vboxProcesses)) { try { process.kill(); } catch (error) { this.logger.error( `Error killing VirtualBox process for session ${sessionId}:`, error ); } } // Clear all data this.vboxProcesses.clear(); // Call parent cleanup await super.cleanup(); } } export default VirtualBoxProtocol;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ooples/mcp-console-automation'

If you have feedback or need assistance with the MCP directory API, please join our Discord server