Skip to main content
Glama
wonderwhy-er

Claude Desktop Commander MCP

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
NameRequiredDescriptionDefault
pidYes
timeout_msNo

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}`
        }],
      };
    }
  • 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(),
    });
  • 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,
        },
    },
  • 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);
    }

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/wonderwhy-er/DesktopCommanderMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server