Skip to main content
Glama
ooples

MCP Console Automation Server

SimpleCommandExecutor.ts8.04 kB
import { spawn, execSync, exec, ChildProcess } from 'child_process'; import { EventEmitter } from 'events'; import { promisify } from 'util'; import { Logger } from '../utils/logger.js'; import { SessionOptions, SSHConnectionOptions } from '../types/index.js'; const execAsync = promisify(exec); /** * Simple command executor that bypasses the complex session/event system * Provides direct command execution with minimal overhead */ export class SimpleCommandExecutor { private logger: Logger; constructor() { this.logger = new Logger('SimpleCommandExecutor'); } /** * Alternative 1: Direct synchronous execution using execSync * Best for: Simple commands that complete quickly * Limitations: No streaming output, blocks until completion */ async executeSyncCommand( command: string, args: string[] = [], options: { cwd?: string; env?: Record<string, string>; timeout?: number; encoding?: BufferEncoding; } = {} ): Promise<{ output: string; exitCode: number }> { try { const fullCommand = args.length > 0 ? `${command} ${args.join(' ')}` : command; const output = execSync(fullCommand, { cwd: options.cwd || process.cwd(), env: { ...process.env, ...options.env }, timeout: options.timeout || 120000, encoding: options.encoding || 'utf8', maxBuffer: 1024 * 1024 * 10, // 10MB buffer }); return { output: output.toString(), exitCode: 0, }; } catch (error: any) { // execSync throws on non-zero exit codes return { output: error.stdout ? error.stdout.toString() + error.stderr?.toString() : error.message, exitCode: error.status || 1, }; } } /** * Alternative 2: Promise-based async execution * Best for: Commands that may take longer but don't need streaming * Benefits: Non-blocking, timeout support, better error handling */ async executeAsyncCommand( command: string, args: string[] = [], options: { cwd?: string; env?: Record<string, string>; timeout?: number; maxBuffer?: number; } = {} ): Promise<{ output: string; exitCode: number }> { try { const fullCommand = args.length > 0 ? `${command} ${args.join(' ')}` : command; const { stdout, stderr } = await execAsync(fullCommand, { cwd: options.cwd || process.cwd(), env: { ...process.env, ...options.env }, timeout: options.timeout || 120000, maxBuffer: options.maxBuffer || 1024 * 1024 * 10, }); return { output: stdout + stderr, exitCode: 0, }; } catch (error: any) { return { output: (error.stdout || '') + (error.stderr || '') || error.message, exitCode: error.code || 1, }; } } /** * Alternative 3: Streaming execution with spawn * Best for: Long-running commands that produce output over time * Benefits: Real-time output, process control, streaming */ async executeStreamingCommand( command: string, args: string[] = [], options: { cwd?: string; env?: Record<string, string>; timeout?: number; onOutput?: (data: string, isError: boolean) => void; } = {} ): Promise<{ output: string; exitCode: number }> { return new Promise((resolve, reject) => { let output = ''; let timeoutHandle: NodeJS.Timeout | null = null; const child = spawn(command, args, { cwd: options.cwd || process.cwd(), env: { ...process.env, ...options.env }, stdio: ['pipe', 'pipe', 'pipe'], }); // Set up timeout if (options.timeout) { timeoutHandle = setTimeout(() => { child.kill('SIGTERM'); reject(new Error(`Command timeout after ${options.timeout}ms`)); }, options.timeout); } // Collect output child.stdout?.on('data', (data) => { const text = data.toString(); output += text; options.onOutput?.(text, false); }); child.stderr?.on('data', (data) => { const text = data.toString(); output += text; options.onOutput?.(text, true); }); child.on('error', (error) => { if (timeoutHandle) clearTimeout(timeoutHandle); reject(error); }); child.on('close', (exitCode) => { if (timeoutHandle) clearTimeout(timeoutHandle); resolve({ output, exitCode: exitCode || 0, }); }); }); } /** * Alternative 4: SSH execution using native SSH client * Best for: Simple SSH commands without persistent sessions * Benefits: No session management, direct execution */ async executeSSHCommand( command: string, sshOptions: SSHConnectionOptions, options: { timeout?: number; args?: string[]; } = {} ): Promise<{ output: string; exitCode: number }> { // Build SSH command const sshArgs: string[] = [ '-o', 'ConnectTimeout=10', '-o', 'ServerAliveInterval=60', '-o', 'ServerAliveCountMax=3', ]; // Add port if specified if (sshOptions.port && sshOptions.port !== 22) { sshArgs.push('-p', sshOptions.port.toString()); } // Add key authentication if (sshOptions.privateKeyPath) { sshArgs.push('-i', sshOptions.privateKeyPath); } // Add strict host key checking control sshArgs.push('-o', 'StrictHostKeyChecking=no'); // Build connection string const connectionString = `${sshOptions.username}@${sshOptions.host}`; sshArgs.push(connectionString); // Add the command to execute const fullCommand = options.args && options.args.length > 0 ? `${command} ${options.args.join(' ')}` : command; sshArgs.push(fullCommand); // Execute using spawn for better control return this.executeStreamingCommand('ssh', sshArgs, { timeout: options.timeout || 120000, }); } /** * Alternative 5: Smart command executor that chooses the best method * Automatically selects the most appropriate execution method based on context */ async executeSmartCommand( command: string, args: string[] = [], options: Partial<SessionOptions> = {} ): Promise<{ output: string; exitCode: number; method: string; }> { // SSH commands if (options.sshOptions) { try { const result = await this.executeSSHCommand( command, options.sshOptions, { timeout: options.timeout, args, } ); return { ...result, method: 'ssh' }; } catch (error: any) { // Fallback to error result return { output: `SSH execution failed: ${error.message}`, exitCode: 1, method: 'ssh-failed', }; } } // Long-running or interactive commands const interactiveCommands = [ 'docker', 'kubectl', 'npm', 'node', 'python', 'pip', ]; const isInteractive = interactiveCommands.some((cmd) => command.toLowerCase().includes(cmd) ); if (isInteractive || (options.timeout && options.timeout > 30000)) { const result = await this.executeStreamingCommand(command, args, { cwd: options.cwd, env: options.env, timeout: options.timeout, }); return { ...result, method: 'streaming' }; } // Quick commands if (!options.timeout || options.timeout <= 30000) { const result = await this.executeAsyncCommand(command, args, { cwd: options.cwd, env: options.env, timeout: options.timeout, }); return { ...result, method: 'async' }; } // Default fallback const result = await this.executeAsyncCommand(command, args, { cwd: options.cwd, env: options.env, timeout: options.timeout, }); return { ...result, method: 'async-fallback' }; } }

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