Skip to main content

MCP server for programmatically driving Textual TUI apps

Project description

textual-mcp-server

An MCP server that lets AI agents launch, interact with, and inspect Textual TUI applications headlessly. Drive any Textual app through its full lifecycle — click buttons, type text, read widget state, take screenshots — all via the Model Context Protocol.

Features

  • Headless app execution — Launch any Textual app without a terminal, powered by Textual's built-in Pilot testing API
  • Full interaction toolkit — Click, type, press keys, and hover over widgets using CSS selectors
  • Rich state inspection — Snapshot the widget tree, query widgets by selector, and extract type-specific properties from 16+ widget types
  • Multi-session support — Run multiple apps concurrently with isolated sessions
  • Error tracking — Automatic collection of worker errors and app exceptions via message hooks
  • Screenshot capture — Export the current screen as plain text or SVG

Installation

Requires Python 3.10+.

pip install textual-mcp-server

For development:

git clone https://github.com/discohead/textual-mcp-server.git
cd textual-mcp-server
pip install -e ".[dev]"

Quick Start

As a standalone server

textual-mcp

With Claude Code

Add to your MCP configuration (e.g., ~/.claude.json or project .mcp.json):

{
  "mcpServers": {
    "textual": {
      "command": "textual-mcp"
    }
  }
}

Typical workflow

1. textual_launch("my_app.py")         → session_id
2. textual_snapshot(session_id)         → widget tree + focus + bindings
3. textual_click(session_id, "#submit") → interact
4. textual_screenshot(session_id)       → visual output
5. textual_stop(session_id)             → cleanup

Tools

Lifecycle

Tool Description
textual_launch Launch a Textual app headlessly. Accepts a file path (app.py), path with class (app.py:MyApp), or module path (mypackage.module:MyApp). Returns a session_id.
textual_stop Stop a running session and return any collected errors.

Interaction

Tool Description
textual_press Simulate key presses (e.g., ["enter"], ["ctrl+s"]).
textual_click Click a widget by CSS selector with optional offset and repeat count.
textual_type_text Type text into the focused input widget, with optional submit (Enter).
textual_hover Hover the mouse over a widget by CSS selector.

Observation

Tool Description
textual_snapshot Snapshot the widget tree with ref markers, focus state, active key bindings, and errors.
textual_screenshot Capture the current screen as plain text or SVG.
textual_query Query widgets matching a CSS selector. Returns type, ID, classes, and extracted properties.
textual_get_screen_stack Get the current screen stack with modal indicators.

Assertion & Waiting

Tool Description
textual_wait_for Wait for a condition: idle, animation, workers (all complete), or widget (selector appears).
textual_check_errors Check for collected worker errors and app exceptions.

Architecture

textual_mcp/
├── server.py              # FastMCP server, tool registration, and tool implementations
├── session.py             # AppSession — headless app lifecycle via Pilot
├── session_manager.py     # Multi-session management
├── app_loader.py          # Dynamic app loading from file or module path
├── error_collector.py     # Message hook for worker error aggregation
└── serializers/
    ├── widget_tree.py     # DOM → indented text tree with [ref=N] markers
    └── widget_state.py    # Type-specific property extraction (16 widget types)

Key design decisions:

  • AppSession wraps Textual's App.run_test() to provide launch/stop semantics with a persistent Pilot handle
  • WidgetTreeSerializer produces LLM-friendly text output — interactive widgets get [ref=N] markers; scrollbars and hidden widgets are excluded
  • WidgetStateExtractor uses an ordered isinstance registry to extract properties from Input, Button, DataTable, TextArea, Tree, and 11 other widget types
  • ErrorCollector hooks into Textual's message system to capture Worker.StateChanged errors without disrupting normal operation

Supported Widget Types

The state extractor provides rich property data for:

Input, Button, Static, Label, Checkbox, Switch, Select, TextArea, DataTable, Tree, ListView, OptionList, TabbedContent, ProgressBar, RadioSet, ContentSwitcher

Development

# Run tests
pytest

# Run a specific test
pytest tests/test_integration_calculator.py -v

Requirements

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

textual_mcp_server-1.0.0.tar.gz (28.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

textual_mcp_server-1.0.0-py3-none-any.whl (15.0 kB view details)

Uploaded Python 3

File details

Details for the file textual_mcp_server-1.0.0.tar.gz.

File metadata

  • Download URL: textual_mcp_server-1.0.0.tar.gz
  • Upload date:
  • Size: 28.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for textual_mcp_server-1.0.0.tar.gz
Algorithm Hash digest
SHA256 7a2063aa634804d01e779447b2ba644e148203ab4b1f6492f71c506210691504
MD5 4663c806319e9d864f102ef6cc4504f2
BLAKE2b-256 fa774793bfd49f759e92f31fe8ca37a00bc352b37491d80bc20d16c31234d046

See more details on using hashes here.

File details

Details for the file textual_mcp_server-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for textual_mcp_server-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 67f4e8834036fba4f1e871329a535e99f7d075966dbc0f6232c3540b98db13d1
MD5 a335336d8766542feda115f28749365e
BLAKE2b-256 6d89df12a65367a32c63a41dcae828abe7c7df47c23714f6907dc98abad7ef65

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page