Metadata-Version: 2.5
Name: pyside6-mcp
Version: 0.1.0
Summary: Playwright-style MCP server for PySide6 apps — inspect, control, and debug via MCP
Project-URL: Homepage, https://github.com/com55/pyside6-mcp
Project-URL: Repository, https://github.com/com55/pyside6-mcp
Project-URL: Issues, https://github.com/com55/pyside6-mcp/issues
License: MIT
License-File: LICENSE
Keywords: claude,debug,gui,mcp,mcp-server,pyside6,qt
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Debuggers
Requires-Python: >=3.11
Requires-Dist: fastmcp>=2.0
Requires-Dist: httpx>=0.27
Provides-Extra: bridge
Requires-Dist: pyside6>=6.6; extra == 'bridge'
Description-Content-Type: text/markdown

<!-- mcp-name: io.github.com55/pyside6-mcp -->
# pyside6-mcp

Playwright-style MCP server for PySide6 apps — lets AI assistants see, control, and debug your Python desktop GUI without modifying your app's source code.

```
AI assistant → MCP tools → pyside6-mcp server → HTTP bridge (ephemeral localhost port) → PySide6 app
```

## Features

- **Screenshot** any window or widget
- **Inspect** the full widget tree (class, name, geometry, text, state)
- **Click, type, scroll, press keys** — full interaction
- **Find widgets** by class, objectName, or text content
- **Read Python logs** captured from the app
- **Run Python** inside the app process for advanced inspection
- **Launch and stop** the app from the agent

Zero changes to your app's source code required.

## Requirements

- Python 3.11+
- PySide6 6.6+
- [uv](https://docs.astral.sh/uv/) (recommended)
- Windows (tested), Linux/macOS (should work)

## Installation

The MCP server (stdio) and the in-app bridge are separate:

| Component | Where it runs | Needs PySide6? |
|-----------|---------------|----------------|
| **MCP server** (`pyside6-mcp`) | MCP client's process | No |
| **Bridge** (`python -m pyside6_mcp …`) | Inside your PySide6 app | Yes |

**No changes to the target project.** `launch_app(cwd=…)` injects only the
bridge modules via an isolated `PYTHONPATH` — nothing is added to `pyproject.toml`,
and the MCP server's dependencies (fastmcp, httpx, …) never enter the app environment.

Requires [uv](https://docs.astral.sh/uv/) on `PATH`. MCP Registry name:
`io.github.com55/pyside6-mcp`.

Unlike [qt-mcp](https://github.com/0xCarbon/qt-mcp), you do **not** add this
package to the target app or set a probe env var. `launch_app` injects the bridge.

### Claude Code

Plugin (MCP + skill):

```bash
claude plugin install github:com55/pyside6-mcp
```

MCP server only:

```bash
claude mcp add -s user pyside6 -- uvx pyside6-mcp
```

### Cursor

Install the Agent Plugin from this repository (or from the Cursor Marketplace
once listed). Manual stdio config:

```json
{
  "mcpServers": {
    "pyside6": {
      "command": "uvx",
      "args": ["pyside6-mcp"]
    }
  }
}
```

After saving, reload MCP servers in **Cursor Settings → MCP**.
See [`examples/cursor-mcp-config.json`](examples/cursor-mcp-config.json).

### Any stdio MCP client

```json
{
  "mcpServers": {
    "pyside6": {
      "command": "uvx",
      "args": ["pyside6-mcp"]
    }
  }
}
```

Place it wherever that client expects MCP config (user-level or project-level).
See [`examples/mcp-config.json`](examples/mcp-config.json).

### VS Code / GitHub Copilot / other clients

Same stdio block as **Any stdio MCP client**.

> **Note:** `uvx` downloads and runs the MCP server in an isolated env — PySide6 is **not**
> required there. PySide6 is only needed in the target app's venv (already a project dependency).
>
> To run from a git checkout instead of PyPI: `uvx --from git+https://github.com/com55/pyside6-mcp pyside6-mcp`

## Usage

### From MCP (recommended — zero project setup)

```
launch_app(cwd="/path/to/project")                              # main.py at root
launch_app(cwd="/path/to/project", script="app.py")             # other name at root
launch_app(cwd="/path/to/project", script="backend/gui.py")   # entry in subfolder
get_launch_help()                                               # full script decision guide
list_apps()                                                     # pids of apps this server launched
```

`cwd` is the project root (`pyproject.toml`). `script` is the entry `.py` **relative to cwd**.

`launch_app` returns `{pid}` (the Qt app process, not the `uv` wrapper). Other tools omit `pid` to target the last launched app; pass `pid=` when several are running. Do not pass `port=`.

### Manual equivalent (same isolated PYTHONPATH that `launch_app` builds)

```bash
cd your-pyside6-project
# PYTHONPATH must point at a directory that contains *only* pyside6_mcp
# (bridge modules). Do not point it at the MCP server's site-packages.
uv run python -m pyside6_mcp main.py
```

The bridge starts automatically. Manual runs default to `http://127.0.0.1:7890` (override with `PYSIDE6_MCP_PORT`). `launch_app` always uses an ephemeral port.

### Or: embed the bridge (optional, for always-on)

```python
# In your app's main(), before app.exec()
from pyside6_mcp import install_bridge
install_bridge()
```

### From your AI assistant

Once the app is running with the bridge active, ask your assistant:

> "Screenshot the app and click the Apply button"
> "Why is the checkbox disabled? Inspect its state."
> "Fill in the form and submit it"
> "Show me the last 20 log lines from the app"

Your assistant uses the `launch_app`, `screenshot`, `get_widget_tree`, `find_widget`, `click`, `type_text`, `get_logs`, and other tools automatically.

`launch_app` returns only when the UI is ready (a visible top-level window that has been quiet for at least 500 ms), not merely when the bridge HTTP server is up. Default `timeout` is 45 seconds.

## Tools

| Tool | Description |
|------|-------------|
| `launch_app(cwd, script?, app_args?, timeout)` | Launch app (no target-project install) and wait for UI readiness. Returns `{pid}` |
| `list_apps()` | Pids of apps launched by this server |
| `get_launch_help()` | How to set cwd, script, app_args before launch |
| `wait_until_ready(timeout, quiet_ms, pid?)` | Wait for UI readiness on an already-running app |
| `wait_for_idle(timeout, quiet_ms, pid?)` | Wait until UI has been quiet after an action |
| `get_app_status(pid?)` | Process + bridge health; detects likely modal blocks |
| `stop_app(pid?)` | Stop a launched app |
| `screenshot(widget_id?, pid?)` | Capture window or specific widget (modal/active-window aware) |
| `get_widget_tree(pid?)` | Full widget hierarchy with IDs |
| `get_widget_info(widget_id, pid?)` | Detailed properties of one widget |
| `get_app_state(pid?)` | Active window, focus, screen info |
| `find_widget(class_name?, object_name?, text?, visible?, pid?)` | Search widgets |
| `click(widget_id?, x?, y?, button?, pid?)` | Mouse click |
| `double_click(widget_id, x?, y?, pid?)` | Double click |
| `type_text(text, widget_id?, pid?)` | Keyboard input |
| `press_key(key, pid?)` | Named key: enter, escape, tab, up/down, f5, … |
| `scroll(dy, widget_id?, dx?, pid?)` | Scroll wheel |
| `list_actions(pid?)` | List QAction menu/toolbar items |
| `trigger_action(name?, text?, pid?)` | Trigger a QAction without clicking menus |
| `get_logs(n?, pid?)` | Recent Python log records |
| `get_app_output(n?, pid?)` | Raw stdout/stderr from launched app |
| `eval_python(code, pid?)` | Execute Python inside the app process |

## Agent Skill

A companion skill ships at [`skills/pyside6-mcp/SKILL.md`](skills/pyside6-mcp/SKILL.md)
and is installed automatically with the plugin. It tells the agent when and how
to use these tools — no need to explain the workflow every time.

## Architecture

```
pyside6_mcp/
├── bridge.py      # In-process HTTP server (runs inside the target app)
├── launch.py      # Isolated PYTHONPATH inject + launch_app argv/validation
├── session.py     # Handshake + in-memory pid → port sessions
├── server.py      # FastMCP stdio server (the MCP client talks to this)
├── __init__.py    # Exports install_bridge()
└── __main__.py    # Launcher: python -m pyside6_mcp <script>
```

**Thread safety**: all Qt operations are marshaled to the main thread via `QApplication.postEvent` with a custom event type — the same mechanism Qt uses internally for cross-thread signals.

## Examples

- [`examples/test_app.py`](examples/test_app.py) — minimal PySide6 app to verify the bridge
- [`examples/mcp-config.json`](examples/mcp-config.json) — generic MCP config (Cursor, VS Code, Windsurf, …)
- [`examples/cursor-mcp-config.json`](examples/cursor-mcp-config.json) — Cursor-specific template
- [`examples/claude-mcp-config.json`](examples/claude-mcp-config.json) — same format, kept for reference

## License

MIT
