Skip to content

ohernandezdev/interactive-cli

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

interactive-cli

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.

Why

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.

Quick Start

Claude Code

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"]
    }
  }
}

From Source

git clone https://github.com/ohernandezdev/interactive-cli.git
cd interactive-cli
npm install && npm run build
claude mcp add-json interactive-cli '{"command":"node","args":["/absolute/path/to/interactive-cli/dist/index.js"]}'

Other MCP Clients

Point your client's MCP config at:

command: node
args: ["/path/to/interactive-cli/dist/index.js"]
transport: stdio

Tools

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

Typical Flow

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
Loading

spawn

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

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_keys

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 f1f12 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 09

get_screen

get_screen({
  sessionId: "s1",
  search: "error|warning",  // optional regex to highlight matching lines
})
// Returns: { screen, cursor: { row, col }, searchResults, stats }

wait_for

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

wait_for_exit({
  sessionId: "s1",
  timeoutMs: 60000,
})
// Returns: { alive: false, exitCode: 0, screen }

Resources

Sessions are exposed as MCP resources:

  • interactive-cli://sessions — JSON list of all sessions
  • interactive-cli://sessions/{id}/screen — live screen content

Prompt Templates

Pre-built prompt templates for common flows (appear as slash commands in Claude Code):

  • eas_build — EAS build with credential handling
  • ssh_session — SSH connection with command execution
  • repl_session — Start a REPL (Python, Node, psql, etc.)
  • docker_interactive — Run an interactive Docker container

How It Works

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...]
Loading
  • 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

Requirements

  • Node.js >= 18
  • macOS or Linux (node-pty uses native PTY)

License

MIT

About

MCP server that lets AI coding agents interact with interactive CLI processes via PTY + xterm headless

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors