Skip to main content
Glama
GeorgePearse

MCP TUI Test

by GeorgePearse
README.md
# MCP TUI Test

**Like Playwright, but for Terminal User Interfaces**

An MCP (Model Context Protocol) server that enables AI assistants to test Terminal User Interface (TUI) applications. This server provides tools to launch, interact with, and verify TUI applications programmatically.

šŸ“š **[View Documentation](https://georgepearse.github.io/mcp-tui-test/)**

## Features

- **Dual Testing Modes**: Choose between stream mode (for CLI tools) or buffer mode (for full TUIs with position awareness)
- **Launch TUI Applications**: Start any terminal-based application with configurable dimensions
- **Send Keyboard Input**: Simulate user typing, special keys, and control combinations
- **Capture Screen Output**: Read and analyze the current terminal display
- **Position-Based Testing**: Verify text at specific screen coordinates (buffer mode)
- **Cursor Tracking**: Monitor cursor position in real-time (buffer mode)
- **Wait for Text**: Asynchronously wait for specific content to appear
- **Assertions**: Verify that expected content is present in the output
- **Session Management**: Run multiple TUI applications simultaneously

## Testing Modes

### Stream Mode (Default)
- Best for: CLI tools, command-line applications, simple interactive programs
- Uses: pexpect for stream-based testing
- Features: Text matching, pattern waiting, output capture
- Example use cases: git, npm, grep, interactive shell scripts

### Buffer Mode
- Best for: Full TUI applications, ncurses apps, dialog boxes, menus
- Uses: pexpect + pyte for screen buffer emulation
- Features: All stream features PLUS position-based assertions, cursor tracking, region extraction
- Example use cases: htop, vim, dialog, interactive menus

**When to use which mode:**
- Use **stream mode** for applications that output text sequentially
- Use **buffer mode** for applications that draw complex UIs with cursor movement

## Installation

### Prerequisites

- Python 3.10 or higher
- [uv](https://docs.astral.sh/uv/)

### Install

Once published to PyPI, no install is needed — run it straight from `uvx`:

```bash
uvx mcp-tui-test
```

For development:

```bash
uv venv
source .venv/bin/activate
uv pip install -e ".[dev]"
```

## Go implementation

A sibling Go implementation lives in [`go/`](go/) — the **same tool contract**
(same tool names, same structured result shapes, same `tui://{session_id}/screen`
resource), as a single static binary with zero runtime dependencies:

```bash
go install github.com/GeorgePearse/mcp-tui-test/go/cmd/mcp-tui-test@latest
```

```json
{
  "mcpServers": {
    "tui-test": { "command": "mcp-tui-test" }
  }
}
```

Built on the official [Go MCP SDK](https://github.com/modelcontextprotocol/go-sdk),
`creack/pty`, and `vt10x` for buffer-mode screen emulation. The Python package
remains the home of the pytest plugin; the two share the tool schema, so clients
can't tell them apart.

## Structured results

Every tool returns structured content (with an output schema), not prose: a
`success` flag plus typed fields (`screen`, `outcome`, `passed`, `row`/`col`,
`screen_excerpt` on failed assertions, `error` when something broke). Agents and
scripts branch on fields instead of parsing āœ“/āœ— strings.

The current screen is also exposed as an MCP **resource** at
`tui://{session_id}/screen`.

For harness-side loops, result handling, and session ordering, see
[Composing TUI tests in a coding harness](docs/codemode.md).

## Session limits & cleanup

Sessions are capped and reaped automatically:

- `MCP_TUI_MAX_SESSIONS` (default 8) — launching past the cap returns a structured error.
- `MCP_TUI_SESSION_TTL_S` (default 900) — sessions older than this are closed on the next access.
- Dead processes are reaped on every registry access and reported by `list_sessions`.
- All sessions are force-closed (SIGKILL escalation) at interpreter exit — no zombies.

## pytest plugin

The same engine drives deterministic CI tests with no LLM in the loop. Installing
the package registers a `tui` fixture:

```python
def test_app_header(tui):
    s = tui.launch("my-app --demo", mode="buffer", dimensions=(120, 40))
    s.wait_until_contains("Dashboard", timeout=10)
    s.assert_at("Dashboard", 0, 2)
    s.send("q")
```

`TuiHandle` offers `send`, `send_ctrl`, `expect`, `assert_contains`, `assert_at`,
`wait_until_contains`, `screen`, `line`, and `cursor`; failures raise
`AssertionError` with the current screen attached. Sessions are closed at test
teardown, pass or fail.

## Usage

### Running the MCP Server

```bash
python server.py
```

Or if installed as a package:

```bash
mcp-tui-test
```

### Configure in Claude Desktop

Add this to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "tui-test": {
      "command": "uvx",
      "args": ["mcp-tui-test"]
    }
  }
}
```

(A local checkout also works: `"command": "python", "args": ["/path/to/mcp-tui-test/server.py"]`.)
```

## Available Tools

### `launch_tui`
Launch a TUI application for testing.

**Parameters:**
- `command` (required): The command to launch the TUI application
- `session_id` (optional): Unique identifier for this session (default: "default")
- `timeout` (optional): Command timeout in seconds (default: 30)
- `dimensions` (optional): Terminal dimensions as WIDTHxHEIGHT (default: "80x24")
- `mode` (optional): Testing mode - "stream" or "buffer" (default: "stream")

**Examples:**
```python
# Stream mode for CLI tools
launch_tui(command="python example_tui_app.py", session_id="test1")

# Buffer mode for full TUI applications
launch_tui(command="htop", session_id="test2", mode="buffer", dimensions="120x40")
```

### `send_keys`
Send keyboard input to a TUI application.

**Parameters:**
- `keys` (required): Keys to send. Use `\n` for Enter, `\t` for Tab, `\x1b` for Escape
- `session_id` (optional): Session identifier (default: "default")
- `delay` (optional): Delay in seconds after sending keys (default: 0.1)

**Example:**
```
send_keys(keys="1\n", session_id="test1")
```

### `send_ctrl`
Send a Ctrl+Key combination to the TUI application.

**Parameters:**
- `key` (required): The key to combine with Ctrl (e.g., 'c', 'd', 'z')
- `session_id` (optional): Session identifier (default: "default")

**Example:**
```
send_ctrl(key="c", session_id="test1")
```

### `capture_screen`
Capture the current screen output of a TUI application.

**Parameters:**
- `session_id` (optional): Session identifier (default: "default")
- `include_ansi` (optional): Whether to include ANSI escape codes in stream mode (default: False)
- `use_buffer` (optional): Force buffer/stream mode. Auto-detects if None (default: None)

**Examples:**
```python
# Auto-detect mode based on session
capture_screen(session_id="test1")

# Force buffer mode capture
capture_screen(session_id="test1", use_buffer=True)
```

### `expect_text`
Wait for specific text to appear in the TUI output.

**Parameters:**
- `pattern` (required): Text or regex pattern to wait for
- `session_id` (optional): Session identifier (default: "default")
- `timeout` (optional): Maximum time to wait in seconds (default: 10)

**Example:**
```
expect_text(pattern="Welcome", session_id="test1", timeout=5)
```

### `assert_contains`
Assert that the current screen contains specific text.

**Parameters:**
- `text` (required): Text to search for in the current screen
- `session_id` (optional): Session identifier (default: "default")
- `use_buffer` (optional): Check buffer/stream mode. Auto-detects if None (default: None)

**Example:**
```python
assert_contains(text="Counter value: 1", session_id="test1")
```

### `assert_at_position` (Buffer Mode Only)
Assert that specific text appears at a screen position.

**Parameters:**
- `text` (required): Text to verify at the position
- `row` (required): Row number (0-indexed)
- `col` (required): Column number (0-indexed)
- `session_id` (optional): Session identifier (default: "default")

**Example:**
```python
# Verify "Error" appears at row 5, column 10
assert_at_position(text="Error", row=5, col=10, session_id="test1")
```

### `get_cursor_position` (Buffer Mode Only)
Get the current cursor position.

**Parameters:**
- `session_id` (optional): Session identifier (default: "default")

**Example:**
```python
get_cursor_position(session_id="test1")
# Returns: "Cursor position (session: test1): row 10, column 25"
```

### `get_screen_region` (Buffer Mode Only)
Extract a rectangular region of the screen.

**Parameters:**
- `row_start` (required): Starting row (0-indexed, inclusive)
- `row_end` (required): Ending row (0-indexed, exclusive)
- `col_start` (optional): Starting column (0-indexed, inclusive, default: 0)
- `col_end` (optional): Ending column (0-indexed, exclusive, default: end of line)
- `session_id` (optional): Session identifier (default: "default")

**Example:**
```python
# Extract rows 5-10, full width
get_screen_region(row_start=5, row_end=10, session_id="test1")

# Extract rows 5-10, columns 20-60
get_screen_region(row_start=5, row_end=10, col_start=20, col_end=60, session_id="test1")
```

### `get_line` (Buffer Mode Only)
Get a specific line from the screen buffer.

**Parameters:**
- `row` (required): Row number (0-indexed)
- `session_id` (optional): Session identifier (default: "default")

**Example:**
```python
get_line(row=3, session_id="test1")
# Returns: "Line 3 (session: test1): [line content]"
```

### `close_session`
Close a TUI testing session.

**Parameters:**
- `session_id` (optional): Session identifier (default: "default")

**Example:**
```
close_session(session_id="test1")
```

### `list_sessions`
List all active TUI testing sessions.

**Example:**
```
list_sessions()
```

## Example Test Scenario

Here's how you might use this MCP to test a TUI application:

1. **Launch the application**:
   ```
   launch_tui(command="python example_tui_app.py")
   ```

2. **Wait for it to load**:
   ```
   expect_text(pattern="Welcome to the Example TUI Application")
   ```

3. **Interact with it**:
   ```
   send_keys(keys="1\n")
   ```

4. **Verify output**:
   ```
   assert_contains(text="Hello, TUI Tester!")
   ```

5. **Clean up**:
   ```
   close_session()
   ```

## Testing the Example App

This repository includes an example TUI application (`example_tui_app.py`) that you can use to test the MCP server.

Run it directly:
```bash
python example_tui_app.py
```

Or test it through the MCP:
```
launch_tui(command="python example_tui_app.py")
send_keys(keys="1\n")
capture_screen()
```

## Use Cases

- **Automated Testing**: Verify TUI applications behave correctly
- **Integration Testing**: Test command-line tools and interactive CLIs
- **Documentation**: Generate screenshots and examples from TUI apps
- **Debugging**: Inspect the state of TUI applications during development
- **CI/CD**: Add TUI testing to your continuous integration pipeline

## Technical Details

This MCP server uses:
- **FastMCP**: For the MCP server implementation
- **pexpect**: For spawning and controlling terminal applications
- **pyte**: For terminal emulation and screen buffer management (buffer mode)
- **ScreenSession wrapper**: Combines pexpect and pyte for hybrid testing

### Architecture
- **Stream Mode**: pexpect directly captures output stream
- **Buffer Mode**: pexpect output → pyte terminal emulator → screen buffer
- **Auto-detection**: Tools automatically use appropriate mode based on session

## Limitations

- Currently designed for Unix-like systems (Linux, macOS)
- Windows support may require modifications (consider using `winpty` or similar)
- Mouse support in TUIs is not currently available
- Buffer mode requires slightly more memory for screen emulation
- Position-based assertions only work in buffer mode

## Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

## License

MIT License - see LICENSE file for details

## Related Projects

- [Playwright](https://playwright.dev/) - Browser automation (inspiration for this project)
- [pexpect](https://pexpect.readthedocs.io/) - Python module for spawning child applications
- [pyte](https://pyte.readthedocs.io/) - Python terminal emulator
- [MCP](https://modelcontextprotocol.io/) - Model Context Protocol specification

## Author

Created for testing TUI applications with AI assistance.

## Documentation development

The site uses Material for MkDocs, matching [VisionChain](https://github.com/GeorgePearse/VisionChain/blob/main/mkdocs.yml)
and [Bayesian Filters](https://github.com/GeorgePearse/bayesian_filters/blob/master/mkdocs.yml).

```bash
uv venv .venv-docs
uv pip install --python .venv-docs/bin/python -r docs-requirements.txt
.venv-docs/bin/mkdocs serve
.venv-docs/bin/mkdocs build --strict
```

For a build you can open directly from disk with local search, use
`MKDOCS_OFFLINE=true .venv-docs/bin/mkdocs build --strict --site-dir /tmp/mcp-tui-test-offline`.
Offline builds use `.html` page URLs; normal builds retain the published directory URLs.

TDQS

B3.3/5.0

Scored across 12 tools

Disambiguation4/5

Session lifecycle (launch_tui, close_session, list_sessions), input (send_keys, send_ctrl), and assertions (assert_contains, assert_at_position) are clearly distinct. There is mild overlap among screen-reading tools (get_line, get_screen_region, capture_screen) and between send_keys/send_ctrl and expect_text/assert_contains, but descriptions resolve these boundaries well.

Naming Consistency4/5

All names are snake_case and verb-first, which is highly consistent. The only slight wrinkle is that screen reads mix verbs (get_line, get_screen_region vs capture_screen), but each verb still conveys the action clearly.

Tool Count5/5

12 tools is well within the ideal range and each maps to a distinct testing need (session management, input, screen inspection, waiting, assertions). No tool feels redundant or bolted on.

Completeness4/5

The surface covers the full TUI testing loop: launch/close/list sessions, send input, read screen state, wait for patterns, and assert on content/position. Minor gaps exist — no terminal resize after launch and no mouse/click input — but core workflows are fully supported.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive