Skip to main content
Glama

Qt Pilot

License: MIT

An MCP server for headless Qt/PySide6 GUI testing. Enables AI assistants like Claude to visually test and interact with Qt desktop applications.

Repository: github.com/neatobandit0/qt-pilot

Features

  • Launch Qt apps headlessly via Xvfb virtual display

  • Capture screenshots for visual verification

  • Simulate interactions: clicks, hovers, keyboard input

  • Widget discovery by object name

  • App health monitoring with stderr capture

  • Full Qt introspection via QTest and Qt APIs

Installation

From GitHub

git clone https://github.com/neatobandit0/qt-pilot.git ~/.claude/plugins/qt-pilot pip install -r ~/.claude/plugins/qt-pilot/requirements.txt

Manual Installation

Copy the plugin to your Claude plugins directory:

cp -r qt-pilot ~/.claude/plugins/

Then add to your ~/.claude.json:

{ "mcpServers": { "qt-pilot": { "type": "stdio", "command": "python3", "args": ["/path/to/qt-pilot/server/main.py"] } } }

Dependencies

pip install mcp PySide6

Also requires Xvfb for headless display:

# Debian/Ubuntu sudo apt install xvfb # RHEL/CentOS/Fedora sudo yum install xorg-x11-server-Xvfb # macOS (via Homebrew) brew install xquartz

MCP Tools

launch_app

Launch a Qt application headlessly.

# Script mode launch_app(script_path="/path/to/test_gui.py") # Module mode launch_app(module="myapp.main", working_dir="/path/to/project")

capture_screenshot

Capture the current window.

capture_screenshot(output_path="/tmp/screenshot.png")

click_widget

Click a widget by its object name.

click_widget(widget_name="submit_button", button="left")

hover_widget

Hover over a widget.

hover_widget(widget_name="menu_item")

type_text

Type text into a widget or focused widget.

type_text(text="hello world", widget_name="search_input") type_text(text="hello") # Types into currently focused widget

press_key

Simulate a key press with optional modifiers.

press_key(key="Enter") press_key(key="S", modifiers=["Ctrl"]) # Ctrl+S press_key(key="Tab")

find_widgets

List widgets matching a name pattern.

find_widgets(name_pattern="*") # All named widgets find_widgets(name_pattern="btn_*") # Widgets starting with "btn_"

get_widget_info

Get detailed widget information.

get_widget_info(widget_name="submit_button") # Returns: type, visible, enabled, size, position, text, checked state, etc.

get_app_status

Check if the application is still running and get diagnostics.

get_app_status() # Returns: {"running": true, "exit_code": null, "stderr": "", "display": ":99"}

wait_for_idle

Wait for the Qt event queue to settle after actions.

click_widget(widget_name="load_button") wait_for_idle(timeout=5.0) # Wait for async operations to complete capture_screenshot()

close_app

Close the running application.

close_app()

Requirements for Target Applications

For widget interactions to work, your Qt application must:

  1. Set object names on interactive widgets:

    button = QPushButton("Click Me") button.setObjectName("my_button") # Required for widget discovery
  2. Use QApplication (not QCoreApplication)

  3. Show at least one window

Architecture

┌─────────────────────────────┐ │ AI Assistant (Claude) │ └─────────────┬───────────────┘ │ MCP Protocol (stdio) ▼ ┌─────────────────────────────┐ │ MCP Server (main.py) │ │ - Tool definitions │ │ - Process management │ └─────────────┬───────────────┘ │ Unix Socket (IPC) ▼ ┌─────────────────────────────┐ │ Test Harness (harness.py) │ │ - Runs inside Xvfb │ │ - QTest interactions │ │ - Widget introspection │ ├─────────────────────────────┤ │ Your Qt Application │ └─────────────────────────────┘

Example Workflow

# 1. Launch a test app launch_app(module="myapp.main", working_dir="/path/to/project") # 2. List available widgets find_widgets() # 3. Interact with the UI click_widget(widget_name="login_button") wait_for_idle() # 4. Type into a field type_text(text="user@example.com", widget_name="email_input") press_key(key="Tab") type_text(text="password123", widget_name="password_input") # 5. Submit and capture result click_widget(widget_name="submit_button") wait_for_idle(timeout=3.0) capture_screenshot(output_path="/tmp/result.png") # 6. Clean up close_app()

Troubleshooting

"Widget not found"

  • Ensure the widget has setObjectName() called

  • Use find_widgets() to list available widget names

"No app is running"

  • Call launch_app() first

  • Check that the script/module path is correct

App crashes silently

  • Use get_app_status() to check for errors

  • The stderr field contains crash information

Screenshots are blank

  • Ensure the application creates and shows a window

  • Use wait_for_idle() after launch for window to render

License

MIT License - see LICENSE file.

-
security - not tested
A
license - permissive license
-
quality - not tested

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/neatobandit0/qt-pilot'

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