terminal-driver-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@terminal-driver-mcpspawn a vim session, write 'print("hello")', and quit without saving"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
terminal-driver-mcp
A real terminal for your AI agent — one it can actually use, not just fire commands at.
An MCP server that gives an agent a persistent, PTY-backed terminal with a headless screen it can read, type into, wait on, and assert against — so it can drive vim, htop, gdb, a Python REPL, an SSH prompt, or the very TUI you're building with it, exactly the way a human would.
Why not just let the agent use its built-in terminal?
Because the built-in terminal isn't really a terminal. Coding agents run each command against a pipe, fire-and-forget, and hand back the raw bytes. That breaks the moment anything is interactive:
The built-in terminal | terminal-driver-mcp |
No TTY — | A real pseudo-terminal: apps behave exactly as they do for a human (colors, redraws, |
Fire-and-forget — the call returns only when the process exits, so it can never answer a password prompt, a | Sessions persist across tool calls — type, read, type again; sit in a debugger or REPL for a whole conversation |
Chronological byte stream — you get a log of ANSI escape codes; where anything landed on screen is unrecoverable | A live 2D screen you read as clean text — menus, dialogs, cursor position, exact rows and columns |
No way to sync — the agent guesses when the UI is ready and types blind |
|
Can't verify its own TUI work — the feedback loop bottoms out at "it compiled" | Snapshot, assert, |
Keep using the built-in terminal for npm test and git status. Reach for this the moment the work is interactive, stateful, full-screen, mouse-driven, or something the agent needs to test rather than just run.
Related MCP server: PTY MCP Server
Architecture
node-pty allocates a real pseudo-terminal, so applications detect a TTY and behave fully interactively (colors, redraws, SIGWINCH).
@xterm/headless ingests the raw ANSI byte stream and maintains a stateful in-memory 2D screen grid.
@xterm/addon-serialize + buffer iteration extract the grid as clean plain text (or raw VT sequences), eliminating "ANSI garbage".
Sessions live in an in-memory registry and persist across tool calls; PTY children are hard-killed on server exit — no zombies.
Local-first: stdio transport only, zero network access.
Session lifetime: sessions are in-memory and last as long as the server process. They survive tool calls and child-process exit (the final screen stays readable), but not a restart of the MCP server itself — if the client relaunches it, or it is killed, all PTYs go with it. Stray async errors no longer trigger this: the server logs and keeps serving instead of crashing, and every deliberate shutdown logs its cause (signal, client disconnect) to stderr.
Tools
Tool | Purpose |
| One-shot: run a command to completion in a fresh PTY, return full output + exit code, auto-cleanup. Kills the process and returns partial output on timeout |
| Spawn a persistent PTY session (command via your shell, or an interactive shell) |
| Snapshot the rendered screen; |
| Render the screen (colors, box-drawing, cursor) to a PNG image for vision models |
| Type text, special keys (enter, escape, arrows, ctrl+c, f-keys, chords, …), and/or raw bytes; with |
| Send a mouse click (SGR) at a cell — for TUIs with mouse tracking; errors if the app isn't listening |
| Mouse drag (press → move → release) for dividers, resize handles, selections |
| Report what the app enabled: mouse tracking, bracketed paste, alt screen, cursor keys/keypad, insert, foreground process, dims |
| The last shell command's exact output, exit code, and duration (needs |
| Block until the running shell command finishes, then return its result (needs |
| Poll until a regex matches the screen |
| Wait until output quiesces (byte silence) or the rendered screen stops changing |
| Pass/fail screen assertion with contextual diff; |
| Extract a rectangle of the screen (a pane, status bar, or widget) |
| Resize PTY + emulator (SIGWINCH reflow) |
| List sessions with pid/status/age |
| Terminate and clean up a session |
| Replay a JSON test script deterministically (see below) |
| Convert a session's |
Every screen header includes the cursor position (cursor row:col, 0-based, matching screen row numbering).
Arrow keys are DECCKM-aware: when a full-screen app (vim, less) enables application cursor mode, arrows are sent as SS3 sequences automatically. Control chords use the byte the target understands: ctrl+<letter> and symbol chords (ctrl+], ctrl+\) send their legacy C0 code (works everywhere), while chords with no legacy encoding (shift+escape, ctrl+enter, ...) fall back to CSI-u (fixterms/kitty). For anything no key name covers, raw_hex sends arbitrary bytes (e.g. raw_hex: "1b5b41" for ESC[A).
Typing a key name as literal text is a common mistake, so input values containing {enter}-style names or backslash escapes like \r are rejected with a hint pointing at special_keys.
The emulator also answers terminal queries (DA1, DSR cursor reports, ...) on the application's behalf, so query-happy TUIs (neovim and friends) behave as they would in a real terminal instead of hanging on a probe.
Mouse events (session_click, session_drag) are sent as SGR sequences and only when the app has enabled mouse tracking — otherwise the tools return a helpful error rather than injecting stray input. Use session_info to see the current tracking mode and other flags.
Synchronization caveats
Both session_wait_idle modes are best-effort: continuously-animating UIs (spinners, progress bars, htop's periodic redraws) never go quiet, so those calls run to timeout — which still returns the current screen. When you know what you're waiting for, session_wait with a pattern is the reliable primitive. stable_screen mode helps with apps that emit bytes without visual change (cursor pings, identical redraws).
When a session's screen header says lines have scrolled off (e.g. after a long build), read them back with session_read(scrollback_lines: N) — up to 1000 lines are retained.
Semantic command boundaries (OSC 133)
Create a shell session with shell_integration: true (interactive bash/zsh) and the server injects OSC 133 hooks, so it knows exactly when each command starts and finishes:
session_create(session_id: "sh", shell_integration: true)
session_write(session_id: "sh", input: "npm test", special_keys: ["enter"])
session_wait_command(session_id: "sh") // blocks until npm test finishes
→ { command: "npm test", exit_code: 1, duration_ms: 8423, output: "…just the test output…" }No blind waits, no CPU heuristics, no marker collisions — and session_last_command returns only that command's output instead of the whole screen, which is far cheaper in tokens. In run_test scripts, a { "command_exit": 0 } step asserts the last command's exit code. Requires bash ≥ 4.4 or zsh; on shells without support the session still works, just without command tracking.
Structured & visual snapshots
session_read(format: "json") returns a structured cell model — per-row runs with fg/bg colors and bold/italic/underline/etc. attributes, the cursor position, and any OSC 8 hyperlink ranges — so an agent can assert on color-encoded state (errors red, selection highlighted) that plain text discards. session_screenshot renders the same state to a PNG (bundled monospace font, Chromium-free via @resvg/resvg-js) for a vision-capable model to inspect layout and color directly.
Session recordings
Every persistent session is recorded to an asciicast v2 file in ~/.terminal-driver-mcp/recordings/ (override with TERMINAL_DRIVER_MCP_RECORDING_DIR). Replay any session after the fact with asciinema play <file>.cast — full fidelity, including agent keystrokes (as input events), resizes, and emulator query responses (non-standard "q" events, ignored by players). The recording path is reported by session_create and session_kill. Recording is best-effort and never fails a session; execute_command runs are not recorded (their full output is already returned).
Deterministic test replay
The agent drives your TUI interactively once, then writes a JSON test script that replays forever with no LLM in the loop — in CI via:
node dist/index.js run tests/*.json # exit 0 = all pass, 1 = failuresor ad-hoc via the run_test tool. Example script:
{
"name": "editor smoke test",
"command": "vim -u NONE /tmp/t.txt",
"cols": 100,
"rows": 24,
"steps": [
{ "wait": "~", "timeout_ms": 8000 },
{ "write": "ihello", "keys": ["escape"] },
{ "assert": "hello", "row": 0, "col": 0 },
{ "resize": [60, 15] },
{ "write": ":q!", "keys": ["enter"] },
{ "expect_exit": 0 }
]
}Step types: {"wait": "<regex>"}, {"idle_ms": N, "mode"?: "silence"|"stable_screen"}, {"write": "text", "keys": [...], "raw_hex"?}, {"assert": "text", "row"?, "col"?}, {"resize": [cols, rows]}, {"sleep_ms": N}, {"command_exit": N} (with "shell_integration": true), {"expect_exit": code}. Execution stops at the first failing step and the report includes the final screen.
Drive once, get a test
You don't have to write the JSON by hand. Drive a session interactively, then convert its recording into a run_test draft:
node dist/index.js skeleton ~/.terminal-driver-mcp/recordings/<session>.cast tests/mytest.jsonor the recording_to_test tool mid-session. Recorded keystrokes become write/keys steps, pauses become idle_ms settles (tighten these into precise wait: regexes), and the final screen becomes a suggested assert. The output is a runnable skeleton you refine — the fast path from "I just did this by hand" to "this is a regression test."
Setup
git clone https://github.com/funkyfunc/terminal-driver-mcp.git
cd terminal-driver-mcp
npm install
npm run buildRegister with Claude Code (from the repo directory):
claude mcp add terminal --scope user -- node "$(pwd)/dist/index.js"Or, once published to npm:
claude mcp add terminal --scope user -- npx -y terminal-driver-mcpThen /mcp inside Claude Code to confirm the connection.
Recommended agent workflow (Assert–Act–Assert)
Observe —
session_readfor a fresh snapshot.Verify —
session_wait/session_wait_idleuntil the expected state is visible and stable.Act —
session_writewith precise keystrokes.Re-verify —
session_assertthat the action produced the expected change before proceeding.
Development
npm run dev # tsc --watch
npm test # build + unit + e2e + gauntlet suites
npm run lint # biome check
npm run format # biome format --write
npm run inspector # hand-drive tools in the MCP Inspector UIThree test layers: test/unit-screen.mjs pins screen-reading invariants (flush-before-read, wide-char columns) fast and deterministically; test/e2e.mjs drives every tool over a real stdio MCP connection (including real vim); and test/gauntlet.mjs runs an adversarial torture-tui.mjs through the server in lockstep — capability probes, byte-split escape sequences, wide characters, an output firehose, a live-redrawing alternate screen, exact keystroke-byte verification (arrows in both cursor modes, CSI-u chords), SIGWINCH, and a slow dialog. The gauntlet is where the hard terminal-compatibility bugs surface.
Git hooks (activated automatically by npm install via core.hooksPath): pre-commit runs lint + typecheck, pre-push runs the full test suite. CI runs the same on Ubuntu and macOS.
Note: postinstall restores the execute bit on node-pty's prebuilt spawn-helper (npm strips it on macOS, which otherwise causes posix_spawnp failed).
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/funkyfunc/terminal-driver-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server