Playwright MCP, but for TUI apps.
MCP server for headless TUI automation. Enables LLMs to run, view, and interact with terminal applications through the Model Context Protocol (MCP).
cargo install --git https://github.com/michaellee8/mcp-tui-driverClaude Code (CLI)
Edit ~/.claude/settings.json:
{
"mcpServers": {
"tui-driver": {
"command": "mcp-tui-driver",
"args": []
}
}
}Claude Desktop
Edit the config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"tui-driver": {
"command": "/path/to/.cargo/bin/mcp-tui-driver",
"args": []
}
}
}Cursor
Edit ~/.cursor/mcp.json (or .cursor/mcp.json in your project root):
{
"mcpServers": {
"tui-driver": {
"command": "mcp-tui-driver",
"args": []
}
}
}VS Code (Continue Extension)
Edit ~/.continue/config.json:
{
"experimental": {
"modelContextProtocolServers": [
{
"transport": {
"type": "stdio",
"command": "mcp-tui-driver",
"args": []
}
}
]
}
}Windsurf (Codeium)
Edit ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"tui-driver": {
"command": "mcp-tui-driver",
"args": []
}
}
}OpenAI Codex CLI
Edit ~/.codex/config.json:
{
"mcp_servers": {
"tui-driver": {
"command": "mcp-tui-driver"
}
}
}Troubleshooting
- Binary not found: Use the full path (e.g.,
/Users/<username>/.cargo/bin/mcp-tui-driver) - Permission denied: Run
chmod +x $(which mcp-tui-driver) - Debug logging: Add
"env": {"RUST_LOG": "debug"}to the server config
- Launch and manage multiple TUI sessions concurrently
- Read terminal content as plain text or accessibility-style snapshots
- Send keyboard input (keys, text, modifier combinations)
- Mouse interaction (click, double-click, right-click)
- Wait for screen content or idle state
- Take PNG screenshots of terminal output
- JavaScript scripting for complex automation workflows
- Session management (resize, signals, info)
- Session recording to asciicast format for playback with asciinema
TUI sessions can be recorded to asciicast v3 format files (.cast) for later playback with asciinema or other compatible players.
To enable recording, pass a recording configuration when launching a session:
{
"name": "tui_launch",
"arguments": {
"command": "bash",
"recording": {
"enabled": true,
"outputPath": "/tmp/session.cast",
"includeInput": false
}
}
}| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| enabled | boolean | Yes | - | Whether recording is enabled |
| outputPath | string | Yes | - | Path to write the recording file (.cast extension recommended) |
| includeInput | boolean | No | false | Whether to include input events in the recording |
Recordings can be played back using the asciinema CLI:
asciinema play /tmp/session.castOr uploaded to asciinema.org for web playback:
asciinema upload /tmp/session.castRecordings use the asciicast v3 format, which consists of:
- A JSON header line with version, terminal dimensions, timestamp, and command
- Event lines in the format
[interval, "type", "data"]
Event types:
o- Output data (terminal output)i- Input data (user input, ifincludeInputis enabled)r- Resize event (terminal dimension changes)x- Exit event (process termination with exit code)
cargo build --releaseecho '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | ./target/release/mcp-tui-driver# Launch a session
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"tui_launch","arguments":{"command":"htop"}}}
# Get screen text
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"tui_text","arguments":{"session_id":"<id>"}}}
# Press a key
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"tui_press_key","arguments":{"session_id":"<id>","key":"q"}}}
# Close session
{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"tui_close","arguments":{"session_id":"<id>"}}}Launch a new TUI application session.
{
"name": "tui_launch",
"arguments": {
"command": "htop",
"args": [],
"cols": 80,
"rows": 24
}
}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| command | string | Yes | - | Command to execute |
| args | array | No | [] | Command arguments |
| cols | integer | No | 80 | Terminal width in columns |
| rows | integer | No | 24 | Terminal height in rows |
| cwd | string | No | current dir | Working directory for the command |
| env | object | No | {} | Additional environment variables (merged with existing) |
| recording | object | No | null | Recording configuration (see Session Recording) |
Returns: {"session_id": "<uuid>"}
Close a TUI session and terminate the process.
{
"name": "tui_close",
"arguments": {
"session_id": "<session_id>"
}
}Returns: {"success": true}
List all active TUI sessions.
{
"name": "tui_list_sessions",
"arguments": {}
}Returns: {"sessions": ["<id1>", "<id2>", ...]}
Get information about a TUI session.
{
"name": "tui_get_session",
"arguments": {
"session_id": "<session_id>"
}
}Returns:
{
"session_id": "<uuid>",
"command": "htop",
"cols": 80,
"rows": 24,
"running": true
}Resize the terminal window dimensions.
{
"name": "tui_resize",
"arguments": {
"session_id": "<session_id>",
"cols": 120,
"rows": 40
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
| session_id | string | Yes | Session identifier |
| cols | integer | Yes | New terminal width in columns |
| rows | integer | Yes | New terminal height in rows |
Returns: {"success": true}
Send a signal to the TUI process.
{
"name": "tui_send_signal",
"arguments": {
"session_id": "<session_id>",
"signal": "SIGINT"
}
}Supported signals: SIGINT, SIGTERM, SIGKILL, SIGHUP, SIGQUIT
Returns: {"success": true}
Get the current plain text content of the terminal.
{
"name": "tui_text",
"arguments": {
"session_id": "<session_id>"
}
}Returns: {"text": "terminal content here..."}
Get an accessibility-style snapshot with element references for clicking.
{
"name": "tui_snapshot",
"arguments": {
"session_id": "<session_id>"
}
}Returns:
{
"yaml": "- row: 1\n spans:\n - ref: span-1\n text: \"File\"\n ...",
"span_count": 42
}The snapshot provides ref identifiers (like span-1) that can be used with tui_click, tui_double_click, and tui_right_click tools.
Take a PNG screenshot of the terminal.
{
"name": "tui_screenshot",
"arguments": {
"session_id": "<session_id>"
}
}Returns:
{
"data": "<base64-encoded-png>",
"format": "png",
"width": 640,
"height": 384
}Press a single key.
{
"name": "tui_press_key",
"arguments": {
"session_id": "<session_id>",
"key": "Enter"
}
}Supported key formats:
- Special keys:
Enter,Tab,Escape(orEsc),Backspace,Delete,Insert,Space - Arrow keys:
Up,Down,Left,Right(orArrowUp,ArrowDown, etc.) - Navigation:
Home,End,PageUp(orPgUp),PageDown(orPgDown) - Function keys:
F1throughF12 - Ctrl combinations:
Ctrl+c,Ctrl+z, etc. - Alt combinations:
Alt+x,Alt+f, etc. - Single characters:
a,A,1,@, etc.
Returns: {"success": true}
Press multiple keys in sequence.
{
"name": "tui_press_keys",
"arguments": {
"session_id": "<session_id>",
"keys": ["Down", "Down", "Enter"]
}
}Returns: {"success": true}
Send raw text to the terminal (useful for typing strings).
{
"name": "tui_send_text",
"arguments": {
"session_id": "<session_id>",
"text": "Hello, World!"
}
}Returns: {"success": true}
Click on an element by reference ID from the snapshot.
{
"name": "tui_click",
"arguments": {
"session_id": "<session_id>",
"ref_id": "span-1"
}
}Returns: {"success": true}
Click at specific terminal coordinates.
{
"name": "tui_click_at",
"arguments": {
"session_id": "<session_id>",
"x": 10,
"y": 5
}
}| Parameter | Type | Description |
|---|---|---|
| x | integer | X coordinate (1-based column) |
| y | integer | Y coordinate (1-based row) |
Returns: {"success": true}
Double-click on an element by reference ID.
{
"name": "tui_double_click",
"arguments": {
"session_id": "<session_id>",
"ref_id": "span-1"
}
}Returns: {"success": true}
Right-click on an element by reference ID.
{
"name": "tui_right_click",
"arguments": {
"session_id": "<session_id>",
"ref_id": "span-1"
}
}Returns: {"success": true}
Wait for specific text to appear on the screen.
{
"name": "tui_wait_for_text",
"arguments": {
"session_id": "<session_id>",
"text": "Ready",
"timeout_ms": 5000
}
}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| session_id | string | Yes | - | Session identifier |
| text | string | Yes | - | Text to wait for |
| timeout_ms | integer | No | 5000 | Timeout in milliseconds |
Returns: {"found": true} or {"found": false} if timeout
Wait for the screen to stop changing (become idle).
{
"name": "tui_wait_for_idle",
"arguments": {
"session_id": "<session_id>",
"idle_ms": 100,
"timeout_ms": 5000
}
}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| session_id | string | Yes | - | Session identifier |
| idle_ms | integer | No | 100 | How long screen must be stable |
| timeout_ms | integer | No | 5000 | Timeout in milliseconds |
Returns: {"success": true}
Get TypeScript interface definitions for tui_run_code. Call this before using tui_run_code to understand the available API.
{
"name": "tui_get_code_interface",
"arguments": {}
}Returns: TypeScript interface definitions as plain text, including the Tui interface with all available methods and the Console interface.
Execute JavaScript code with access to TUI automation functions. Call tui_get_code_interface first to get TypeScript definitions for the available API.
{
"name": "tui_run_code",
"arguments": {
"session_id": "<session_id>",
"code": "tui.sendText('hello'); tui.pressKey('Enter'); tui.text()"
}
}Available tui object methods:
Display:
tui.text()- Returns the current screen texttui.snapshot()- Returns an accessibility snapshot as a JavaScript objecttui.screenshot(filename?)- Takes a screenshot and saves to file, returns the file path
Input:
tui.sendText(text)- Sends text to the terminaltui.pressKey(key)- Presses a key (e.g., "Enter", "Ctrl+c")tui.pressKeys(keys)- Presses multiple keys in sequence
Mouse:
tui.click(ref)- Clicks on element by reference IDtui.clickAt(x, y)- Clicks at the specified coordinatestui.doubleClick(ref)- Double-clicks on element by reference IDtui.rightClick(ref)- Right-clicks on element by reference IDtui.hover(ref)- Hovers over element by reference IDtui.drag(startRef, endRef)- Drags from one element to another
Wait:
tui.waitForText(text, timeoutMs?)- Waits for text to appear, returns booleantui.waitForIdle(timeoutMs?, idleMs?)- Waits for screen to settle, returns boolean
Control:
tui.resize(cols, rows)- Resizes the terminaltui.sendSignal(signal)- Sends a signal (SIGINT, SIGTERM, etc.)
Debug:
tui.getScrollback()- Returns number of lines scrolled off screentui.getInput(chars?)- Returns raw input buffertui.getOutput(chars?)- Returns raw output buffer
Console output is captured and returned with results:
console.log(...),console.info(...),console.warn(...),console.error(...),console.debug(...)
Returns:
{
"result": "<last expression value>",
"logs": [{"level": "log", "message": "..."}, ...]
}Get raw input sent to the process (escape sequences included). Useful for debugging what was sent to the terminal.
{
"name": "tui_get_input",
"arguments": {
"session_id": "<session_id>",
"chars": 10000
}
}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| session_id | string | Yes | - | Session identifier |
| chars | integer | No | 10000 | Maximum characters to return |
Returns:
{
"length": 1234,
"content": "<raw escape sequences>"
}Get raw PTY output (escape sequences included). Useful for debugging terminal output.
{
"name": "tui_get_output",
"arguments": {
"session_id": "<session_id>",
"chars": 10000
}
}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| session_id | string | Yes | - | Session identifier |
| chars | integer | No | 10000 | Maximum characters to return |
Returns:
{
"length": 5678,
"content": "<raw PTY output>"
}Get the number of lines that have scrolled off the visible screen.
{
"name": "tui_get_scrollback",
"arguments": {
"session_id": "<session_id>"
}
}Returns:
{
"lines": 42
}mcp-tui-driver/
tui-driver/ # Core library for PTY management and terminal emulation
src/
driver.rs # TuiDriver - main automation interface
keys.rs # Key parsing and ANSI escape sequences
mouse.rs # Mouse event handling
snapshot.rs # Accessibility snapshot generation and screenshot rendering
error.rs # Error types
lib.rs # Public API exports
mcp-server/ # MCP server binary exposing tools via JSON-RPC over stdio
src/
main.rs # CLI entrypoint and transport setup
server.rs # MCP protocol handling and tool implementations
tools.rs # Tool parameter and result types
boa.rs # JavaScript runtime integration (Boa engine)
- wezterm-term - Terminal emulation from WezTerm
- portable-pty - Cross-platform PTY (pseudo-terminal) management
- boa_engine - JavaScript execution for scripting
- rmcp - MCP protocol implementation
- image - Screenshot rendering
# Run tests
cargo test
# Check formatting
cargo fmt --check
# Run clippy
cargo clippy
# Build release binary
cargo build --releaseRUST_LOG- Set logging level (e.g.,debug,info,mcp_tui_driver=debug)
Integration tests require a functional PTY environment:
cargo test --package tui-driver -- --test-threads=1The server communicates over stdin/stdout using JSON-RPC 2.0 with the MCP protocol (version 2024-11-05).
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"<tool_name>","arguments":{...}}}MIT