Skip to main content
Glama
ooples

MCP Console Automation Server

RDPProtocol.ts36.5 kB
import { EventEmitter } from 'events'; import { spawn, ChildProcess } from 'child_process'; import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'; import { join, dirname } from 'path'; import { v4 as uuidv4 } from 'uuid'; import * as forge from 'node-forge'; // Canvas import made optional to handle missing dependency let createCanvas: any, Canvas: any, CanvasRenderingContext2D: any; try { const canvasModule = require('canvas'); createCanvas = canvasModule.createCanvas; Canvas = canvasModule.Canvas; CanvasRenderingContext2D = canvasModule.CanvasRenderingContext2D; } catch (error) { console.warn('canvas module not available, canvas functionality disabled'); } import { RDPConnectionOptions, RDPSession, RDPCapabilities, RDPMonitorConfig, ConsoleOutput, SessionOptions, ConsoleSession, } from '../types/index.js'; import { IProtocol, ProtocolCapabilities, ProtocolHealthStatus, } from '../core/ProtocolFactory.js'; import { Logger } from '../utils/logger.js'; // Import node-rdpjs-2 dynamically to handle potential missing dependency let rdp: any; try { rdp = require('node-rdpjs-2'); } catch (error) { console.warn('node-rdpjs-2 not available, falling back to native RDP tools'); } export interface RDPProtocolEvents { connected: (session: RDPSession) => void; disconnected: (sessionId: string, reason?: string) => void; error: (sessionId: string, error: Error) => void; output: (output: ConsoleOutput) => void; 'screen-update': (sessionId: string, imageData: Buffer) => void; 'clipboard-data': (sessionId: string, data: string, format: string) => void; 'file-transfer-progress': ( sessionId: string, progress: FileTransferProgress ) => void; 'audio-data': (sessionId: string, audioBuffer: Buffer) => void; 'session-recording-update': ( sessionId: string, recordingPath: string ) => void; 'gateway-authenticated': (sessionId: string, gatewayInfo: any) => void; 'smart-card-request': (sessionId: string, request: SmartCardRequest) => void; 'performance-metrics': ( sessionId: string, metrics: RDPPerformanceMetrics ) => void; } export interface FileTransferProgress { transferId: string; fileName: string; bytesTransferred: number; totalBytes: number; direction: 'upload' | 'download'; status: 'pending' | 'in-progress' | 'completed' | 'failed'; error?: string; } export interface SmartCardRequest { requestId: string; cardName: string; readerName: string; requiresPin: boolean; challenge?: string; } export interface RDPPerformanceMetrics { timestamp: Date; latency: number; bandwidth: number; packetLoss: number; frameRate: number; compressionRatio: number; cpuUsage: number; memoryUsage: number; networkUtilization: number; } export interface RDPGatewayConfig { hostname: string; port: number; username?: string; password?: string; domain?: string; accessToken?: string; certificate?: string; enableAuth: boolean; } export class RDPProtocol extends EventEmitter implements IProtocol { public readonly type = 'rdp' as const; public readonly capabilities: ProtocolCapabilities; public readonly healthStatus: ProtocolHealthStatus; private logger: Logger; private sessions: Map<string, RDPSession> = new Map(); private connections: Map<string, any> = new Map(); // RDP connection instances private fallbackProcesses: Map<string, ChildProcess> = new Map(); private rdpCapabilities: RDPCapabilities; private sessionRecordings: Map<string, any> = new Map(); private clipboardBuffer: Map<string, string> = new Map(); private fileTransfers: Map<string, FileTransferProgress[]> = new Map(); private performanceMonitors: Map<string, NodeJS.Timeout> = new Map(); constructor() { super(); this.logger = new Logger('RDPProtocol'); this.capabilities = { supportsStreaming: true, supportsFileTransfer: true, supportsX11Forwarding: false, supportsPortForwarding: false, supportsAuthentication: true, supportsEncryption: true, supportsCompression: true, supportsMultiplexing: false, supportsKeepAlive: true, supportsReconnection: true, supportsBinaryData: true, supportsCustomEnvironment: false, supportsWorkingDirectory: false, supportsSignals: false, supportsResizing: true, supportsPTY: false, maxConcurrentSessions: 1, defaultTimeout: 30000, supportedEncodings: ['utf-8'], supportedAuthMethods: ['password', 'certificate'], platformSupport: { windows: true, linux: true, macos: true, freebsd: false, }, }; this.healthStatus = { isHealthy: true, lastChecked: new Date(), errors: [], warnings: [], metrics: { activeSessions: 0, totalSessions: 0, averageLatency: 0, successRate: 1.0, uptime: 0, }, dependencies: { rdp: { available: rdp !== undefined, version: '1.0', }, mstsc: { available: process.platform === 'win32', version: 'system', }, }, }; // Initialize RDP-specific capabilities this.rdpCapabilities = this.detectCapabilities(); this.logger.info('RDP Protocol initialized'); } /** * Detect system RDP capabilities */ private detectCapabilities(): RDPCapabilities { const isWindows = process.platform === 'win32'; const hasNodeRdp = rdp !== undefined; return { protocolVersions: [ 'RDP 8.1', 'RDP 10', 'RDP 10.1', 'RDP 10.2', 'RDP 10.3', 'RDP 10.4', 'RDP 10.5', 'RDP 10.6', 'RDP 10.7', ], maxResolution: { width: 7680, height: 4320 }, // 8K support supportedColorDepths: [8, 15, 16, 24, 32], compressionSupported: true, encryptionSupported: true, audioSupported: true, videoSupported: true, clipboardSupported: true, fileTransferSupported: true, printingSupported: isWindows, smartCardSupported: isWindows, remoteAppSupported: true, multiMonitorSupported: true, gpuAccelerationSupported: isWindows, h264Supported: true, remoteFXSupported: isWindows, nlaSupported: true, credSSPSupported: isWindows, gatewaySupported: true, sessionRecordingSupported: true, }; } /** * Create a new RDP session */ async createRDPSession( sessionId: string, options: RDPConnectionOptions ): Promise<RDPSession> { try { this.logger.info(`Creating RDP session ${sessionId}`, { host: options.host, port: options.port, }); const session: RDPSession = { sessionId, connectionId: uuidv4(), host: options.host, port: options.port || 3389, username: options.username, domain: options.domain, protocol: options.protocol || 'RDP 10', connectionTime: new Date(), lastActivity: new Date(), status: 'connecting', displaySize: { width: options.width || 1920, height: options.height || 1080, }, colorDepth: options.colorDepth || 32, compressionLevel: options.compressionLevel || 'medium', encryptionLevel: options.encryptionLevel || 'high', audioEnabled: options.audioMode !== 'disabled', clipboardEnabled: options.enableClipboard !== false, fileTransferEnabled: options.enableFileTransfer === true, gatewayUsed: !!options.gatewayHostname, remoteAppMode: options.enableRemoteApp === true, recordingActive: options.enableSessionRecording === true, bytesReceived: 0, bytesSent: 0, packetsReceived: 0, packetsSent: 0, errorCount: 0, warnings: [], metadata: {}, }; this.sessions.set(sessionId, session); // Attempt to use node-rdpjs-2 first, fallback to native tools if (rdp && !options.customRDPFile) { await this.createNativeRDPSession(sessionId, options); } else { await this.createFallbackSession(sessionId, options); } // Setup session monitoring this.setupSessionMonitoring(sessionId); // Setup session recording if enabled if (options.enableSessionRecording) { this.setupSessionRecording(sessionId, options); } this.emit('connected', session); return session; } catch (error) { this.logger.error(`Failed to create RDP session ${sessionId}`, error); const session = this.sessions.get(sessionId); if (session) { session.status = 'failed'; this.sessions.set(sessionId, session); } this.emit('error', sessionId, error as Error); throw error; } } /** * Create RDP session using node-rdpjs-2 */ private async createNativeRDPSession( sessionId: string, options: RDPConnectionOptions ): Promise<void> { return new Promise((resolve, reject) => { try { const clientConfig = this.buildClientConfig(options); const client = rdp.createClient(clientConfig); // Setup event handlers client.on('connect', () => { this.logger.info(`RDP session ${sessionId} connected`); const session = this.sessions.get(sessionId); if (session) { session.status = 'connected'; session.connectionTime = new Date(); this.sessions.set(sessionId, session); } resolve(); }); client.on('bitmap', (bitmap: any) => { this.handleScreenUpdate(sessionId, bitmap); }); client.on('clipboard', (data: string, format: string) => { this.handleClipboardData(sessionId, data, format); }); client.on('error', (error: Error) => { this.logger.error(`RDP session ${sessionId} error`, error); this.emit('error', sessionId, error); reject(error); }); client.on('close', (reason?: string) => { this.handleSessionClose(sessionId, reason); }); // Setup gateway connection if needed if (options.gatewayHostname) { this.setupGatewayConnection(client, sessionId, options); } // Setup smart card authentication if needed if (options.enableSmartCardAuth) { this.setupSmartCardAuth(client, sessionId, options); } this.connections.set(sessionId, client); client.connect(); } catch (error) { reject(error); } }); } /** * Create fallback RDP session using system tools (mstsc.exe or xfreerdp) */ private async createFallbackSession( sessionId: string, options: RDPConnectionOptions ): Promise<void> { return new Promise((resolve, reject) => { try { const isWindows = process.platform === 'win32'; let command: string; let args: string[]; if (isWindows) { // Use mstsc.exe on Windows const rdpFile = this.generateRDPFile(sessionId, options); command = 'mstsc'; args = [rdpFile]; if (options.fullScreen) { args.push('/f'); } if (options.enableMultiMonitor) { args.push('/multimon'); } if (options.enableRestrictedAdmin) { args.push('/restrictedAdmin'); } if (options.enableRemoteCredentialGuard) { args.push('/remoteGuard'); } } else { // Use xfreerdp on Linux/macOS command = 'xfreerdp'; args = this.buildXFreeRDPArgs(options); } this.logger.info( `Starting fallback RDP process for session ${sessionId}`, { command, args } ); const childProcess = spawn(command, args, { stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env, DISPLAY: ':0' }, }); childProcess.stdout?.on('data', (data: Buffer) => { this.handleProcessOutput(sessionId, data.toString(), 'stdout'); }); childProcess.stderr?.on('data', (data: Buffer) => { this.handleProcessOutput(sessionId, data.toString(), 'stderr'); }); childProcess.on('spawn', () => { this.logger.info(`RDP process spawned for session ${sessionId}`); const session = this.sessions.get(sessionId); if (session) { session.status = 'connected'; session.connectionTime = new Date(); this.sessions.set(sessionId, session); } resolve(); }); process.on('error', (error: Error) => { this.logger.error( `RDP process error for session ${sessionId}`, error ); reject(error); }); childProcess.on('exit', (code: number | null) => { this.handleProcessExit(sessionId, code); }); this.fallbackProcesses.set(sessionId, childProcess); } catch (error) { reject(error); } }); } /** * Build client configuration for node-rdpjs-2 */ private buildClientConfig(options: RDPConnectionOptions): any { const config: any = { host: options.host, port: options.port || 3389, username: options.username, password: options.password, domain: options.domain || '', // Display settings width: options.width || 1920, height: options.height || 1080, colorDepth: options.colorDepth || 32, // Security settings enableNLA: options.enableNLA !== false, enableTLS: options.enableTLS !== false, enableCredSSP: options.enableCredSSP === true, // Performance settings enableBitmapCaching: options.enableBitmapCaching !== false, enableGlyphCaching: options.enableGlyphCaching !== false, compressionLevel: this.mapCompressionLevel(options.compressionLevel), // Audio settings enableAudio: options.audioMode !== 'disabled', audioMode: options.audioMode || 'local', audioQuality: options.audioQuality || 'medium', // Clipboard and file transfer enableClipboard: options.enableClipboard !== false, enableFileTransfer: options.enableFileTransfer === true, enableDriveRedirection: options.enableDriveRedirection === true, // Advanced settings keyboardLayout: options.keyboardLayout || 'en-US', keyboardHook: options.keyboardHook || 'enabled', enableUnicodeKeyboard: options.enableUnicodeKeyboard !== false, // Timeout settings timeout: options.timeout || 30000, keepAliveInterval: options.keepAliveInterval || 30000, }; // Add certificate authentication if provided if (options.certificatePath) { config.certificate = readFileSync(options.certificatePath); if (options.certificatePassword) { config.certificatePassword = options.certificatePassword; } } return config; } /** * Generate RDP file for mstsc.exe */ private generateRDPFile( sessionId: string, options: RDPConnectionOptions ): string { const rdpDir = join(process.cwd(), 'temp', 'rdp'); if (!existsSync(rdpDir)) { mkdirSync(rdpDir, { recursive: true }); } const rdpFilePath = join(rdpDir, `${sessionId}.rdp`); let rdpContent = ''; // Basic connection settings rdpContent += `full address:s:${options.host}:${options.port || 3389}\n`; rdpContent += `username:s:${options.username}\n`; if (options.domain) { rdpContent += `domain:s:${options.domain}\n`; } // Display settings rdpContent += `screen mode id:i:${options.fullScreen ? 2 : 1}\n`; rdpContent += `desktopwidth:i:${options.width || 1920}\n`; rdpContent += `desktopheight:i:${options.height || 1080}\n`; rdpContent += `session bpp:i:${options.colorDepth || 32}\n`; // Multi-monitor settings if (options.enableMultiMonitor && options.monitors) { rdpContent += `use multimon:i:1\n`; options.monitors.forEach((monitor, index) => { rdpContent += `monitor${index}:s:${monitor.x},${monitor.y},${monitor.width},${monitor.height}\n`; }); } // Audio settings rdpContent += `audiomode:i:${this.mapAudioMode(options.audioMode)}\n`; rdpContent += `audiocapturemode:i:${options.enableMicrophone ? 1 : 0}\n`; // Clipboard and file transfer rdpContent += `redirectclipboard:i:${options.enableClipboard ? 1 : 0}\n`; rdpContent += `redirectdrives:i:${options.enableDriveRedirection ? 1 : 0}\n`; rdpContent += `redirectprinters:i:${options.enablePrinterRedirection ? 1 : 0}\n`; // Security settings rdpContent += `authentication level:i:${options.enableNLA ? 2 : 0}\n`; rdpContent += `enablecredsspsupport:i:${options.enableCredSSP ? 1 : 0}\n`; rdpContent += `negotiate security layer:i:1\n`; // Performance settings rdpContent += `compression:i:1\n`; rdpContent += `bitmapcachepersistenable:i:${options.enableBitmapCaching ? 1 : 0}\n`; // Gateway settings if (options.gatewayHostname) { rdpContent += `gatewayhostname:s:${options.gatewayHostname}\n`; rdpContent += `gatewayusagemethod:i:${options.enableGatewayAuth ? 2 : 1}\n`; if (options.gatewayUsername) { rdpContent += `gatewayusername:s:${options.gatewayUsername}\n`; } if (options.gatewayDomain) { rdpContent += `gatewaydomain:s:${options.gatewayDomain}\n`; } } // RemoteApp settings if (options.enableRemoteApp && options.remoteApplicationProgram) { rdpContent += `remoteapplicationmode:i:1\n`; rdpContent += `remoteapplicationprogram:s:${options.remoteApplicationProgram}\n`; if (options.remoteApplicationArgs) { rdpContent += `remoteapplicationcmdline:s:${options.remoteApplicationArgs}\n`; } if (options.remoteApplicationDir) { rdpContent += `remoteapplicationdir:s:${options.remoteApplicationDir}\n`; } if (options.remoteApplicationName) { rdpContent += `remoteapplicationname:s:${options.remoteApplicationName}\n`; } } // Advanced settings if (options.alternateShell) { rdpContent += `alternate shell:s:${options.alternateShell}\n`; } if (options.shellWorkingDir) { rdpContent += `shell working directory:s:${options.shellWorkingDir}\n`; } // Load balance info if (options.loadBalanceInfo) { rdpContent += `loadbalanceinfo:s:${options.loadBalanceInfo}\n`; } // Custom settings if (options.additionalSettings) { Object.entries(options.additionalSettings).forEach(([key, value]) => { rdpContent += `${key}:s:${value}\n`; }); } writeFileSync(rdpFilePath, rdpContent, 'utf8'); return rdpFilePath; } /** * Build xfreerdp command arguments */ private buildXFreeRDPArgs(options: RDPConnectionOptions): string[] { const args: string[] = []; // Basic connection args.push(`/v:${options.host}:${options.port || 3389}`); args.push(`/u:${options.username}`); if (options.password) { args.push(`/p:${options.password}`); } if (options.domain) { args.push(`/d:${options.domain}`); } // Display settings args.push(`/w:${options.width || 1920}`); args.push(`/h:${options.height || 1080}`); args.push(`/bpp:${options.colorDepth || 32}`); if (options.fullScreen) { args.push('/f'); } // Multi-monitor support if (options.enableMultiMonitor) { args.push('/multimon'); } // Audio settings if (options.audioMode === 'disabled') { args.push('/audio-mode:2'); } else if (options.audioMode === 'remote') { args.push('/audio-mode:1'); } else { args.push('/audio-mode:0'); } if (options.enableMicrophone) { args.push('/microphone'); } // Clipboard and file transfer if (options.enableClipboard) { args.push('+clipboard'); } else { args.push('-clipboard'); } if (options.enableDriveRedirection) { args.push('/drive:home,/home'); if (options.redirectedDrives) { options.redirectedDrives.forEach((drive) => { args.push(`/drive:${drive},${drive}`); }); } } // Performance settings if (options.compressionLevel !== 'none') { args.push('+compression'); } if (options.enableBitmapCaching) { args.push('+bitmap-cache'); } if (options.enableRemoteFX) { args.push('+rfx'); } if (options.enableGPUAcceleration) { args.push('/gfx:AVC444'); } // Security settings if (options.enableNLA) { args.push('/nla'); } if (options.enableTLS) { args.push('/tls'); } if (options.ignoreCertificateErrors) { args.push('/cert:ignore'); } // Gateway settings if (options.gatewayHostname) { args.push(`/g:${options.gatewayHostname}:${options.gatewayPort || 443}`); if (options.gatewayUsername) { args.push(`/gu:${options.gatewayUsername}`); } if (options.gatewayPassword) { args.push(`/gp:${options.gatewayPassword}`); } if (options.gatewayDomain) { args.push(`/gd:${options.gatewayDomain}`); } } // RemoteApp settings if (options.enableRemoteApp && options.remoteApplicationProgram) { args.push(`/app:${options.remoteApplicationProgram}`); if (options.remoteApplicationArgs) { args.push(`/app-cmd:${options.remoteApplicationArgs}`); } if (options.remoteApplicationDir) { args.push(`/app-workdir:${options.remoteApplicationDir}`); } } // Timeout settings if (options.timeout) { args.push(`/timeout:${Math.floor(options.timeout / 1000)}`); } return args; } /** * Setup session monitoring */ private setupSessionMonitoring(sessionId: string): void { const monitor = setInterval(() => { this.updatePerformanceMetrics(sessionId); }, 5000); // Update every 5 seconds this.performanceMonitors.set(sessionId, monitor); } /** * Setup session recording */ private setupSessionRecording( sessionId: string, options: RDPConnectionOptions ): void { if (!options.recordingPath) return; try { const recordingDir = dirname(options.recordingPath); if (!existsSync(recordingDir)) { mkdirSync(recordingDir, { recursive: true }); } // Initialize recording metadata const recordingInfo = { sessionId, startTime: new Date(), filePath: options.recordingPath, format: options.recordingFormat || 'mp4', quality: options.recordingQuality || 'medium', frameCount: 0, duration: 0, }; this.sessionRecordings.set(sessionId, recordingInfo); this.logger.info( `Session recording started for ${sessionId}`, recordingInfo ); } catch (error) { this.logger.error( `Failed to setup session recording for ${sessionId}`, error ); } } /** * Setup gateway connection */ private setupGatewayConnection( client: any, sessionId: string, options: RDPConnectionOptions ): void { const gatewayConfig: RDPGatewayConfig = { hostname: options.gatewayHostname!, port: options.gatewayPort || 443, username: options.gatewayUsername, password: options.gatewayPassword, domain: options.gatewayDomain, accessToken: options.gatewayAccessToken, enableAuth: options.enableGatewayAuth !== false, }; client.setupGateway(gatewayConfig); client.on('gateway-authenticated', (info: any) => { this.logger.info( `RDP Gateway authenticated for session ${sessionId}`, info ); this.emit('gateway-authenticated', sessionId, info); }); } /** * Setup smart card authentication */ private setupSmartCardAuth( client: any, sessionId: string, options: RDPConnectionOptions ): void { client.enableSmartCard(true); client.on('smart-card-request', (request: SmartCardRequest) => { this.logger.info(`Smart card request for session ${sessionId}`, request); this.emit('smart-card-request', sessionId, request); }); if (options.smartCardPin) { client.on('smart-card-pin-required', () => { client.provideSmartCardPin(options.smartCardPin); }); } } /** * Handle screen updates */ private handleScreenUpdate(sessionId: string, bitmap: any): void { try { // Convert bitmap to image buffer const canvas = createCanvas(bitmap.width, bitmap.height); const ctx = canvas.getContext('2d'); // Create ImageData from bitmap const imageData = ctx.createImageData(bitmap.width, bitmap.height); imageData.data.set(bitmap.data); ctx.putImageData(imageData, 0, 0); const imageBuffer = canvas.toBuffer('image/png'); this.emit('screen-update', sessionId, imageBuffer); // Update recording if active const recording = this.sessionRecordings.get(sessionId); if (recording) { recording.frameCount++; recording.duration = Date.now() - recording.startTime.getTime(); // Add frame to recording (implementation depends on recording format) } } catch (error) { this.logger.error( `Error handling screen update for session ${sessionId}`, error ); } } /** * Handle clipboard data */ private handleClipboardData( sessionId: string, data: string, format: string ): void { this.clipboardBuffer.set(sessionId, data); this.emit('clipboard-data', sessionId, data, format); } /** * Handle session close */ private handleSessionClose(sessionId: string, reason?: string): void { this.logger.info(`RDP session ${sessionId} closed`, { reason }); const session = this.sessions.get(sessionId); if (session) { session.status = 'disconnected'; session.lastActivity = new Date(); this.sessions.set(sessionId, session); } // Cleanup resources this.cleanup(sessionId); this.emit('disconnected', sessionId, reason); } /** * Handle process output for fallback sessions */ private handleProcessOutput( sessionId: string, data: string, type: 'stdout' | 'stderr' ): void { const output: ConsoleOutput = { sessionId, type, data, timestamp: new Date(), raw: data, }; this.emit('output', output); // Update session stats const session = this.sessions.get(sessionId); if (session) { session.lastActivity = new Date(); if (type === 'stdout') { session.bytesReceived += Buffer.byteLength(data); } this.sessions.set(sessionId, session); } } /** * Handle process exit for fallback sessions */ private handleProcessExit(sessionId: string, code: number | null): void { this.logger.info(`RDP process exited for session ${sessionId}`, { exitCode: code, }); const session = this.sessions.get(sessionId); if (session) { session.status = code === 0 ? 'disconnected' : 'failed'; session.lastActivity = new Date(); this.sessions.set(sessionId, session); } this.cleanup(sessionId); this.emit('disconnected', sessionId, `Process exited with code ${code}`); } /** * Update performance metrics */ private updatePerformanceMetrics(sessionId: string): void { const session = this.sessions.get(sessionId); if (!session || session.status !== 'connected') return; // Simulate performance metrics (in real implementation, gather from RDP client) const metrics: RDPPerformanceMetrics = { timestamp: new Date(), latency: Math.random() * 100 + 10, // 10-110ms bandwidth: Math.random() * 1000000 + 100000, // 100KB-1MB packetLoss: Math.random() * 0.1, // 0-10% frameRate: Math.random() * 10 + 20, // 20-30 fps compressionRatio: Math.random() * 0.5 + 0.3, // 30-80% cpuUsage: Math.random() * 50 + 10, // 10-60% memoryUsage: Math.random() * 1000000000 + 100000000, // 100MB-1GB networkUtilization: Math.random() * 0.8 + 0.1, // 10-90% }; this.emit('performance-metrics', sessionId, metrics); } /** * Send input to RDP session */ async sendRDPInput(sessionId: string, input: string): Promise<void> { const connection = this.connections.get(sessionId); const fallbackProcess = this.fallbackProcesses.get(sessionId); if (connection && connection.sendInput) { connection.sendInput(input); } else if (fallbackProcess && fallbackProcess.stdin) { fallbackProcess.stdin.write(input); } else { throw new Error(`No active connection for session ${sessionId}`); } // Update session activity const session = this.sessions.get(sessionId); if (session) { session.lastActivity = new Date(); session.bytesSent += Buffer.byteLength(input); this.sessions.set(sessionId, session); } } /** * Send key sequence to RDP session */ async sendKeySequence(sessionId: string, keys: string[]): Promise<void> { const connection = this.connections.get(sessionId); if (connection && connection.sendKeySequence) { connection.sendKeySequence(keys); } else { throw new Error(`Key sequence not supported for session ${sessionId}`); } } /** * Send clipboard data to remote session */ async sendClipboardData( sessionId: string, data: string, format: string = 'text' ): Promise<void> { const connection = this.connections.get(sessionId); if (connection && connection.setClipboard) { connection.setClipboard(data, format); } else { this.logger.warn(`Clipboard not supported for session ${sessionId}`); } } /** * Start file transfer */ async startFileTransfer( sessionId: string, localPath: string, remotePath: string, direction: 'upload' | 'download' ): Promise<string> { const transferId = uuidv4(); const connection = this.connections.get(sessionId); if (!connection || !connection.transferFile) { throw new Error(`File transfer not supported for session ${sessionId}`); } const progress: FileTransferProgress = { transferId, fileName: localPath.split('/').pop() || localPath, bytesTransferred: 0, totalBytes: 0, direction, status: 'pending', }; let transfers = this.fileTransfers.get(sessionId) || []; transfers.push(progress); this.fileTransfers.set(sessionId, transfers); try { progress.status = 'in-progress'; const result = await connection.transferFile( localPath, remotePath, direction, (bytesTransferred: number, totalBytes: number) => { progress.bytesTransferred = bytesTransferred; progress.totalBytes = totalBytes; this.emit('file-transfer-progress', sessionId, progress); } ); progress.status = 'completed'; this.emit('file-transfer-progress', sessionId, progress); return transferId; } catch (error) { progress.status = 'failed'; progress.error = (error as Error).message; this.emit('file-transfer-progress', sessionId, progress); throw error; } } /** * Get session information */ getSession(sessionId: string): RDPSession | undefined { return this.sessions.get(sessionId); } /** * Get all sessions */ getAllSessions(): RDPSession[] { return Array.from(this.sessions.values()); } /** * Get system capabilities */ getCapabilities(): RDPCapabilities { return { ...this.rdpCapabilities }; } /** * Disconnect session */ async disconnectSession(sessionId: string): Promise<void> { const connection = this.connections.get(sessionId); const fallbackProcess = this.fallbackProcesses.get(sessionId); if (connection && connection.disconnect) { connection.disconnect(); } else if (fallbackProcess) { fallbackProcess.kill('SIGTERM'); } this.cleanup(sessionId); } /** * Cleanup session resources */ private cleanup(sessionId: string): void { // Clear performance monitoring const monitor = this.performanceMonitors.get(sessionId); if (monitor) { clearInterval(monitor); this.performanceMonitors.delete(sessionId); } // Stop recording const recording = this.sessionRecordings.get(sessionId); if (recording) { // Finalize recording file this.sessionRecordings.delete(sessionId); this.emit('session-recording-update', sessionId, recording.filePath); } // Clear connections this.connections.delete(sessionId); this.fallbackProcesses.delete(sessionId); // Clear buffers this.clipboardBuffer.delete(sessionId); this.fileTransfers.delete(sessionId); } /** * Helper methods */ private mapCompressionLevel(level?: string): number { switch (level) { case 'none': return 0; case 'low': return 1; case 'medium': return 2; case 'high': return 3; default: return 2; } } private mapAudioMode(mode?: string): number { switch (mode) { case 'local': return 0; case 'remote': return 1; case 'disabled': return 2; default: return 0; } } // IProtocol required methods async initialize(): Promise<void> { // RDP initialization is handled in connect methods } async createSession(options: SessionOptions): Promise<ConsoleSession> { const sessionId = `rdp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; if (!options.rdpOptions) { throw new Error('RDP options are required'); } // Create the actual RDP session await this.createRDPSession(sessionId, options.rdpOptions); const session: ConsoleSession = { id: sessionId, command: options.command || '', args: options.args || [], cwd: '/', env: options.environment || {}, createdAt: new Date(), status: 'running', type: 'rdp', streaming: options.streaming ?? true, rdpOptions: options.rdpOptions, executionState: 'idle', activeCommands: new Map(), }; this.emit('sessionCreated', session); return session; } async executeCommand( sessionId: string, command: string, args?: string[] ): Promise<void> { // RDP doesn't execute commands directly, but we can emit events this.emit('commandExecuted', { sessionId, command, args, timestamp: new Date(), }); } async sendInput(sessionId: string, input: string): Promise<void> { await this.sendRDPInput(sessionId, input); } async getOutput(sessionId: string, since?: Date): Promise<string> { // RDP output is screen updates, not text return 'RDP screen output (binary data)'; } async closeSession(sessionId: string): Promise<void> { await this.disconnectSession(sessionId); } async getHealthStatus(): Promise<ProtocolHealthStatus> { this.healthStatus.lastChecked = new Date(); this.healthStatus.metrics.activeSessions = this.sessions.size; this.healthStatus.isHealthy = this.healthStatus.errors.length === 0; return { ...this.healthStatus }; } async dispose(): Promise<void> { await this.destroy(); } /** * Destroy protocol and cleanup all resources */ async destroy(): Promise<void> { this.logger.info('Destroying RDP Protocol'); // Disconnect all sessions const sessionIds = Array.from(this.sessions.keys()); await Promise.all(sessionIds.map((id) => this.disconnectSession(id))); // Clear all data structures this.sessions.clear(); this.connections.clear(); this.fallbackProcesses.clear(); this.sessionRecordings.clear(); this.clipboardBuffer.clear(); this.fileTransfers.clear(); // Clear all intervals this.performanceMonitors.forEach((monitor) => clearInterval(monitor)); this.performanceMonitors.clear(); this.removeAllListeners(); } } export default RDPProtocol;

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