textual-mcp-server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@textual-mcp-server launch my_app.py and snapshot the UI"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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
Pilottesting APIFull 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-serverFor 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-mcpWith Claude Code
claude mcp add textual -- textual-mcpOr 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) → cleanupTools
Lifecycle
Tool | Description |
| Launch a Textual app headlessly. Accepts a file path ( |
| Stop a running session and return any collected errors. |
Interaction
Tool | Description |
| Simulate key presses (e.g., |
| Click a widget by CSS selector with optional offset and repeat count. |
| Type text into the focused input widget, with optional submit (Enter). |
| Hover the mouse over a widget by CSS selector. |
Observation
Tool | Description |
| Snapshot the widget tree with ref markers, focus state, active key bindings, and errors. |
| Capture the current screen as plain text or SVG. |
| Query widgets matching a CSS selector. Returns type, ID, classes, and extracted properties. |
| Get the current screen stack with modal indicators. |
Assertion & Waiting
Tool | Description |
| Wait for a condition: |
| 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:
AppSessionwraps Textual'sApp.run_test()to provide launch/stop semantics with a persistentPilothandleWidgetTreeSerializerproduces LLM-friendly text output — interactive widgets get[ref=N]markers; scrollbars and hidden widgets are excludedWidgetStateExtractoruses an ordered isinstance registry to extract properties from Input, Button, DataTable, TextArea, Tree, and 11 other widget typesErrorCollectorhooks into Textual's message system to captureWorker.StateChangederrors 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 -vRequirements
License
MIT
Available Tools
12 toolstextual_check_errorsCRead-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_stackCRead-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_queryBRead-only
Query widgets matching a CSS selector with their properties.
Inputs: session_id (required), selector (required). Returns: result
textual_screenshotBRead-only
Capture a visual screenshot of the running app (text or SVG).
Inputs: session_id (required), format. Returns: result
textual_snapshotBRead-only
Snapshot current app state — widget tree with refs, focus, bindings, errors.
Inputs: session_id (required), max_depth, include_styles. Returns: result
textual_stopBDestructive
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_forCRead-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
Scored across 12 tools
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.
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.
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.
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
Related MCP Connectors
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
Official MCP server for Agentwork — delegate tasks to AI agents with human-in-the-loop
Trading MCP server for AI agents, with live market data, account reads and controlled execution.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceMCP server enabling AI agents to interact with terminal applications through structured Terminal State Tree representation. Works with any AI assistant that supports the Model Context Protocol.95 npm21MIT
- AlicenseNot gradedqualityCmaintenanceMCP server that lets AI agents see and interact with terminal/CLI applications through virtual terminals and PNG screenshots.75 npmMIT
- FlicenseNot gradedqualityDmaintenanceMCP server that enables an AI agent to display rich Markdown content on a physical monitor connected to a headless Linux device via a Textual/Rich TUI.1-
- AlicenseBqualityDmaintenanceLocal + remote terminal interaction control MCP Server. Lets AI agents control interactive TUI programs the way a human would.298 npmMIT