Skip to main content
Glama
ooples

MCP Console Automation Server

RustProtocol.ts16.6 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'; // Rust Protocol connection options interface RustConnectionOptions extends SessionOptions { rustcPath?: string; cargoPath?: string; rustupPath?: string; toolchain?: string; target?: string; profile?: 'dev' | 'release' | 'test' | 'bench'; features?: string[]; allFeatures?: boolean; noDefaultFeatures?: boolean; package?: string; bin?: string; example?: string; test?: string; bench?: string; cargoFlags?: string[]; rustcFlags?: string[]; sourceFile?: string; cratePath?: string; enableBacktrace?: boolean; enableDebug?: boolean; debugTool?: 'gdb' | 'lldb' | 'rr' | 'valgrind'; enableProfiling?: boolean; profilingTool?: 'perf' | 'valgrind' | 'cargo-profdata'; environment?: Record<string, string>; crossCompile?: boolean; linker?: string; rustFlags?: string[]; manifestPath?: string; workspaceRoot?: string; } /** * Rust Protocol Implementation * * Provides Rust development environment console access through cargo and rustc commands * Supports building, running, testing, cross-compilation, debugging, and profiling */ export class RustProtocol extends BaseProtocol { public readonly type: ConsoleType = 'rust'; public readonly capabilities: ProtocolCapabilities; private rustProcesses = 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('rust'); this.capabilities = { supportsStreaming: true, supportsFileTransfer: false, supportsX11Forwarding: false, supportsPortForwarding: false, supportsAuthentication: false, supportsEncryption: false, supportsCompression: false, supportsMultiplexing: false, supportsKeepAlive: true, supportsReconnection: true, supportsBinaryData: false, supportsCustomEnvironment: true, supportsWorkingDirectory: true, supportsSignals: true, supportsResizing: false, supportsPTY: true, maxConcurrentSessions: 30, defaultTimeout: 60000, // Rust builds can take longer supportedEncodings: ['utf-8'], supportedAuthMethods: [], platformSupport: { windows: true, linux: true, macos: true, freebsd: true, }, }; } async initialize(): Promise<void> { if (this.isInitialized) return; try { // Check if Rust/Cargo is available await this.checkRustAvailability(); this.isInitialized = true; this.logger.info('Rust protocol initialized with production features'); } catch (error: any) { this.logger.error('Failed to initialize Rust protocol', error); throw error; } } async createSession(options: SessionOptions): Promise<ConsoleSession> { const sessionId = `rust-${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 rustProcess = this.rustProcesses.get(sessionId); const session = this.sessions.get(sessionId); if (!rustProcess || !rustProcess.stdin || !session) { throw new Error(`No active Rust session: ${sessionId}`); } rustProcess.stdin.write(input); session.lastActivity = new Date(); this.emit('input-sent', { sessionId, input, timestamp: new Date(), }); this.logger.debug( `Sent input to Rust session ${sessionId}: ${input.substring(0, 100)}` ); } async closeSession(sessionId: string): Promise<void> { try { const rustProcess = this.rustProcesses.get(sessionId); if (rustProcess) { // Try graceful shutdown first rustProcess.kill('SIGTERM'); // Force kill after timeout (longer for Rust builds) setTimeout(() => { if (rustProcess && !rustProcess.killed) { rustProcess.kill('SIGKILL'); } }, 10000); this.rustProcesses.delete(sessionId); } // Clean up base protocol session this.sessions.delete(sessionId); this.logger.info(`Rust session ${sessionId} closed`); this.emit('session-closed', sessionId); } catch (error) { this.logger.error(`Error closing Rust session ${sessionId}:`, error); throw error; } } async doCreateSession( sessionId: string, options: SessionOptions, sessionState: SessionState ): Promise<ConsoleSession> { if (!this.isInitialized) { await this.initialize(); } const rustOptions = options as RustConnectionOptions; // Build Rust command const rustCommand = this.buildRustCommand(rustOptions); // Spawn Rust process const rustProcess = spawn(rustCommand[0], rustCommand.slice(1), { stdio: ['pipe', 'pipe', 'pipe'], cwd: options.cwd || process.cwd(), env: { ...process.env, ...this.buildEnvironment(rustOptions), ...options.env, }, }); // Set up output handling rustProcess.stdout?.on('data', (data) => { const output: ConsoleOutput = { sessionId, type: 'stdout', data: data.toString(), timestamp: new Date(), }; this.addToOutputBuffer(sessionId, output); }); rustProcess.stderr?.on('data', (data) => { const output: ConsoleOutput = { sessionId, type: 'stderr', data: data.toString(), timestamp: new Date(), }; this.addToOutputBuffer(sessionId, output); }); rustProcess.on('error', (error) => { this.logger.error(`Rust process error for session ${sessionId}:`, error); this.emit('session-error', { sessionId, error }); }); rustProcess.on('close', (code) => { this.logger.info( `Rust process closed for session ${sessionId} with code ${code}` ); this.markSessionComplete(sessionId, code || 0); }); // Store the process this.rustProcesses.set(sessionId, rustProcess); // Create session object const session: ConsoleSession = { id: sessionId, command: rustCommand[0], args: rustCommand.slice(1), cwd: options.cwd || process.cwd(), env: { ...process.env, ...this.buildEnvironment(rustOptions), ...options.env, }, createdAt: new Date(), lastActivity: new Date(), status: 'running', type: this.type, streaming: options.streaming, executionState: 'idle', activeCommands: new Map(), pid: rustProcess.pid, }; this.sessions.set(sessionId, session); this.logger.info( `Rust session ${sessionId} created for ${rustOptions.sourceFile || rustOptions.package || rustOptions.cratePath || 'Rust application'}` ); this.emit('session-created', { sessionId, type: 'rust', 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: true, // Rust builds are typically one-shot unless running a server isPersistent: false, createdAt: session.createdAt, lastActivity: session.lastActivity, pid: session.pid, metadata: {}, }; } async handleError( error: Error, context: ErrorContext ): Promise<ErrorRecoveryResult> { this.logger.error( `Error in Rust session ${context.sessionId}: ${error.message}` ); return { recovered: false, strategy: 'none', attempts: 0, duration: 0, error: error.message, }; } async recoverSession(sessionId: string): Promise<boolean> { const rustProcess = this.rustProcesses.get(sessionId); return (rustProcess && !rustProcess.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.rustProcesses.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.checkRustAvailability(); return { ...baseStatus, dependencies: { rust: { available: true }, cargo: { available: true }, }, }; } catch (error) { return { ...baseStatus, isHealthy: false, errors: [...baseStatus.errors, `Rust not available: ${error}`], dependencies: { rust: { available: false }, cargo: { available: false }, }, }; } } private async checkRustAvailability(): Promise<void> { return new Promise((resolve, reject) => { // Check for cargo first since it's the primary tool const testProcess = spawn('cargo', ['--version'], { stdio: 'pipe' }); testProcess.on('close', (code) => { if (code === 0) { resolve(); } else { reject( new Error( 'Rust toolchain not found. Please install Rust via rustup.' ) ); } }); testProcess.on('error', () => { reject( new Error('Rust toolchain not found. Please install Rust via rustup.') ); }); }); } private buildRustCommand(options: RustConnectionOptions): string[] { const command = []; // Debug with external tools if (options.enableDebug && options.debugTool) { switch (options.debugTool) { case 'gdb': command.push('gdb', '--args'); break; case 'lldb': command.push('lldb', '--'); break; case 'rr': command.push('rr', 'record'); break; case 'valgrind': command.push('valgrind'); break; } } // Profiling with external tools if (options.enableProfiling && options.profilingTool) { switch (options.profilingTool) { case 'perf': command.push('perf', 'record', '--call-graph=dwarf'); break; case 'valgrind': command.push('valgrind', '--tool=callgrind'); break; } } // Primary command: cargo vs rustc if (options.sourceFile && !options.cratePath) { // Direct rustc compilation if (options.rustcPath) { command.push(options.rustcPath); } else { command.push('rustc'); } // Rustc flags if (options.rustcFlags) { command.push(...options.rustcFlags); } // Target specification if (options.target) { command.push('--target', options.target); } // Source file command.push(options.sourceFile); } else { // Cargo-based project if (options.cargoPath) { command.push(options.cargoPath); } else { command.push('cargo'); } // Determine cargo subcommand if (options.test) { command.push('test'); if (options.test !== 'all') { command.push(options.test); } } else if (options.bench) { command.push('bench'); if (options.bench !== 'all') { command.push(options.bench); } } else if (options.example) { command.push('run', '--example', options.example); } else if (options.bin) { command.push('run', '--bin', options.bin); } else { command.push('run'); } // Build profile if (options.profile && options.profile !== 'dev') { command.push('--profile', options.profile); } // Release build if (options.profile === 'release') { command.push('--release'); } // Package selection if (options.package) { command.push('--package', options.package); } // Manifest path if (options.manifestPath) { command.push('--manifest-path', options.manifestPath); } // Target specification if (options.target) { command.push('--target', options.target); } // Features if (options.allFeatures) { command.push('--all-features'); } else if (options.noDefaultFeatures) { command.push('--no-default-features'); } else if (options.features && options.features.length > 0) { command.push('--features', options.features.join(',')); } // Cargo flags if (options.cargoFlags) { command.push(...options.cargoFlags); } } // Application arguments if (options.args) { command.push('--'); command.push(...options.args); } return command; } private buildEnvironment( options: RustConnectionOptions ): Record<string, string> { const env: Record<string, string> = {}; // Rust toolchain selection if (options.toolchain) { env.RUSTUP_TOOLCHAIN = options.toolchain; } // Cross-compilation target if (options.target) { env.CARGO_BUILD_TARGET = options.target; } // Custom linker if (options.linker) { env.CARGO_TARGET_LINKER = options.linker; } // Rust flags if (options.rustFlags) { env.RUSTFLAGS = options.rustFlags.join(' '); } // Backtrace settings if (options.enableBacktrace) { env.RUST_BACKTRACE = '1'; } // Debug settings if (options.enableDebug) { env.RUST_LOG = 'debug'; } // Profiling if (options.enableProfiling) { env.CARGO_PROFILE_RELEASE_DEBUG = 'true'; } // Workspace root if (options.workspaceRoot) { env.CARGO_WORKSPACE_DIR = options.workspaceRoot; } // Custom environment variables if (options.environment) { Object.assign(env, options.environment); } return env; } async cleanup(): Promise<void> { this.logger.info('Cleaning up Rust protocol'); // Close all Rust processes for (const [sessionId, process] of Array.from(this.rustProcesses)) { try { process.kill(); } catch (error) { this.logger.error( `Error killing Rust process for session ${sessionId}:`, error ); } } // Clear all data this.rustProcesses.clear(); // Call parent cleanup await super.cleanup(); } } export default RustProtocol;

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