Skip to main content
Glama
ooples

MCP Console Automation Server

HyperVProtocol.ts18.1 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'; // Hyper-V Protocol connection options interface HyperVConnectionOptions extends SessionOptions { hyperVHost?: string; vmName?: string; vmId?: string; hyperVPath?: string; username?: string; password?: string; domain?: string; operation?: | 'connect' | 'start' | 'stop' | 'save' | 'pause' | 'resume' | 'checkpoint' | 'restore'; checkpointName?: string; enableEnhancedSession?: boolean; enableRDP?: boolean; rdpPort?: number; enableVMConnect?: boolean; vmGeneration?: 1 | 2; memorySizeMB?: number; cpuCount?: number; enableDynamicMemory?: boolean; memoryMinimumMB?: number; memoryMaximumMB?: number; enableNestedVirtualization?: boolean; enableSecureBoot?: boolean; enableTPM?: boolean; switchName?: string; vlanId?: number; macAddress?: string; enableMacSpoofing?: boolean; enableDhcpGuard?: boolean; enableRouterGuard?: boolean; enablePortMirroring?: boolean; vhdPath?: string; vhdSizeGB?: number; enableCheckpoints?: boolean; automaticStartAction?: 'Nothing' | 'StartIfRunning' | 'Start'; automaticStopAction?: 'TurnOff' | 'Save' | 'ShutDown'; automaticStartDelay?: number; smartPagingFilePath?: string; snapshotFileLocation?: string; configurationDataRoot?: string; enableGuestServices?: boolean; enableHeartbeat?: boolean; enableKeyValuePairExchange?: boolean; enableShutdown?: boolean; enableTimeSynchronization?: boolean; enableVSS?: boolean; environment?: Record<string, string>; } /** * Hyper-V Protocol Implementation * * Provides Microsoft Hyper-V virtualization console access through PowerShell and VM management commands * Supports VM lifecycle management, enhanced sessions, RDP connections, and enterprise virtualization features */ export class HyperVProtocol extends BaseProtocol { public readonly type: ConsoleType = 'hyperv'; public readonly capabilities: ProtocolCapabilities; private hyperVProcesses = 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('hyperv'); this.capabilities = { supportsStreaming: true, supportsFileTransfer: false, supportsX11Forwarding: false, supportsPortForwarding: true, supportsAuthentication: true, supportsEncryption: true, supportsCompression: false, supportsMultiplexing: true, supportsKeepAlive: true, supportsReconnection: true, supportsBinaryData: true, supportsCustomEnvironment: true, supportsWorkingDirectory: true, supportsSignals: true, supportsResizing: true, supportsPTY: false, // Hyper-V uses console redirection maxConcurrentSessions: 50, // Hyper-V can handle many VMs defaultTimeout: 120000, // VM operations can take time supportedEncodings: ['utf-8'], supportedAuthMethods: ['password', 'certificate', 'domain'], platformSupport: { windows: true, // Hyper-V is Windows-only linux: false, macos: false, freebsd: false, }, }; } async initialize(): Promise<void> { if (this.isInitialized) return; try { // Check if Hyper-V tools are available await this.checkHyperVAvailability(); this.isInitialized = true; this.logger.info('Hyper-V protocol initialized with production features'); } catch (error: any) { this.logger.error('Failed to initialize Hyper-V protocol', error); throw error; } } async createSession(options: SessionOptions): Promise<ConsoleSession> { const sessionId = `hyperv-${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 hyperVProcess = this.hyperVProcesses.get(sessionId); const session = this.sessions.get(sessionId); if (!hyperVProcess || !hyperVProcess.stdin || !session) { throw new Error(`No active Hyper-V session: ${sessionId}`); } hyperVProcess.stdin.write(input); session.lastActivity = new Date(); this.emit('input-sent', { sessionId, input, timestamp: new Date(), }); this.logger.debug( `Sent input to Hyper-V session ${sessionId}: ${input.substring(0, 100)}` ); } async closeSession(sessionId: string): Promise<void> { try { const hyperVProcess = this.hyperVProcesses.get(sessionId); if (hyperVProcess) { // Try graceful shutdown first hyperVProcess.kill('SIGTERM'); // Force kill after timeout setTimeout(() => { if (hyperVProcess && !hyperVProcess.killed) { hyperVProcess.kill('SIGKILL'); } }, 30000); this.hyperVProcesses.delete(sessionId); } // Clean up base protocol session this.sessions.delete(sessionId); this.logger.info(`Hyper-V session ${sessionId} closed`); this.emit('session-closed', sessionId); } catch (error) { this.logger.error(`Error closing Hyper-V session ${sessionId}:`, error); throw error; } } async doCreateSession( sessionId: string, options: SessionOptions, sessionState: SessionState ): Promise<ConsoleSession> { if (!this.isInitialized) { await this.initialize(); } const hyperVOptions = options as HyperVConnectionOptions; // Validate required VM parameters if (!hyperVOptions.vmName && !hyperVOptions.vmId) { throw new Error('VM name or VM ID is required for Hyper-V protocol'); } // Build Hyper-V command const hyperVCommand = this.buildHyperVCommand(hyperVOptions); // Spawn Hyper-V process const hyperVProcess = spawn(hyperVCommand[0], hyperVCommand.slice(1), { stdio: ['pipe', 'pipe', 'pipe'], cwd: options.cwd || process.cwd(), env: { ...process.env, ...this.buildEnvironment(hyperVOptions), ...options.env, }, }); // Set up output handling hyperVProcess.stdout?.on('data', (data) => { const output: ConsoleOutput = { sessionId, type: 'stdout', data: data.toString(), timestamp: new Date(), }; this.addToOutputBuffer(sessionId, output); }); hyperVProcess.stderr?.on('data', (data) => { const output: ConsoleOutput = { sessionId, type: 'stderr', data: data.toString(), timestamp: new Date(), }; this.addToOutputBuffer(sessionId, output); }); hyperVProcess.on('error', (error) => { this.logger.error( `Hyper-V process error for session ${sessionId}:`, error ); this.emit('session-error', { sessionId, error }); }); hyperVProcess.on('close', (code) => { this.logger.info( `Hyper-V process closed for session ${sessionId} with code ${code}` ); this.markSessionComplete(sessionId, code || 0); }); // Store the process this.hyperVProcesses.set(sessionId, hyperVProcess); // Create session object const session: ConsoleSession = { id: sessionId, command: hyperVCommand[0], args: hyperVCommand.slice(1), cwd: options.cwd || process.cwd(), env: { ...process.env, ...this.buildEnvironment(hyperVOptions), ...options.env, }, createdAt: new Date(), lastActivity: new Date(), status: 'running', type: this.type, streaming: options.streaming, executionState: 'idle', activeCommands: new Map(), pid: hyperVProcess.pid, }; this.sessions.set(sessionId, session); this.logger.info( `Hyper-V session ${sessionId} created for VM ${hyperVOptions.vmName || hyperVOptions.vmId}` ); this.emit('session-created', { sessionId, type: 'hyperv', 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, // Hyper-V 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 Hyper-V session ${context.sessionId}: ${error.message}` ); return { recovered: false, strategy: 'none', attempts: 0, duration: 0, error: error.message, }; } async recoverSession(sessionId: string): Promise<boolean> { const hyperVProcess = this.hyperVProcesses.get(sessionId); return (hyperVProcess && !hyperVProcess.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.hyperVProcesses.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.checkHyperVAvailability(); return { ...baseStatus, dependencies: { hyperv: { available: true }, }, }; } catch (error) { return { ...baseStatus, isHealthy: false, errors: [...baseStatus.errors, `Hyper-V not available: ${error}`], dependencies: { hyperv: { available: false }, }, }; } } private async checkHyperVAvailability(): Promise<void> { return new Promise((resolve, reject) => { const testProcess = spawn( 'powershell', [ '-Command', 'Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V', ], { stdio: 'pipe' } ); testProcess.on('close', (code) => { if (code === 0) { resolve(); } else { reject( new Error( 'Hyper-V not available. Please enable Hyper-V feature on Windows.' ) ); } }); testProcess.on('error', () => { reject( new Error( 'PowerShell not available. Hyper-V requires Windows PowerShell.' ) ); }); }); } private buildHyperVCommand(options: HyperVConnectionOptions): string[] { const command = []; // Use PowerShell for Hyper-V management command.push('powershell'); command.push('-ExecutionPolicy', 'Bypass'); command.push('-Command'); let psCommand = ''; // Build PowerShell command based on operation switch (options.operation) { case 'start': psCommand = `Start-VM -Name '${options.vmName || options.vmId}'`; break; case 'stop': psCommand = `Stop-VM -Name '${options.vmName || options.vmId}' -Force`; break; case 'save': psCommand = `Save-VM -Name '${options.vmName || options.vmId}'`; break; case 'pause': psCommand = `Suspend-VM -Name '${options.vmName || options.vmId}'`; break; case 'resume': psCommand = `Resume-VM -Name '${options.vmName || options.vmId}'`; break; case 'checkpoint': psCommand = `Checkpoint-VM -Name '${options.vmName || options.vmId}'`; if (options.checkpointName) { psCommand += ` -SnapshotName '${options.checkpointName}'`; } break; case 'restore': if (options.checkpointName) { psCommand = `Restore-VMSnapshot -VMName '${options.vmName || options.vmId}' -Name '${options.checkpointName}' -Confirm:$false`; } else { psCommand = `Get-VMSnapshot -VMName '${options.vmName || options.vmId}' | Sort-Object CreationTime | Select-Object -Last 1 | Restore-VMSnapshot -Confirm:$false`; } break; case 'connect': default: if (options.enableVMConnect !== false) { // Use VMConnect for console access if (options.hyperVHost) { psCommand = `vmconnect '${options.hyperVHost}' '${options.vmName || options.vmId}'`; } else { psCommand = `vmconnect 'localhost' '${options.vmName || options.vmId}'`; } } else if (options.enableRDP) { // Use RDP connection psCommand = `Get-VM -Name '${options.vmName || options.vmId}' | Get-VMNetworkAdapter | Select-Object -ExpandProperty IPAddresses | Where-Object { $_ -match '^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$' } | Select-Object -First 1 | ForEach-Object { mstsc /v:$_ }`; } else { // PowerShell Direct (enhanced session) if (options.enableEnhancedSession !== false) { psCommand = `Enter-PSSession -VMName '${options.vmName || options.vmId}'`; } else { psCommand = `Get-VM -Name '${options.vmName || options.vmId}' | Format-Table Name, State, CPUUsage, MemoryAssigned, MemoryDemand, MemoryStatus, Uptime, Status`; } } break; } // Add credentials if provided if (options.username && options.password) { if (psCommand.includes('Enter-PSSession')) { const credScript = `$securePassword = ConvertTo-SecureString '${options.password}' -AsPlainText -Force; `; if (options.domain) { psCommand = credScript + `$credential = New-Object System.Management.Automation.PSCredential('${options.domain}\\${options.username}', $securePassword); ` + psCommand + ` -Credential $credential`; } else { psCommand = credScript + `$credential = New-Object System.Management.Automation.PSCredential('${options.username}', $securePassword); ` + psCommand + ` -Credential $credential`; } } } command.push(psCommand); return command; } private buildEnvironment( options: HyperVConnectionOptions ): Record<string, string> { const env: Record<string, string> = {}; // Hyper-V environment variables if (options.hyperVHost) { env.HYPERV_HOST = options.hyperVHost; } if (options.vmName) { env.HYPERV_VM_NAME = options.vmName; } if (options.vmId) { env.HYPERV_VM_ID = options.vmId; } // Authentication if (options.username) { env.HYPERV_USERNAME = options.username; } if (options.domain) { env.HYPERV_DOMAIN = options.domain; } // Operation settings if (options.operation) { env.HYPERV_OPERATION = options.operation; } // Enhanced session settings if (options.enableEnhancedSession !== undefined) { env.HYPERV_ENHANCED_SESSION = options.enableEnhancedSession.toString(); } // RDP settings if (options.enableRDP !== undefined) { env.HYPERV_ENABLE_RDP = options.enableRDP.toString(); } if (options.rdpPort) { env.HYPERV_RDP_PORT = options.rdpPort.toString(); } // Custom environment variables if (options.environment) { Object.assign(env, options.environment); } return env; } async cleanup(): Promise<void> { this.logger.info('Cleaning up Hyper-V protocol'); // Close all Hyper-V processes for (const [sessionId, process] of Array.from(this.hyperVProcesses)) { try { process.kill(); } catch (error) { this.logger.error( `Error killing Hyper-V process for session ${sessionId}:`, error ); } } // Clear all data this.hyperVProcesses.clear(); // Call parent cleanup await super.cleanup(); } } export default HyperVProtocol;

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