Skip to main content
Glama
ooples

MCP Console Automation Server

ENHANCED_IMPLEMENTATION_PLAN.md20.1 kB
# Enhanced Race Condition Solution Plan ## Event-Driven Architecture + Smart Execute **Version:** 2.0 **Date:** 2025-09-27 **Status:** Implementation Ready --- ## Executive Summary After re-analysis using sequential thinking MCP, the comprehensive solution addresses THREE problems: 1. **Architectural Inefficiency:** `console_execute_command` polls every 100ms despite having event-driven completion detection 2. **User Guidance Problem:** Error messages guide users to wrong APIs (sendInput/getOutput) 3. **UX Complexity:** Users must understand and choose between 3 execution patterns **Solution:** Three-phase implementation that fixes the architecture, updates documentation, and provides intelligent execution. **Total Effort:** 22-30 hours across 3 phases **Risk:** Low-Medium (incremental deployment with value at each phase) --- ## Problem Analysis ### Problem 1: Polling Inefficiency **Current Implementation (ConsoleManager.ts lines 1057-1105):** ```typescript private async waitForCommandCompletion(commandId: string, timeout: number) { const checkInterval = 100; // Check every 100ms return new Promise((resolve) => { const checkCompletion = () => { // Check if command status changed if (currentExecution.status !== 'executing') { resolve({...}); return; } // Continue polling setTimeout(checkCompletion, checkInterval); }; checkCompletion(); }); } ``` **Issues:** - CPU waste: Checks every 100ms even when idle - Latency: Up to 100ms delay in detecting completion - Scalability: Multiple concurrent commands = many polling loops - Not event-driven: Despite having event-based detection at line 7910 **Root Cause:** Completion is detected via events (line 7916: `completeCommandExecution` called from data event), but wrapped in polling. ### Problem 2: User Guidance (Covered in Original Plan) Error messages at lines 1402, 1461 incorrectly suggest using `sendInput` + `waitForOutput`. ### Problem 3: UX Complexity Users must choose between 3 patterns: - `console_execute_command` - might timeout or hit token limits - Streaming session - complex setup, manual pagination - Background job - async, need to poll for completion **Better UX:** One intelligent tool that does the right thing automatically. --- ## Solution Architecture ### Three-Phase Implementation ``` Phase 1: Event-Driven Completion (4-6 hours) ↓ Fixes architectural inefficiency Phase 2: Documentation Fixes (6-8 hours) ↓ Prevents wrong API usage Phase 3: Smart Execute (12-16 hours) ↓ Best UX with intelligent execution ``` **Total:** 22-30 hours, deployable incrementally --- ## Phase 1: Event-Driven Completion Fix **Priority:** HIGH (Architectural Fix) **Effort:** 4-6 hours **Risk:** Low (internal refactoring) ### Current vs. Proposed **Current Flow:** ``` Data arrives → detectCommandCompletion (line 7910) → completeCommandExecution (line 7916) → Updates status to 'completed' Meanwhile: waitForCommandCompletion polls status every 100ms ``` **Proposed Flow:** ``` Data arrives → detectCommandCompletion (line 7910) → completeCommandExecution (line 7916) → Emits 'command-completed' event waitForCommandCompletion awaits event (zero latency, zero CPU) ``` ### Implementation Steps #### Step 1.1: Add Event Emission (ConsoleManager.ts) **File:** `src/core/ConsoleManager.ts` **Location:** `completeCommandExecution` method (around line 917) ```typescript private completeCommandExecution(commandId: string, exitCode?: number): void { const execution = this.commandExecutions.get(commandId); if (!execution) return; execution.status = 'completed'; execution.exitCode = exitCode; execution.completedAt = new Date(); execution.duration = execution.completedAt.getTime() - execution.startedAt.getTime(); const session = this.sessions.get(execution.sessionId); if (session) { session.executionState = 'idle'; session.lastCommandCompletedAt = new Date(); session.currentCommandId = undefined; } // NEW: Emit completion event this.emit('command-completed', { commandId, exitCode, duration: execution.duration, status: execution.status }); } ``` #### Step 1.2: Replace Polling with Event Await **File:** `src/core/ConsoleManager.ts` **Location:** `waitForCommandCompletion` method (lines 1057-1105) **Replace entire method with:** ```typescript private async waitForCommandCompletion(commandId: string, timeout: number): Promise<{ exitCode?: number; duration: number; status: 'completed' | 'failed' | 'timeout'; }> { const commandExecution = this.commandExecutions.get(commandId); if (!commandExecution) { throw new Error(`Command execution ${commandId} not found`); } const startTime = Date.now(); return new Promise((resolve, reject) => { // Setup timeout const timeoutHandle = setTimeout(() => { this.removeListener('command-completed', completionListener); this.completeCommandExecution(commandId, -1); resolve({ exitCode: -1, duration: Date.now() - startTime, status: 'timeout' }); }, timeout); // Setup completion listener const completionListener = (event: any) => { if (event.commandId === commandId) { clearTimeout(timeoutHandle); this.removeListener('command-completed', completionListener); resolve({ exitCode: event.exitCode, duration: event.duration || (Date.now() - startTime), status: event.status === 'completed' ? 'completed' : 'failed' }); } }; this.on('command-completed', completionListener); // Check if already completed (race condition protection) const currentExecution = this.commandExecutions.get(commandId); if (currentExecution && currentExecution.status !== 'executing') { clearTimeout(timeoutHandle); this.removeListener('command-completed', completionListener); resolve({ exitCode: currentExecution.exitCode, duration: currentExecution.duration || (Date.now() - startTime), status: currentExecution.status === 'completed' ? 'completed' : 'failed' }); } }); } ``` #### Step 1.3: Add Event Types **File:** `src/types/index.ts` ```typescript export interface CommandCompletedEvent { commandId: string; exitCode?: number; duration: number; status: 'completed' | 'failed' | 'timeout'; } ``` ### Testing Phase 1 ```typescript // Test 1: Verify zero latency const start = Date.now(); await executeCommand({ sessionId, command: 'echo test' }); const latency = Date.now() - start; assert(latency < 150); // Should be <150ms (previously 100-200ms due to polling) // Test 2: Verify no CPU waste during idle const cpuBefore = process.cpuUsage(); await sleep(1000); // Wait with active session const cpuAfter = process.cpuUsage(); const cpuUsed = (cpuAfter.user - cpuBefore.user) / 1000; assert(cpuUsed < 10); // Should use <10ms CPU during 1s idle // Test 3: Verify concurrent commands scale const commands = Array(100).fill(0).map(() => executeCommand({ sessionId, command: 'echo test' }) ); await Promise.all(commands); // Should complete without performance degradation ``` --- ## Phase 2: Documentation Fixes **Priority:** HIGH (Prevents User Errors) **Effort:** 6-8 hours **Risk:** Low (documentation only) Same as original plan - fix error messages and tool descriptions. --- ## Phase 3: Smart Execute Implementation **Priority:** MEDIUM-HIGH (Best UX) **Effort:** 12-16 hours **Risk:** Medium (new feature) ### Concept **User Experience:** ```typescript // Single tool, works for everything const result = await executeCommand({ sessionId, command: "any-command-here" }); // System automatically: // - Analyzes command // - Picks best strategy // - Switches mid-execution if needed // - Returns unified result ``` **Under the hood:** Intelligent strategy selection and switching. ### Architecture ``` ┌─────────────────────────────────────────┐ │ console_execute_smart │ │ (New tool - recommended for all users) │ └──────────────┬──────────────────────────┘ │ ▼ ┌──────────────────────┐ │ Smart Executor │ │ - Command Analyzer │ │ - Strategy Selector │ │ - Runtime Monitor │ │ - Result Aggregator │ └──────────┬───────────┘ │ ┌──────────┴─────────────┐ │ │ ▼ ▼ ┌────────────┐ ┌────────────────┐ │ Fast Path │ │ Adaptive Path │ │ (execute) │ │ (monitor → │ │ │ │ switch) │ └────────────┘ └────────────────┘ │ │ │ ┌───────────────────┴─────────────┐ │ │ │ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────────┐ ┌──────────────┐ │ execute_cmd │ │ streaming session│ │ background │ │ │ │ │ │ job │ └──────────────┘ └──────────────────┘ └──────────────┘ ``` ### Smart Executor Algorithm ```typescript class SmartExecutor { async execute(command: string, options: ExecuteOptions): Promise<UnifiedResult> { // Step 1: Analyze command const analysis = this.analyzeCommand(command); // Step 2: Select initial strategy const strategy = this.selectStrategy(analysis); // Step 3: Execute with monitoring if (strategy === 'fast') { return await this.executeFastPath(command, options); } else if (strategy === 'streaming') { return await this.executeStreaming(command, options); } else { // 'background' return await this.executeBackground(command, options); } } private analyzeCommand(command: string): CommandAnalysis { // Known long-running patterns const longRunners = [ /npm\s+(install|ci|update)/, /pip\s+install/, /cargo\s+build/, /mvn\s+clean\s+install/, /gradle\s+build/, /(train|build|compile|bundle|webpack)/i ]; // Known quick commands const quickCommands = [ /^(ls|pwd|echo|cat|head|tail|grep|find)/, /^git\s+(status|log|diff|branch)/ ]; // Known streaming commands const streamingCommands = [ /tail\s+-f/, /watch\s+/, /journalctl\s+-f/ ]; return { isLongRunner: longRunners.some(r => r.test(command)), isQuick: quickCommands.some(r => r.test(command)), isStreaming: streamingCommands.some(r => r.test(command)), estimatedDuration: this.estimateFromHistory(command) }; } private selectStrategy(analysis: CommandAnalysis): 'fast' | 'streaming' | 'background' { if (analysis.isStreaming) return 'streaming'; if (analysis.isLongRunner) return 'background'; if (analysis.isQuick) return 'fast'; // Unknown commands: start with fast path return 'fast'; } private async executeFastPath(command: string, options: ExecuteOptions): Promise<UnifiedResult> { // Start monitoring const monitor = this.createOutputMonitor(); try { // Execute with conservative timeout const result = await this.consoleManager.executeCommandInSession( options.sessionId, command, options.args, options.timeout || 30000 // 30s default ); return { success: true, output: result.output, exitCode: result.exitCode, duration: result.duration, strategyUsed: 'fast', switched: false }; } catch (error) { // Check if we should switch strategies if (monitor.outputSize > 10 * 1024) { // Large output detected - switch to streaming return await this.switchToStreaming(command, options, monitor); } if (error.message.includes('timeout') && monitor.isStillRunning) { // Timeout but still running - switch to background return await this.switchToBackground(command, options, monitor); } throw error; } } private async switchToStreaming( command: string, options: ExecuteOptions, monitor: OutputMonitor ): Promise<UnifiedResult> { // Migration logic: switch from execute to streaming // Both share same session, so can access same output buffer const chunks: string[] = []; let sequenceId = 0; while (true) { const stream = await this.getStream({ sessionId: options.sessionId, since: sequenceId, maxLines: 50 }); chunks.push(...stream.chunks.map(c => c.data)); if (!stream.hasMore) break; sequenceId = stream.nextSequenceId; } return { success: true, output: chunks.join(''), strategyUsed: 'streaming', switched: true, switchReason: 'Large output detected' }; } private async switchToBackground( command: string, options: ExecuteOptions, monitor: OutputMonitor ): Promise<UnifiedResult> { // Migration logic: switch from execute to background job const job = await this.executeAsync({ command, args: options.args, options: { sessionId: options.sessionId, timeout: 600000 // 10 minutes } }); // Poll for completion let status; do { await sleep(1000); status = await this.getJobStatus(job.jobId); } while (status.job.status === 'running'); const output = await this.getJobOutput(job.jobId); return { success: status.job.status === 'completed', output: output.output.map(o => o.content).join(''), exitCode: status.job.exitCode, strategyUsed: 'background', switched: true, switchReason: 'Command exceeded timeout' }; } } ``` ### Implementation Files **New File:** `src/core/SmartExecutor.ts` (300-400 lines) - CommandAnalyzer class - StrategySelector class - RuntimeMonitor class - ResultAggregator class - SmartExecutor orchestrator **New File:** `src/mcp/tools/console_execute_smart.ts` (100-150 lines) - MCP tool definition - Parameter validation - SmartExecutor integration **Modified:** `src/mcp/server.ts` - Add console_execute_smart tool registration - Update error messages to recommend smart execute ### Testing Phase 3 ```typescript // Test 1: Quick command stays fast const result1 = await executeSmart({ command: 'echo test' }); assert(result1.strategyUsed === 'fast'); assert(result1.switched === false); // Test 2: Large output switches to streaming const result2 = await executeSmart({ command: 'cat large-file.log' }); assert(result2.strategyUsed === 'streaming'); assert(result2.switched === true); // Test 3: Long command switches to background const result3 = await executeSmart({ command: 'npm install' }); assert(result3.strategyUsed === 'background'); // Test 4: Known patterns pre-selected correctly const result4 = await executeSmart({ command: 'tail -f log.txt' }); assert(result4.strategyUsed === 'streaming'); assert(result4.switched === false); // Pre-selected, not switched ``` --- ## Migration Guide ### For Existing Users **Before (Manual Choice):** ```typescript // Users must know which pattern to use const result = await executeCommand({ command: 'long-task' }); // Might timeout! // OR const session = await createSession({ command: 'long-task', streaming: true }); const stream = await getStream({ sessionId: session.sessionId }); // ... pagination logic ... // OR const job = await executeAsync({ command: 'long-task' }); // ... polling logic ... ``` **After (Smart Execute):** ```typescript // One tool, works for everything const result = await executeSmart({ command: 'any-command', sessionId: 'my-session' }); // System automatically picks best strategy and returns unified result console.log(result.output); console.log(`Used strategy: ${result.strategyUsed}`); ``` ### Backward Compatibility All existing tools remain unchanged: - `console_execute_command` - still works as before (but now event-driven) - `console_create_session` + streaming - still available for power users - `console_execute_async` - still available for explicit async needs **Recommendation:** New users should prefer `console_execute_smart`, power users can still use specific patterns. --- ## Timeline & Effort ### Phase 1: Event-Driven Completion - Implementation: 4 hours - Testing: 1 hour - Documentation: 1 hour - **Total: 6 hours** ### Phase 2: Documentation Fixes - Error messages: 1 hour - Tool descriptions: 30 minutes - USAGE_PATTERNS.md: 3 hours - Integration guide: 1 hour - Examples: 2 hours - **Total: 7.5 hours** ### Phase 3: Smart Execute - SmartExecutor core: 6 hours - Command analyzer: 2 hours - Strategy switcher: 3 hours - MCP tool integration: 2 hours - Testing: 3 hours - Documentation: 2 hours - **Total: 18 hours** **Grand Total: 31.5 hours** Can be deployed incrementally with value at each phase. --- ## Risk Assessment ### Phase 1 Risks: LOW - Internal refactoring only - Backward compatible - Well-tested event system already exists **Mitigation:** Comprehensive testing of edge cases ### Phase 2 Risks: LOW - Documentation changes only - No code changes **Mitigation:** Clear examples and migration guide ### Phase 3 Risks: MEDIUM - New feature with complex logic - Strategy switching needs careful handling - Edge cases in pattern recognition **Mitigation:** - Extensive testing - Graceful degradation if switching fails - User can override strategy if needed - Deploy as opt-in tool first --- ## Success Metrics ### Phase 1 Success - ✅ Zero polling during idle periods - ✅ <50ms latency in completion detection (down from 100ms) - ✅ 100+ concurrent commands without CPU degradation ### Phase 2 Success - ✅ No users guided to sendInput/getOutput pattern - ✅ Clear documentation for all patterns - ✅ Migration guide available ### Phase 3 Success - ✅ 90%+ commands succeed without manual strategy selection - ✅ Automatic switching works for large outputs - ✅ Users report improved UX - ✅ Reduced support questions about "which tool to use" --- ## Future Enhancements ### Machine Learning Command Analysis - Learn from command execution history - Predict duration/output size more accurately - Adapt to user's specific codebase patterns ### User Hints ```typescript await executeSmart({ command: 'my-command', hints: { expectedDuration: 'long', expectedOutputSize: 'large' } }); ``` ### Strategy Override ```typescript await executeSmart({ command: 'my-command', strategyOverride: 'streaming' // Force specific strategy }); ``` --- ## Conclusion This enhanced plan addresses: 1. ✅ **Architectural inefficiency** - Remove 100ms polling 2. ✅ **User guidance problem** - Fix error messages 3. ✅ **UX complexity** - Intelligent execution **Benefits over documentation-only approach:** - Better performance (event-driven vs polling) - Better UX (one tool vs three choices) - Self-optimizing (learns from patterns) - Still backward compatible **Effort:** 31.5 hours total, deployable incrementally **Risk:** Low-Medium, mitigated through phased deployment This is a production-ready plan that significantly improves both architecture and user experience.

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