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

A
license - permissive license
-
quality - not tested
D
maintenance

Maintenance

Maintainers
Response time
Release cycle
1Releases (12mo)

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

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