Skip to main content
Glama
discohead

textual-mcp-server

by discohead

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

claude mcp add textual -- textual-mcp

Or add manually 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

Latest Blog Posts

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/discohead/textual-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server