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);
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden and does well by detailing behavioral traits: early exit on REPL prompts, detection of completion vs running states, prevention of hanging, and clear status messages. It explains what the tool does beyond basic reading, though it could mention error handling or output format.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is overly verbose and poorly structured, with redundant sections (e.g., 'SMART FEATURES' and 'REPL USAGE' overlap), bullet points that could be condensed, and unnecessary marketing language ('This command can be referenced as...'). It lacks front-loading of critical information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no annotations, no output schema, and low schema coverage, the description is moderately complete. It covers behavioral aspects well but misses parameter details and return value explanation. For a tool with smart detection features, it's adequate but has gaps in technical specifics.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate for the two parameters (pid, timeout_ms). It mentions 'timeout' briefly but doesn't explain what pid represents, the units for timeout_ms, or default behaviors. The description adds minimal semantic value beyond the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool reads output from a running process with intelligent completion detection, which is a specific verb+resource combination. It distinguishes from siblings like 'interact_with_process' by focusing on reading output rather than sending input, though the distinction could be more explicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when to use this tool: when a process is running and output needs to be read with smart detection. It implies an alternative ('interact_with_process' for when the process is waiting for input) but doesn't explicitly name it or provide exclusion criteria.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

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