MCP server that lets AI coding agents interact with interactive CLI processes. Uses a real PTY + xterm headless to render terminal output as clean text — no ANSI escape soup.
Works with Claude Code, Cursor, Windsurf, or any MCP-compatible client.
AI agents can run shell commands, but they choke on anything interactive:
$ eas build --platform ios
✔ Select a build profile › (waiting for input...)
This MCP server bridges that gap — spawn the process, read the screen, send keystrokes.
claude mcp add-json interactive-cli '{"command":"npx","args":["-y","interactive-cli-mcp"]}'Or add to ~/.claude/settings.json manually:
{
"mcpServers": {
"interactive-cli": {
"command": "npx",
"args": ["-y", "interactive-cli-mcp"]
}
}
}git clone https://github.com/ohernandezdev/interactive-cli.git
cd interactive-cli
npm install && npm run buildclaude mcp add-json interactive-cli '{"command":"node","args":["/absolute/path/to/interactive-cli/dist/index.js"]}'Point your client's MCP config at:
command: node
args: ["/path/to/interactive-cli/dist/index.js"]
transport: stdio
| Tool | Description | Read-only |
|---|---|---|
spawn |
Start an interactive process in a PTY | No |
send_input |
Type text (appends Enter by default) | No |
send_keys |
Send special keys in sequence (arrows, ctrl combos, etc.) | No |
get_screen |
Capture current terminal screen, optional regex search | Yes |
wait_for |
Block until a regex pattern appears in output | No |
wait_for_exit |
Block until the process exits | No |
resize |
Change terminal dimensions | No |
list_sessions |
List all active sessions | Yes |
close |
Kill process, clean up session | No |
sequenceDiagram
participant Agent as AI Agent
participant MCP as interactive-cli
participant PTY as Process (PTY)
Agent->>MCP: spawn("eas build --platform ios")
MCP->>PTY: Start process in PTY
PTY-->>MCP: Initial output
MCP-->>Agent: sessionId + screen
Agent->>MCP: get_screen(sessionId)
MCP-->>Agent: "Select profile: ❯ dev staging prod"
Agent->>MCP: send_keys(["down","down","enter"])
MCP->>PTY: ↓ ↓ ⏎
PTY-->>MCP: "✔ Selected: production"
MCP-->>Agent: updated screen
Agent->>MCP: wait_for("Build complete|error", 600s)
loop Every 1s
MCP->>PTY: check output
end
PTY-->>MCP: "Build complete"
MCP-->>Agent: matched + screen
Agent->>MCP: close(sessionId)
MCP->>PTY: SIGTERM
MCP-->>Agent: final screen + exit code
spawn({
command: "ssh user@server.com", // full command string
cwd: "/path/to/project", // optional working directory
env: { "NODE_ENV": "production" }, // optional extra env vars
cols: 120, // terminal width (default 120)
rows: 30, // terminal height (default 30)
waitMs: 3000, // ms to wait for initial output (default 2000)
})Commands run through your shell ($SHELL or /bin/zsh), so pipes, redirects, and builtins work.
send_input({
sessionId: "s1",
text: "yes", // text to type
pressEnter: true, // append Enter (default true). false for password fields
waitMs: 2000, // ms to wait for response (default 2000)
})Send one or more keys in sequence with a small delay between each:
send_keys({
sessionId: "s1",
keys: ["down", "down", "enter"], // navigate menu
delayBetweenMs: 50, // ms between keys (default 50)
waitMs: 1500, // ms to wait after last key (default 1500)
})Supported keys: enter tab escape space up down left right backspace delete home end page_up page_down f1–f12 ctrl+c ctrl+d ctrl+z ctrl+l ctrl+a ctrl+e ctrl+r ctrl+w ctrl+u ctrl+k ctrl+p ctrl+n y n 0–9
get_screen({
sessionId: "s1",
search: "error|warning", // optional regex to highlight matching lines
})
// Returns: { screen, cursor: { row, col }, searchResults, stats }wait_for({
sessionId: "s1",
pattern: "\\$|#|>", // regex to match (case-insensitive)
timeoutMs: 30000, // max wait (default 30s)
intervalMs: 1000, // check interval (default 1s)
})
// Returns: { matched: true/false, screen, elapsed }wait_for_exit({
sessionId: "s1",
timeoutMs: 60000,
})
// Returns: { alive: false, exitCode: 0, screen }Sessions are exposed as MCP resources:
interactive-cli://sessions— JSON list of all sessionsinteractive-cli://sessions/{id}/screen— live screen content
Pre-built prompt templates for common flows (appear as slash commands in Claude Code):
eas_build— EAS build with credential handlingssh_session— SSH connection with command executionrepl_session— Start a REPL (Python, Node, psql, etc.)docker_interactive— Run an interactive Docker container
graph LR
A[AI Agent] <-->|MCP Protocol| B[interactive-cli server]
B --> C[Session Manager]
C --> D[node-pty<br/><i>real PTY</i>]
C --> E[xterm headless<br/><i>screen render</i>]
C --> F[Truncation<br/><i>80K limit</i>]
D <--> G[Child Process<br/>ssh, eas, python...]
- node-pty spawns a real pseudo-terminal, so the child process thinks it's talking to a human
- xterm headless maintains a virtual terminal buffer that renders ANSI sequences into a clean 2D text grid
- Output is truncated at 80K chars (MCP clients like Claude Code cap at 100K) with smart middle-truncation preserving start and end
- Tool annotations (
readOnlyHint,destructiveHint) tell the client which tools are safe to run in parallel and which need permission
- Node.js >= 18
- macOS or Linux (node-pty uses native PTY)
MIT