Skip to main content
Glama
textual-mcp-server

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

Related MCP server: can-see

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

Available Tools

12 tools
textual_check_errorsC
Read-only

Check for collected errors and app exceptions.

Inputs: session_id (required), clear. Returns: result

textual_clickA

Click a widget by CSS selector in a running Textual app.

Inputs: session_id (required), selector (required), offset, times. Returns: result

textual_get_screen_stackC
Read-only

Get the current screen stack with modal indicators.

Inputs: session_id (required). Returns: result

textual_hoverC

Hover the mouse over a widget by CSS selector.

Inputs: session_id (required), selector (required), offset. Returns: result

textual_launchC

Launch a Textual app headlessly for QA interaction.

Inputs: app_path (required), size, session_id. Returns: result

textual_pressC

Simulate key presses in a running Textual app.

Inputs: session_id (required), keys (required). Returns: result

textual_queryB
Read-only

Query widgets matching a CSS selector with their properties.

Inputs: session_id (required), selector (required). Returns: result

textual_screenshotB
Read-only

Capture a visual screenshot of the running app (text or SVG).

Inputs: session_id (required), format. Returns: result

textual_snapshotB
Read-only

Snapshot current app state — widget tree with refs, focus, bindings, errors.

Inputs: session_id (required), max_depth, include_styles. Returns: result

textual_stopB
Destructive

Stop a running Textual app session.

Inputs: session_id (required). Returns: result

textual_type_textC

Type text into the focused input widget.

Inputs: session_id (required), text (required), submit. Returns: result

textual_wait_forC
Read-only

Wait for a condition: idle, animation, workers, or widget.

Inputs: session_id (required), condition (required), selector, text, +1 more. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.5/5.0

    Scored across 12 tools

    Disambiguation5/5

    Each tool targets a distinct action: launching, clicking, hovering, typing, pressing keys, querying widgets, taking screenshots, capturing state, waiting for conditions, checking errors, and stopping the app. No two tools overlap in purpose.

    Naming Consistency5/5

    All tools follow a consistent 'textual_' prefix followed by a descriptive verb in snake_case (e.g., textual_click, textual_query). No deviations in naming convention.

    Tool Count5/5

    With 12 tools, the server covers all essential interactions for headless QA of Textual apps: launching, UI manipulation, state inspection, and error checking. The count is well-scoped without unnecessary redundancy.

    Completeness4/5

    The tool set covers core workflows (launch, interact, query, debug, stop). Minor gaps like drag-and-drop or scroll are absent but not critical for typical testing scenarios.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers