Process Lifecycle for start_process, interact_with_process, and force_terminate in Desktop Commander MCP
The process lifecycle in Desktop Commander MCP follows a three-stage state machine: start_process creates a persistent shell session and returns a PID, interact_with_process sends input and polls for output until a prompt or completion is detected, and force_terminate ends the session with SIGINT followed by SIGKILL.
Desktop Commander MCP manages long-lived shell and REPL sessions that persist across tool invocations, enabling interactive workflows like Python data analysis or multi-step DevOps automation. Understanding the process lifecycle for the start_process, interact_with_process, and force_terminate tools is essential for building reliable MCP server integrations that maintain state across multiple AI agent turns.
The Three-Stage Process Lifecycle
The lifecycle operates as a managed state machine within the TerminalManager class, which stores active sessions in a Map<number, TerminalSession> and tracks completed sessions separately for pagination.
Stage 1: Creating Sessions with start_process
The start_process tool in src/tools/improved-process-tools.ts spawns a new shell or REPL and prepares it for interaction.
The implementation follows these steps:
- Argument validation –
StartProcessArgsSchemaparses{ command, timeout_ms?, shell?, verbose_timing? }. - Shell selection – If
shellis omitted, the default is retrieved fromconfigManager.getConfig()or falls back tocmd.exeon Windows and/bin/shon Unix. See the shell-selection logic insrc/terminal-manager.ts(lines 77-89). - Process spawn –
TerminalManager.executeCommandbuilds a platform-specific spawn configuration usinggetShellSpawnArgsand callsspawn. - Early-output buffering – While the child runs, the wait buffer (
MAX_WAIT_OUTPUT_CHARS) accumulates the first chunk of output. - Prompt detection – The system uses
quickPromptPatternsregex andanalyzeProcessStateheuristics to determine if the process is blocked waiting for input. If a prompt appears before timeout, the tool resolves with{ pid, output, isBlocked: true }. - Return – The tool returns a message containing the PID, initial output, and status emoji (
🔄if waiting,✅if finished,⏳otherwise).
The response construction in src/tools/improved-process-tools.ts (lines 102-108) formats the result:
return {
content: [{
type: "text",
text: `Process started with PID ${result.pid} (shell: ${shellUsed})\nInitial output:\n${result.output}${statusMessage}${timingMessage}`
}],
};
Stage 2: Interacting with Running Sessions
Once a session exists, interact_with_process sends commands and captures output without restarting the shell, preserving environment variables and working directory state.
The interaction flow in src/terminal-manager.ts and src/tools/improved-process-tools.ts works as follows:
- Snapshot –
terminalManager.captureOutputSnapshot(pid)records the current character and line count before sending input. - Send input –
terminalManager.sendInputToProcess(pid, input)appends a newline (if missing) and writes to the child’s stdin. - Waiting loop – Every 50ms, the system fetches new output since the snapshot via
getOutputSinceSnapshot. It checksquickPromptPatternsor runsanalyzeProcessStateto detect if the process is waiting for input or has finished. The loop exits aftermaxAttempts = timeout_ms / pollIntervalMsiterations. - Output cleaning –
cleanProcessOutputstrips echoed input and trims whitespace. If output exceedsconfig.fileReadLineLimit, it truncates with a warning. - Result – Returns a status emoji (✅ or 🔄) with state message, cleaned output, and optional timing telemetry.
The final response construction (lines 640-645 in improved-process-tools.ts) returns:
return {
content: [{
type: "text",
text: responseText
}],
};
Stage 3: Terminating Sessions with force_terminate
The force_terminate tool ends sessions reliably using a two-stage kill sequence.
The termination logic in src/terminal-manager.ts (lines 172-182) handles two cases:
- Virtual Node sessions – For
node:localfallbacks (negative PIDs), the entry is simply deleted from the sessions map. - Real processes – The system sends
SIGINT, waits 1 second, then sendsSIGKILLif the process remains inthis.sessions.
session.process.kill('SIGINT');
setTimeout(() => {
if (this.sessions.has(pid)) {
session.process.kill('SIGKILL');
}
}, 1000);
How the Tools Work Together
The three tools form a cohesive workflow for persistent shell management:
- Create –
start_processreturns PID 1234 and stores the session in TerminalManager’s active sessions map. - Interact –
interact_with_processsends commands likels -l\n, waits for prompt detection, and updateslastReadIndexautomatically. - Read More –
read_process_output(optional) fetches additional lines using offset-based pagination without sending new input. - Finish –
force_terminateclears the session from memory and kills the underlying process.
Because TerminalSession objects persist in memory, environment state (variables, current directory, loaded modules) survives across multiple interact_with_process calls, enabling true REPL-style workflows.
Practical Usage Examples
The following examples demonstrate the complete lifecycle using MCP tool invocations:
// 1. Start a Python REPL session
const start = await callTool('start_process', {
command: 'python3 -i',
timeout_ms: 12000,
origin: 'ui'
});
// Returns: "Process started with PID 5678 ... 🔄 ..."
// 2. Execute Python code and wait for the REPL prompt
const interact = await callTool('interact_with_process', {
pid: 5678,
input: 'import pandas as pd; df = pd.read_csv("/abs/data.csv")\n',
timeout_ms: 8000,
wait_for_prompt: true,
verbose_timing: false
});
// Returns: "✅ Input executed in process 5678:\n\n📤 Output:\n[...]"
// 3. Retrieve additional output after long-running operations
const more = await callTool('read_process_output', {
pid: 5678,
offset: 0,
length: 200,
timeout_ms: 3000
});
// Returns: "[Reading 42 new lines ...]"
// 4. Clean up when finished
await callTool('force_terminate', { pid: 5678 });
// Returns: "Successfully initiated termination of session 5678"
Key Source Files and Implementation
| File | Purpose | Key Functions |
|---|---|---|
src/tools/improved-process-tools.ts |
Public tool implementations | startProcess, interactWithProcess, forceTerminate |
src/terminal-manager.ts |
Core session management | executeCommand, sendInputToProcess, captureOutputSnapshot, getOutputSinceSnapshot, forceTerminate |
src/tools/schemas.ts |
Zod validation schemas | StartProcessArgsSchema, InteractWithProcessArgsSchema, ForceTerminateArgsSchema |
src/utils/process-detection.ts |
State heuristics | analyzeProcessState, quickPromptPatterns |
src/config-manager.ts |
Configuration defaults | getConfig(), default shell resolution |
Summary
start_processcreates persistent shell sessions usingTerminalManager.executeCommand, detects initial prompts viaanalyzeProcessState, and returns a stable PID for future interactions.interact_with_processmanages the running state by capturing output snapshots, sending stdin input, and polling every 50ms until detecting a prompt or completion, preserving all shell state between calls.force_terminateimplements reliable cleanup with a graceful SIGINT followed by SIGKILL after 1 second, or simple deletion for virtualnode:localsessions.- The TerminalManager maintains all state in memory using
Map<number, TerminalSession>, enabling long-running REPL workflows that survive across multiple MCP tool invocations.
Frequently Asked Questions
How does prompt detection work in Desktop Commander MCP?
Prompt detection uses a combination of quick regex patterns (quickPromptPatterns) and deeper process state analysis (analyzeProcessState in src/utils/process-detection.ts). The system checks output every 50ms during the waiting loop to determine if the process is blocked awaiting input, has finished execution, or is still processing.
What happens if a process hangs during interaction?
If interact_with_process exceeds the timeout_ms limit (calculated as maxAttempts = timeout_ms / pollIntervalMs), the tool returns with a timeout status. The session remains active in the TerminalManager, allowing subsequent interaction attempts or force_terminate to recover or kill the process.
Can I run multiple shell processes simultaneously?
Yes. Each call to start_process creates a distinct TerminalSession stored in the sessions Map with a unique PID. You can interact with multiple PIDs concurrently, though each interaction is synchronous and blocks until the prompt is detected or timeout occurs.
How is output buffering handled across interactions?
The captureOutputSnapshot method records the current character count before sending input, and getOutputSinceSnapshot retrieves only new output generated after that point. This incremental approach prevents duplicate data while respecting the fileReadLineLimit configuration for large outputs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →