Metadata-Version: 2.4
Name: qinspector-mcp
Version: 0.1.0
Summary: stdio MCP bridge for Qt apps that embed QInspector
Project-URL: Homepage, https://github.com/DebiruC/QInspector
Author: NineYYY
License: MIT
Keywords: debug,mcp,qinspector,qt
Requires-Python: >=3.10
Requires-Dist: mcp>=1.0.0
Description-Content-Type: text/markdown

# QInspector MCP Server

stdio MCP bridge for Qt apps that embed **QInspector**. The MCP process **listens** on TCP; apps are clients (default `127.0.0.1:9876`). Up to 8 apps can be connected at the same time.

```text
Agent --stdio MCP--> qinspector-mcp --listen TCP--> Qt App(s) (QInspector)
```

## App identity

On connect each app sends `app.hello` with a stable `appId` (default `QCoreApplication::applicationName()`, override with `QInspector::SetAppId()`) plus its pid. The bridge keys apps by `appId`; two instances of the same app become `editor` and `editor#2`. Apps that never send `app.hello` get a fallback key `app-N`.

Tools resolve a session by `name` alone when it is globally unique; pass `app` only when several apps expose the same session name (the error message lists the candidates).

## Requirements

- [uv](https://docs.astral.sh/uv/) (recommended) **or** Python ≥ 3.10 + pip
- A build of the Qt example / your app with QInspector `Connect()` + `Attach(...)`

**Port conflict:** only one process may listen on the configured port (default `9876`).

## Install / run with uv (recommended)

Package name: **`qinspector-mcp`**. Console script entry: **`qinspector-mcp`**.

### From a local checkout (this monorepo)

```bash
# one-shot (uv builds a temp env, installs deps, runs the script)
uvx --from /path/to/QInspector/mcp qinspector-mcp

# or install into the current project / venv
cd /path/to/QInspector/mcp
uv pip install -e .
qinspector-mcp
```

### From Git (after the repo is public)

```bash
uvx --from "git+https://github.com/DebiruC/QInspector.git#subdirectory=mcp" qinspector-mcp
```

### From PyPI (after you publish)

```bash
uvx qinspector-mcp
# or: uv tool install qinspector-mcp && qinspector-mcp
```

Publishing (when ready):

```bash
cd mcp
uv build
uv publish   # needs PyPI credentials / token
```

### Without uv

```bash
cd mcp
pip install -e .
qinspector-mcp
# or: python -m qinspector_mcp
```

Then start `QtWidgets` / `QtQuick`. The server logs `session.hello` when sessions register.

## Claude Code / Cursor config

Paste into the host’s MCP config (e.g. Claude Code user/`project` MCP settings, or Cursor `mcp.json`). No `cwd` / `PYTHONPATH` — `uvx` installs the entry point in its own env.

### PyPI (recommended after publish)

```json
{
  "mcpServers": {
    "qinspector": {
      "command": "uvx",
      "args": ["qinspector-mcp"],
      "env": {
        "QINSPECTOR_HOST": "127.0.0.1",
        "QINSPECTOR_PORT": "9876",
        "QINSPECTOR_RPC_TIMEOUT": "10"
      }
    }
  }
}
```

### From Git (before / without PyPI)

```json
{
  "mcpServers": {
    "qinspector": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/DebiruC/QInspector.git#subdirectory=mcp",
        "qinspector-mcp"
      ],
      "env": {
        "QINSPECTOR_HOST": "127.0.0.1",
        "QINSPECTOR_PORT": "9876",
        "QINSPECTOR_RPC_TIMEOUT": "10"
      }
    }
  }
}
```

### Local checkout (development)

Replace the path with your clone:

```json
{
  "mcpServers": {
    "qinspector": {
      "command": "uvx",
      "args": ["--from", "/path/to/QInspector/mcp", "qinspector-mcp"],
      "env": {
        "QINSPECTOR_HOST": "127.0.0.1",
        "QINSPECTOR_PORT": "9876",
        "QINSPECTOR_RPC_TIMEOUT": "10"
      }
    }
  }
}
```

On Windows a path like `S:/Code/Debiru/QInspector/mcp` is fine (forward slashes).

## Tools

| Tool | Purpose |
|------|---------|
| `list_sessions` | Connected apps (`app` key, `appId`, `pid`, `client`) with their sessions |
| `ping` | `ping` RPC on a session (`app` optional) |
| `ui_tree` | `ui.tree` RPC. Compact: default-valued fields omitted, `qt_*` chrome hidden (`filter="all"` to show). Agent defaults: maxDepth=8, maxNodes=500, includeInvisible=False |
| `ui_properties` | `ui.properties` RPC. Exact state of one node: all readable properties, or pass `properties=[...]` for specific names |
| `ui_set` | `ui.set` RPC. Write one property (scalar value; Qt converts strings to the property type). Requires `SetInputInjectionEnabled(true)` (else -32014). Result echoes the read-back value. On QML this replaces bindings |
| `ui_find` | `ui.find` RPC. Search the FULL tree by `className` / `objectName` / `text` substrings (ANDed, case-insensitive); returns matches only. Use when `ui_tree` truncates |
| `ui_screenshot` | `ui.screenshot` RPC at original size (no downscale). Returns MCP image content, or writes to `save_path` and returns metadata only. Optional `format=png|jpeg`, `quality` (jpeg only). Oversized → try jpeg/lower quality |
| `ui_click` / `ui_drag` / `ui_wheel` / `ui_type` | Synthetic input. Requires `QInspector::SetInputInjectionEnabled(true)` in the app (else code -32014). Target by `objectName` from `ui_tree` (recommended) or coordinates. Typical loop: `ui_tree` → `ui_click` → `ui_screenshot` |
| `app_logs` | Captured qDebug/qWarning/QML logs. Requires `SetLogCaptureEnabled(true)` (else -32015). Incremental: pass `since_seq=<lastSeq from previous call>`. `handlerActive:false` = another handler displaced capture |
| `wait_for` | MCP-side polling helper (no RPC of its own): polls `ui.properties` until a property `equals` / `contains` the expectation or `timeout_s` elapses. Use after an action instead of sleeping |
| `raw_call` | Only if `QINSPECTOR_MCP_ALLOW_RAW=1` |

## Agent workflow

1. Start MCP (IDE via `uvx`, or `qinspector-mcp` in a terminal).
2. Start the Qt app so it connects and sends `session.hello`.
3. `list_sessions` → pick `name`.
4. `ui_tree(name=...)` / `ui_screenshot(name=...)` / `ping(name=...)`.

## Environment

| Variable | Default | Meaning |
|----------|---------|---------|
| `QINSPECTOR_HOST` | `127.0.0.1` | Bind address |
| `QINSPECTOR_PORT` | `9876` | Bind port |
| `QINSPECTOR_RPC_TIMEOUT` | `10` | Seconds to wait for App response |
| `QINSPECTOR_MCP_ALLOW_RAW` | off | Enable `raw_call` tool |

## License

MIT — see the repository root `LICENSE` when present; `pyproject.toml` declares `license = "MIT"`.
