read_process_output
Read and monitor process output with intelligent completion detection. Automatically identifies when a process is ready for input or has finished, preventing timeouts and hangs. Works across Python, Node.js, R, Julia, and other REPL environments.
Instructions
Read output from a running process with intelligent completion detection.
Automatically detects when process is ready for more input instead of timing out.
SMART FEATURES:
- Early exit when REPL shows prompt (>>>, >, etc.)
- Detects process completion vs still running
- Prevents hanging on interactive prompts
- Clear status messages about process state
REPL USAGE:
- Stops immediately when REPL prompt detected
- Shows clear status: waiting for input vs finished
- Shorter timeouts needed due to smart detection
- Works with Python, Node.js, R, Julia, etc.
DETECTION STATES:
Process waiting for input (ready for interact_with_process)
Process finished execution
Timeout reached (may still be running)
This command can be referenced as "DC: ..." or "use Desktop Commander to ..." in your instructions.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| pid | Yes | ||
| timeout_ms | No |
Implementation Reference
- Core handler function that reads new output from a running process (by PID), intelligently detects if the process is waiting for input or finished, supports verbose timing telemetry, and returns formatted results with status messages.export async function readProcessOutput(args: unknown): Promise<ServerResult> { const parsed = ReadProcessOutputArgsSchema.safeParse(args); if (!parsed.success) { return { content: [{ type: "text", text: `Error: Invalid arguments for read_process_output: ${parsed.error}` }], isError: true, }; } const { pid, timeout_ms = 5000, verbose_timing = false } = parsed.data; const session = terminalManager.getSession(pid); if (!session) { // Check if this is a completed session const completedOutput = terminalManager.getNewOutput(pid); if (completedOutput) { return { content: [{ type: "text", text: completedOutput }], }; } // Neither active nor completed session found return { content: [{ type: "text", text: `No session found for PID ${pid}` }], isError: true, }; } let output = ""; let timeoutReached = false; let earlyExit = false; let processState: ProcessState | undefined; // Timing telemetry const startTime = Date.now(); let firstOutputTime: number | undefined; let lastOutputTime: number | undefined; const outputEvents: any[] = []; let exitReason: 'early_exit_quick_pattern' | 'early_exit_periodic_check' | 'process_finished' | 'timeout' = 'timeout'; try { const outputPromise: Promise<string> = new Promise<string>((resolve) => { const initialOutput = terminalManager.getNewOutput(pid); if (initialOutput && initialOutput.length > 0) { const now = Date.now(); if (!firstOutputTime) firstOutputTime = now; lastOutputTime = now; if (verbose_timing) { outputEvents.push({ timestamp: now, deltaMs: now - startTime, source: 'initial_poll', length: initialOutput.length, snippet: initialOutput.slice(0, 50).replace(/\n/g, '\\n') }); } // Immediate check on existing output const state = analyzeProcessState(initialOutput, pid); if (state.isWaitingForInput) { earlyExit = true; processState = state; exitReason = 'early_exit_periodic_check'; } resolve(initialOutput); return; } let resolved = false; let interval: NodeJS.Timeout | null = null; let timeout: NodeJS.Timeout | null = null; // Quick prompt patterns for immediate detection const quickPromptPatterns = />>>\s*$|>\s*$|\$\s*$|#\s*$/; const cleanup = () => { if (interval) clearInterval(interval); if (timeout) clearTimeout(timeout); }; let resolveOnce = (value: string, isTimeout = false) => { if (resolved) return; resolved = true; cleanup(); timeoutReached = isTimeout; if (isTimeout) exitReason = 'timeout'; resolve(value); }; // Monitor for new output with immediate detection const session = terminalManager.getSession(pid); if (session && session.process && session.process.stdout && session.process.stderr) { const immediateDetector = (data: Buffer, source: 'stdout' | 'stderr') => { const text = data.toString(); const now = Date.now(); if (!firstOutputTime) firstOutputTime = now; lastOutputTime = now; if (verbose_timing) { outputEvents.push({ timestamp: now, deltaMs: now - startTime, source, length: text.length, snippet: text.slice(0, 50).replace(/\n/g, '\\n') }); } // Immediate check for obvious prompts if (quickPromptPatterns.test(text)) { const newOutput = terminalManager.getNewOutput(pid) || text; const state = analyzeProcessState(output + newOutput, pid); if (state.isWaitingForInput) { earlyExit = true; processState = state; exitReason = 'early_exit_quick_pattern'; if (verbose_timing && outputEvents.length > 0) { outputEvents[outputEvents.length - 1].matchedPattern = 'quick_pattern'; } resolveOnce(newOutput); return; } } }; const stdoutDetector = (data: Buffer) => immediateDetector(data, 'stdout'); const stderrDetector = (data: Buffer) => immediateDetector(data, 'stderr'); session.process.stdout.on('data', stdoutDetector); session.process.stderr.on('data', stderrDetector); // Cleanup immediate detectors when done const originalResolveOnce = resolveOnce; const cleanupDetectors = () => { if (session.process.stdout) { session.process.stdout.off('data', stdoutDetector); } if (session.process.stderr) { session.process.stderr.off('data', stderrDetector); } }; // Override resolveOnce to include cleanup const resolveOnceWithCleanup = (value: string, isTimeout = false) => { cleanupDetectors(); originalResolveOnce(value, isTimeout); }; // Replace the local resolveOnce reference resolveOnce = resolveOnceWithCleanup; } interval = setInterval(() => { const newOutput = terminalManager.getNewOutput(pid); if (newOutput && newOutput.length > 0) { const now = Date.now(); if (!firstOutputTime) firstOutputTime = now; lastOutputTime = now; if (verbose_timing) { outputEvents.push({ timestamp: now, deltaMs: now - startTime, source: 'periodic_poll', length: newOutput.length, snippet: newOutput.slice(0, 50).replace(/\n/g, '\\n') }); } const currentOutput = output + newOutput; const state = analyzeProcessState(currentOutput, pid); // Early exit if process is clearly waiting for input if (state.isWaitingForInput) { earlyExit = true; processState = state; exitReason = 'early_exit_periodic_check'; if (verbose_timing && outputEvents.length > 0) { outputEvents[outputEvents.length - 1].matchedPattern = 'periodic_check'; } resolveOnce(newOutput); return; } output = currentOutput; // Continue collecting if still running if (!state.isFinished) { return; } // Process finished processState = state; exitReason = 'process_finished'; resolveOnce(newOutput); } }, 50); // Check every 50ms for faster response timeout = setTimeout(() => { const finalOutput = terminalManager.getNewOutput(pid) || ""; resolveOnce(finalOutput, true); }, timeout_ms); }); const newOutput = await outputPromise; output += newOutput; // Analyze final state if not already done if (!processState) { processState = analyzeProcessState(output, pid); } } catch (error) { return { content: [{ type: "text", text: `Error reading output: ${error}` }], isError: true, }; } // Format response based on what we detected let statusMessage = ''; if (earlyExit && processState?.isWaitingForInput) { statusMessage = `\n🔄 ${formatProcessStateMessage(processState, pid)}`; } else if (processState?.isFinished) { statusMessage = `\n✅ ${formatProcessStateMessage(processState, pid)}`; } else if (timeoutReached) { statusMessage = '\n⏱️ Timeout reached - process may still be running'; } // Add timing information if requested let timingMessage = ''; if (verbose_timing) { const endTime = Date.now(); const timingInfo = { startTime, endTime, totalDurationMs: endTime - startTime, exitReason, firstOutputTime, lastOutputTime, timeToFirstOutputMs: firstOutputTime ? firstOutputTime - startTime : undefined, outputEvents: outputEvents.length > 0 ? outputEvents : undefined }; timingMessage = formatTimingInfo(timingInfo); } const responseText = output || 'No new output available'; return { content: [{ type: "text", text: `${responseText}${statusMessage}${timingMessage}` }], }; }
- src/tools/schemas.ts:28-32 (schema)Zod schema defining input parameters for read_process_output: pid (required), timeout_ms (optional), verbose_timing (optional). Used for validation in handler and tool registration.export const ReadProcessOutputArgsSchema = z.object({ pid: z.number(), timeout_ms: z.number().optional(), verbose_timing: z.boolean().optional(), });
- src/server.ts:1208-1210 (registration)Registration/dispatch in the central CallToolRequest handler switch statement. Routes read_process_output calls to the appropriate handler function imported from handlers/index.ts.case "read_process_output": result = await handlers.handleReadProcessOutput(args); break;
- src/server.ts:762-799 (registration)Tool metadata registration in list_tools handler: defines name, detailed description, input schema (converted to JSON schema), and annotations for the MCP tool listing.name: "read_process_output", description: ` Read output from a running process with intelligent completion detection. Automatically detects when process is ready for more input instead of timing out. SMART FEATURES: - Early exit when REPL shows prompt (>>>, >, etc.) - Detects process completion vs still running - Prevents hanging on interactive prompts - Clear status messages about process state REPL USAGE: - Stops immediately when REPL prompt detected - Shows clear status: waiting for input vs finished - Shorter timeouts needed due to smart detection - Works with Python, Node.js, R, Julia, etc. DETECTION STATES: Process waiting for input (ready for interact_with_process) Process finished execution Timeout reached (may still be running) PERFORMANCE DEBUGGING (verbose_timing parameter): Set verbose_timing: true to get detailed timing information including: - Exit reason (early_exit_quick_pattern, early_exit_periodic_check, process_finished, timeout) - Total duration and time to first output - Complete timeline of all output events with timestamps - Which detection mechanism triggered early exit Use this to identify when timeouts could be reduced or detection patterns improved. ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(ReadProcessOutputArgsSchema), annotations: { title: "Read Process Output", readOnlyHint: true, }, },
- src/handlers/terminal-handlers.ts:27-33 (handler)Thin wrapper handler that validates input arguments using the schema and delegates to the core readProcessOutput implementation./** * Handle read_process_output command (improved read_output) */ export async function handleReadProcessOutput(args: unknown): Promise<ServerResult> { const parsed = ReadProcessOutputArgsSchema.parse(args); return readProcessOutput(parsed); }