rename binary name to gammaray-mcp-bridge
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,6 +3,9 @@ build/
|
|||||||
bridge/build/
|
bridge/build/
|
||||||
install-prefix/
|
install-prefix/
|
||||||
|
|
||||||
|
# Compiled test binaries (not committed)
|
||||||
|
tests/testapp/widget_test_app
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
__pycache__/
|
__pycache__/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
|
|||||||
85
PLAN.md
85
PLAN.md
@@ -1,19 +1,19 @@
|
|||||||
# QML SceneGraph MCP Bridge — Plan
|
# GammaRay MCP Bridge — Plan
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
|
|
||||||
Build an MCP server that exposes QML SceneGraph introspection data (from GammaRay's probe) to LLMs for assisted debugging. The bridge is a **GammaRay client**: it connects to a probe injected into the target Qt/QML app, reads the same models the GammaRay GUI uses, and translates them into MCP JSON-RPC tool calls.
|
Build an MCP server that exposes GammaRay probe introspection data (QML SceneGraph, QML items, and Qt Widgets) to LLMs for assisted debugging. The bridge is a **GammaRay client**: it connects to a probe injected into the target Qt app, reads the same models the GammaRay GUI uses, and translates them into MCP JSON-RPC tool calls.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
Target Qt/QML App
|
Target Qt/QML App
|
||||||
│ GammaRay probe injected (preload/attach), loads quickinspector plugin
|
│ GammaRay probe injected (preload/attach), loads quickinspector + widgetinspector plugins
|
||||||
│
|
│
|
||||||
│ GammaRay binary protocol over TCP/local socket
|
│ GammaRay binary protocol over TCP/local socket
|
||||||
│ (common/protocol.h — QDataStream-based, NOT JSON-RPC)
|
│ (common/protocol.h — QDataStream-based, NOT JSON-RPC)
|
||||||
▼
|
▼
|
||||||
QML SceneGraph MCP Bridge (new project, GPL-2.0-or-later)
|
GammaRay MCP Bridge (new project, GPL-2.0-or-later)
|
||||||
│ Links gammaray_client + gammaray_common (installed from source)
|
│ Links gammaray_client + gammaray_common (installed from source)
|
||||||
│ Uses ClientConnectionManager to connect, ObjectBroker to get models
|
│ Uses ClientConnectionManager to connect, ObjectBroker to get models
|
||||||
│ Connection is lazy: bridge starts without a probe, MCP client calls
|
│ Connection is lazy: bridge starts without a probe, MCP client calls
|
||||||
@@ -99,7 +99,7 @@ Caveats of the FetchContent approach:
|
|||||||
|
|
||||||
```cmake
|
```cmake
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(QmlSceneGraphMcpBridge LANGUAGES CXX)
|
project(GammaRayMcpBridge LANGUAGES CXX)
|
||||||
set(CMAKE_CXX_STANDARD 20) # qtmcp requires C++20
|
set(CMAKE_CXX_STANDARD 20) # qtmcp requires C++20
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ set(QT_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
|||||||
set(QT_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
set(QT_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
FetchContent_MakeAvailable(qtmcp)
|
FetchContent_MakeAvailable(qtmcp)
|
||||||
|
|
||||||
add_executable(qml-sg-mcp-bridge
|
add_executable(gammaray-mcp-bridge
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/gammaray_session.cpp # wraps ClientConnectionManager + ObjectBroker
|
src/gammaray_session.cpp # wraps ClientConnectionManager + ObjectBroker
|
||||||
src/gammaray_session.h
|
src/gammaray_session.h
|
||||||
@@ -127,7 +127,7 @@ add_executable(qml-sg-mcp-bridge
|
|||||||
src/scenegraph_tools.h
|
src/scenegraph_tools.h
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(qml-sg-mcp-bridge PRIVATE
|
target_link_libraries(gammaray-mcp-bridge PRIVATE
|
||||||
gammaray_client # VERIFIED: no GammaRay:: namespace
|
gammaray_client # VERIFIED: no GammaRay:: namespace
|
||||||
gammaray_common
|
gammaray_common
|
||||||
Qt6::Core
|
Qt6::Core
|
||||||
@@ -139,7 +139,7 @@ target_link_libraries(qml-sg-mcp-bridge PRIVATE
|
|||||||
)
|
)
|
||||||
|
|
||||||
# qtmcp shared libs + stdio plugin live in build tree; make runtime find them:
|
# qtmcp shared libs + stdio plugin live in build tree; make runtime find them:
|
||||||
set_target_properties(qml-sg-mcp-bridge PROPERTIES
|
set_target_properties(gammaray-mcp-bridge PROPERTIES
|
||||||
BUILD_RPATH "$<TARGET_FILE_DIR:Qt6::McpServer>;$<TARGET_FILE_DIR:Qt6::McpCommon>"
|
BUILD_RPATH "$<TARGET_FILE_DIR:Qt6::McpServer>;$<TARGET_FILE_DIR:Qt6::McpCommon>"
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
@@ -152,7 +152,7 @@ cmake --build build
|
|||||||
# Run with probe libs + qtmcp runtime libs on the path:
|
# Run with probe libs + qtmcp runtime libs on the path:
|
||||||
LD_LIBRARY_PATH=$(pwd)/install-prefix/lib QT_QPA_PLATFORM=offscreen \
|
LD_LIBRARY_PATH=$(pwd)/install-prefix/lib QT_QPA_PLATFORM=offscreen \
|
||||||
QT_PLUGIN_PATH=$PWD/build/lib/x86_64-linux-gnu/qt6/plugins \
|
QT_PLUGIN_PATH=$PWD/build/lib/x86_64-linux-gnu/qt6/plugins \
|
||||||
./build/qml-sg-mcp-bridge --connect tcp://127.0.0.1:11732
|
./build/gammaray-mcp-bridge --connect tcp://127.0.0.1:11732
|
||||||
```
|
```
|
||||||
|
|
||||||
### ❗ MUST use QApplication, not QCoreApplication
|
### ❗ MUST use QApplication, not QCoreApplication
|
||||||
@@ -173,7 +173,7 @@ int main(int argc, char **argv) {
|
|||||||
// ... parse --connect <url> ...
|
// ... parse --connect <url> ...
|
||||||
|
|
||||||
QMcpServer server(QStringLiteral("stdio")); // backend name in ctor, no default
|
QMcpServer server(QStringLiteral("stdio")); // backend name in ctor, no default
|
||||||
server.setInstructions("QML SceneGraph introspection via GammaRay");
|
server.setInstructions("QML SceneGraph & Qt Widget introspection via GammaRay");
|
||||||
|
|
||||||
auto *tools = new SceneGraphTools(&server); // holds GammaRay client connection
|
auto *tools = new SceneGraphTools(&server); // holds GammaRay client connection
|
||||||
server.registerToolSet(tools, {
|
server.registerToolSet(tools, {
|
||||||
@@ -505,7 +505,7 @@ When in doubt, these files have the ground truth:
|
|||||||
- ✅ **Step 1 done**: GammaRay 3.4.0 built + installed to `install-prefix/`. Protocol version 38.
|
- ✅ **Step 1 done**: GammaRay 3.4.0 built + installed to `install-prefix/`. Protocol version 38.
|
||||||
- ✅ **Minimal client built & run**: `minimal-client/` links `gammaray_client` + `gammaray_common`, connects to a live probe, receives `ready()`, fetches `QuickSceneGraphModel` / `QuickWindowModel` via `ObjectBroker` — clean exit. Proves the client ABI + protocol path is viable for the bridge.
|
- ✅ **Minimal client built & run**: `minimal-client/` links `gammaray_client` + `gammaray_common`, connects to a live probe, receives `ready()`, fetches `QuickSceneGraphModel` / `QuickWindowModel` via `ObjectBroker` — clean exit. Proves the client ABI + protocol path is viable for the bridge.
|
||||||
- ✅ **qtmcp FetchContent viable**: `Qt6::McpServer`/`Qt6::McpCommon` build & link from a `FetchContent_MakeAvailable()` call. No separate clone/install.
|
- ✅ **qtmcp FetchContent viable**: `Qt6::McpServer`/`Qt6::McpCommon` build & link from a `FetchContent_MakeAvailable()` call. No separate clone/install.
|
||||||
- ✅ **Bridge scaffold built & working end-to-end** (`bridge/`): `qml-sg-mcp-bridge` links GammaRay client + qtmcp, runs as a stdio MCP server. Smoke test (`/tmp/opencode/smoke_test.py`) against a live probe confirms:
|
- ✅ **Bridge scaffold built & working end-to-end** (`bridge/`): `gammaray-mcp-bridge` links GammaRay client + qtmcp, runs as a stdio MCP server. Smoke test against a live probe confirms:
|
||||||
- `initialize` → serverInfo + capabilities + protocolVersion `2024-11-05` ✅
|
- `initialize` → serverInfo + capabilities + protocolVersion `2024-11-05` ✅
|
||||||
- `tools/list` → 15 tools registered: `connectProbe`, `connectProbeDefault`, `disconnectProbe`, `getMaterialProperties`, `getMaterialShaders`, `getNodeAdjacency`, `getNodeVertices`, `getShaderSource`, `listQuickWindows`, `listScenegraphNodes`, `probeStatus`, `selectQuickWindow`, `selectScenegraphNode`, `setRenderMode`, `setSlowMode` ✅
|
- `tools/list` → 15 tools registered: `connectProbe`, `connectProbeDefault`, `disconnectProbe`, `getMaterialProperties`, `getMaterialShaders`, `getNodeAdjacency`, `getNodeVertices`, `getShaderSource`, `listQuickWindows`, `listScenegraphNodes`, `probeStatus`, `selectQuickWindow`, `selectScenegraphNode`, `setRenderMode`, `setSlowMode` ✅
|
||||||
- `probeStatus` (before connect) → `{"ready":false,"state":"disconnected","url":""}` ✅
|
- `probeStatus` (before connect) → `{"ready":false,"state":"disconnected","url":""}` ✅
|
||||||
@@ -550,39 +550,38 @@ cmake --build build
|
|||||||
```
|
```
|
||||||
`run.sh` sets `LD_LIBRARY_PATH` (GammaRay + qtmcp libs) and `QT_PLUGIN_PATH` (qtmcp `mcpserverbackend/libqmcpserverstdio.so`) and `QT_QPA_PLATFORM=offscreen`.
|
`run.sh` sets `LD_LIBRARY_PATH` (GammaRay + qtmcp libs) and `QT_PLUGIN_PATH` (qtmcp `mcpserverbackend/libqmcpserverstdio.so`) and `QT_QPA_PLATFORM=offscreen`.
|
||||||
|
|
||||||
## Qt Widget support (planned)
|
## Qt Widget support (implemented)
|
||||||
|
|
||||||
GammaRay 的 widget inspector 与 quick inspector 共享同一套底层架构(`PropertyController`、`SelectionModelClient`、`RemoteModel`),大部分 QML 工具的模式可直接复用。
|
GammaRay 的 widget inspector 与 quick inspector 共享同一套底层架构(`PropertyController`、`SelectionModelClient`、`RemoteModel`),大部分 QML 工具的模式已直接复用。
|
||||||
|
|
||||||
### 可提供的 widget 工具
|
### 已实现的 widget 工具
|
||||||
|
|
||||||
**导航(与 QML 类似,模型名不同)**
|
**导航(与 QML 类似,模型名不同)**
|
||||||
- `listWidgets()` — 遍历 `com.kdab.GammaRay.WidgetTree` 模型,返回 widget 层次树
|
- ✅ `listWidgets()` — 遍历 `com.kdab.GammaRay.WidgetTree` 模型,返回 widget 层次树
|
||||||
- `selectWidget(address)` — 通过 `SelectionModelClient` 选取 widget,触发 `PropertyController` 填充子模型(属性、attribute 等)
|
- ✅ `selectWidget(address)` — 通过 `SelectionModelClient` 选取 widget,触发 `PropertyController` 填充子模型(属性、attribute 等)
|
||||||
- `listTopLevelWindows()`(已有 `listQuickWindows`,可在 widget 应用中继续使用)
|
|
||||||
|
|
||||||
**属性/attribute(与 QML 共享读取实现)**
|
**属性/attribute(与 QML 共享读取实现)**
|
||||||
- `getWidgetProperties(address)` — 读取同一套 `AggregatedPropertyModel`(通过 `PropertyController`),模型对象名 `com.kdab.GammaRay.Widget.<address>.properties`,与 `getItemProperties` 共享实现代码
|
- ✅ `getWidgetProperties(address)` — 读取同一套 `AggregatedPropertyModel`(通过 `PropertyController`),与 `getItemProperties` 共享实现代码
|
||||||
- `getWidgetAttributes(address)` — 读取 `widgetAttributeModel`(`AttributeModel<QWidget, Qt::WidgetAttribute>`),列出 Qt::WidgetAttribute 值
|
- ✅ `getWidgetAttributes(address)` — 读取 `widgetAttributeModel`(`AttributeModel<QWidget, Qt::WidgetAttribute>`),列出 Qt::WidgetAttribute 值
|
||||||
|
|
||||||
**视觉/分析(新功能,需 RemoteViewInterface)**
|
**视觉/分析(新功能,需 RemoteViewInterface)**
|
||||||
- `grabWidget(address)` — 通过 `WidgetRemoteView`(`com.kdab.GammaRay.WidgetRemoteView`)抓取 widget 截图,异步图像传输
|
- ❌ `grabWidget(address)` — 通过 `WidgetRemoteView`(`com.kdab.GammaRay.WidgetRemoteView`)抓取 widget 截图,异步图像传输
|
||||||
- `analyzePainting(address)` — 触发 `PaintAnalyzer` 捕获绘制操作
|
- ❌ `analyzePainting(address)` — 触发 `PaintAnalyzer` 捕获绘制操作
|
||||||
- `exportWidgetAsSvg(address)` — 通过 `WidgetInspectorInterface` 导出 SVG
|
- ❌ `exportWidgetAsSvg(address)` — 通过 `WidgetInspectorInterface` 导出 SVG
|
||||||
- `exportWidgetAsUiFile(address)` — 导出 Qt Designer .ui 文件
|
- ❌ `exportWidgetAsUiFile(address)` — 导出 Qt Designer .ui 文件
|
||||||
|
|
||||||
**控制**
|
**控制**
|
||||||
- `setInputRedirection(enabled)` — 允许将鼠标/键盘事件转发到目标 widget
|
- ❌ `setInputRedirection(enabled)` — 允许将鼠标/键盘事件转发到目标 widget
|
||||||
|
|
||||||
### 实现策略
|
### 实现策略
|
||||||
|
|
||||||
| 难度 | 工作项 | 说明 |
|
| 难度 | 工作项 | 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 低 | `listWidgets()` / `selectWidget()` | 直接复用 `listQuickItems()` / `selectQuickItem()` 的代码模式,模型名改为 `com.kdab.GammaRay.WidgetTree` |
|
| ✅ 已完成 | `listWidgets()` / `selectWidget()` | 直接复用 `listQuickItems()` / `selectQuickItem()` 的代码模式,模型名改为 `com.kdab.GammaRay.WidgetTree` |
|
||||||
| 低 | `getWidgetProperties()` | 与 `getItemProperties()` 完全一样,都读 `AggregatedPropertyModel`,可提取共享函数 |
|
| ✅ 已完成 | `getWidgetProperties()` | 与 `getItemProperties()` 完全一样,都读 `AggregatedPropertyModel`,已提取共享函数 `readAggregatedPropertyModel()` |
|
||||||
| 中 | `getWidgetAttributes()` | 需要读取 `widgetAttributeModel`(`PropertyController` 子模型),模式与 `getMaterialProperties()` 类似 |
|
| ✅ 已完成 | `getWidgetAttributes()` | 读取 `widgetAttributeModel`(`PropertyController` 子模型),模式与 `getMaterialProperties()` 类似 |
|
||||||
| 中 | `WidgetInspectorInterface` 代理 | IID `com.kdab.GammaRay.WidgetInspector`,用于 `export*`/`setInputRedirection` 等方法,类似 `QuickInspectorProxy` |
|
| ✅ 已完成 | `WidgetInspectorInterface` 代理 | IID `com.kdab.GammaRay.WidgetInspector`,用于 `export*`/`setInputRedirection` 等方法,类似 `QuickInspectorProxy` |
|
||||||
| 高 | `grabWidget()` / `analyzePainting()` | 需要 `RemoteViewInterface` 的 `TransferImage` 异步路径,模式与 `MaterialExtensionInterface` 的 `gotShader` 信号类似 |
|
| ❌ 未实现 | `grabWidget()` / `analyzePainting()` | 需要 `RemoteViewInterface` 的 `TransferImage` 异步路径,模式与 `MaterialExtensionInterface` 的 `gotShader` 信号类似 |
|
||||||
|
|
||||||
### 与 QML 工具的关键区别
|
### 与 QML 工具的关键区别
|
||||||
|
|
||||||
@@ -594,18 +593,20 @@ GammaRay 的 widget inspector 与 quick inspector 共享同一套底层架构(
|
|||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
### QML SceneGraph 工具(当前阶段)
|
### 已完成的阶段
|
||||||
|
|
||||||
|
- ✅ **QML SceneGraph 工具**:listScenegraphNodes、getNodeVertices、getNodeAdjacency、getMaterialShaders、getShaderSource、getMaterialProperties、setRenderMode、setSlowMode
|
||||||
|
- ✅ **QML Item 属性工具**:listQuickItems、selectQuickItem、getItemProperties
|
||||||
|
- ✅ **Qt Widget 工具**:listWidgets、selectWidget、getWidgetProperties、getWidgetAttributes
|
||||||
|
- ✅ **跨平台保护**:QuickInspector 和 WidgetInspector 的代理注册均已添加条件守卫,避免在未加载对应插件的 target 进程中崩溃
|
||||||
|
- ✅ **测试套件**:27 个 QML 测试 + 7 个 widget 测试,共 34 个集成测试
|
||||||
|
|
||||||
|
### 剩余工作
|
||||||
|
|
||||||
1. **Test `getShaderSource` async path end-to-end** — needs a QML app with real shaders (current test app uses software renderer where GeometryNodes have `nullptr` geometry).
|
1. **Test `getShaderSource` async path end-to-end** — needs a QML app with real shaders (current test app uses software renderer where GeometryNodes have `nullptr` geometry).
|
||||||
2. **Test geometry/material tools against a QML app with real QSGGeometry data** — current test app `relayout.qml` uses software renderer so `hasGeometry: false` and empty sub-models are correct behavior.
|
2. **Test geometry/material tools against a QML app with real QSGGeometry data** — current test app uses software renderer so `hasGeometry: false` and empty sub-models are correct behavior.
|
||||||
3. **Implement `grabTexture(objectId)`** — `TextureExtension` async image grab via `RemoteViewInterface` `TransferImage`. Follows the `MaterialExtensionInterface` proxy pattern.
|
3. **Implement `grabWidget(objectId)`** — `RemoteViewInterface` async image grab via `TransferImage`. Follows the `MaterialExtensionInterface` proxy pattern.
|
||||||
4. **Fix `listScenegraphNodes` first-call fill stability** — single-call instead of caller needing a second call.
|
4. **Implement `grabTexture(objectId)`** — `TextureExtension` async image grab via `RemoteViewInterface` `TransferImage`.
|
||||||
5. **Build `--connect` startup flag integration** with systemd service for auto-attach during development.
|
5. **Implement `analyzePainting()` / `exportWidgetAsSvg()` / `exportWidgetAsUiFile()`** — via `WidgetInspectorInterface` proxy.
|
||||||
|
6. **Fix `listScenegraphNodes` first-call fill stability** — single-call instead of caller needing a second call.
|
||||||
### Qt Widget 支持(未来阶段)
|
7. **Build `--connect` startup flag integration** with systemd service for auto-attach during development.
|
||||||
|
|
||||||
1. 创建 widget_tools.{h,cpp},注册 `listWidgets` / `selectWidget` / `getWidgetProperties` / `getWidgetAttributes` MCP 工具
|
|
||||||
2. 从 `scenegraph_tools.cpp` 提取共享的 `AggregatedPropertyModel` 读取函数,供 QML 和 widget 工具共用
|
|
||||||
3. 创建 `WidgetInspectorInterface` 代理(类似 `QuickInspectorProxy`),处理 `export*`/`setInputRedirection`
|
|
||||||
4. 实现 `RemoteViewInterface` 异步图像传输路径(grabWidget/analyzePainting)
|
|
||||||
5. 添加 widget 测试应用(简单 QWidget 窗口 + 按钮/布局),编写 widget 工具的集成测试
|
|
||||||
|
|||||||
32
README.md
32
README.md
@@ -1,6 +1,6 @@
|
|||||||
# QML SceneGraph MCP Bridge
|
# GammaRay MCP Bridge
|
||||||
|
|
||||||
A [MCP](https://modelcontextprotocol.io/) server that bridges [GammaRay](https://github.com/KDAB/GammaRay) probe introspection data into MCP tools, enabling LLMs to inspect and debug Qt Quick / QML scene graphs, items, geometry, and materials.
|
A [MCP](https://modelcontextprotocol.io/) server that bridges [GammaRay](https://github.com/KDAB/GammaRay) probe introspection data into MCP tools, enabling LLMs to inspect and debug Qt Quick / QML scene graphs, items, geometry, materials, and Qt Widgets.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ Target Qt/QML App ──TCP──► GammaRay MCP Bridge ──stdio──►
|
|||||||
injected) qtmcp MCP server) Claude Desktop, etc.)
|
injected) qtmcp MCP server) Claude Desktop, etc.)
|
||||||
```
|
```
|
||||||
|
|
||||||
The bridge is a GammaRay **client**: it connects to a probe injected into a target Qt/QML app, reads the same models the GammaRay GUI uses, and translates them into MCP JSON-RPC tool calls over stdio.
|
The bridge is a GammaRay **client**: it connects to a probe injected into a target Qt app, reads the same models the GammaRay GUI uses, and translates them into MCP JSON-RPC tool calls over stdio. Supports both QML Quick apps (SceneGraph, QML items) and Qt Widget apps (widget hierarchy, properties, attributes).
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@@ -46,6 +46,10 @@ cmake --build build
|
|||||||
install-prefix/bin/gammaray --inject-only --listen tcp://127.0.0.1:11732 \
|
install-prefix/bin/gammaray --inject-only --listen tcp://127.0.0.1:11732 \
|
||||||
--injector preload /usr/lib/qt6/bin/qml /path/to/app.qml -platform offscreen
|
--injector preload /usr/lib/qt6/bin/qml /path/to/app.qml -platform offscreen
|
||||||
|
|
||||||
|
# Or inject into a widget app
|
||||||
|
install-prefix/bin/gammaray --inject-only --listen tcp://127.0.0.1:11732 \
|
||||||
|
--injector preload /path/to/widget-app
|
||||||
|
|
||||||
# Start the bridge (stdio MCP server)
|
# Start the bridge (stdio MCP server)
|
||||||
bridge/run.sh
|
bridge/run.sh
|
||||||
```
|
```
|
||||||
@@ -62,7 +66,7 @@ The bridge starts in lazy-connect mode. Call `connectProbe("127.0.0.1", 11732)`
|
|||||||
| `disconnectProbe()` | Drop connection and forget URL |
|
| `disconnectProbe()` | Drop connection and forget URL |
|
||||||
| `probeStatus()` | Report connection state |
|
| `probeStatus()` | Report connection state |
|
||||||
|
|
||||||
### Navigation
|
### QML Navigation
|
||||||
| Tool | Description |
|
| Tool | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `listQuickWindows()` | List QQuickWindows in the target app |
|
| `listQuickWindows()` | List QQuickWindows in the target app |
|
||||||
@@ -92,15 +96,25 @@ The bridge starts in lazy-connect mode. Call `connectProbe("127.0.0.1", 11732)`
|
|||||||
| `setRenderMode(mode)` | Set render mode (NormalRendering, VisualizeOverdraw, etc.) |
|
| `setRenderMode(mode)` | Set render mode (NormalRendering, VisualizeOverdraw, etc.) |
|
||||||
| `setSlowMode(enabled)` | Toggle continuous rendering |
|
| `setSlowMode(enabled)` | Toggle continuous rendering |
|
||||||
|
|
||||||
|
### Widget inspection
|
||||||
|
| Tool | Description |
|
||||||
|
|---|---|
|
||||||
|
| `listWidgets()` | Recursive QWidget hierarchy (types, names, visibility) |
|
||||||
|
| `selectWidget(address)` | Select a widget, populating its property and attribute models |
|
||||||
|
| `getWidgetProperties(address)` | Get all Q_PROPERTY values (geometry, font, palette, window flags, etc.) |
|
||||||
|
| `getWidgetAttributes(address)` | Get Qt::WidgetAttribute flags (acceptDrops, enabled, etc.) |
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Unit tests only (no probe needed):
|
# QML test suite (requires a QML app probe):
|
||||||
./tests/run_tests.sh --no-probe
|
./tests/run_tests.sh
|
||||||
|
|
||||||
# Full integration test suite (probe required):
|
# Widget test suite (requires a widget app probe):
|
||||||
./tests/run_tests.sh --no-probe # skips probe-dependent tests
|
./tests/run_widget_tests.sh
|
||||||
./tests/run_tests.sh -v # verbose
|
|
||||||
|
# Unit tests only:
|
||||||
|
./tests/run_tests.sh --no-probe
|
||||||
```
|
```
|
||||||
|
|
||||||
See `tests/README.md` for details.
|
See `tests/README.md` for details.
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# QML SceneGraph MCP Bridge
|
# GammaRay MCP Bridge
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#
|
#
|
||||||
# Links GammaRay client libs (GPL-2.0-or-later) => this bridge must be
|
# Links GammaRay client libs (GPL-2.0-or-later) => this bridge must be
|
||||||
# GPL-compatible. qtmcp is consumed under its GPL-2.0-only option (compatible).
|
# GPL-compatible. qtmcp is consumed under its GPL-2.0-only option (compatible).
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(QmlSceneGraphMcpBridge LANGUAGES CXX)
|
project(GammaRayMcpBridge LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20) # qtmcp requires C++20
|
set(CMAKE_CXX_STANDARD 20) # qtmcp requires C++20
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
@@ -37,7 +37,7 @@ find_package(GammaRay REQUIRED)
|
|||||||
# --- Qt6 components for the bridge target (Qt6 already found globally by qtmcp) ---
|
# --- Qt6 components for the bridge target (Qt6 already found globally by qtmcp) ---
|
||||||
find_package(Qt6 COMPONENTS Core Gui Widgets Network REQUIRED)
|
find_package(Qt6 COMPONENTS Core Gui Widgets Network REQUIRED)
|
||||||
|
|
||||||
add_executable(qml-sg-mcp-bridge
|
add_executable(gammaray-mcp-bridge
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/gammaray_session.cpp
|
src/gammaray_session.cpp
|
||||||
src/gammaray_session.h
|
src/gammaray_session.h
|
||||||
@@ -56,7 +56,7 @@ add_executable(qml-sg-mcp-bridge
|
|||||||
src/widget_inspector_proxy.h
|
src/widget_inspector_proxy.h
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(qml-sg-mcp-bridge PRIVATE
|
target_link_libraries(gammaray-mcp-bridge PRIVATE
|
||||||
gammaray_client # VERIFIED: no GammaRay:: namespace
|
gammaray_client # VERIFIED: no GammaRay:: namespace
|
||||||
gammaray_common
|
gammaray_common
|
||||||
Qt6::Core
|
Qt6::Core
|
||||||
@@ -68,7 +68,7 @@ target_link_libraries(qml-sg-mcp-bridge PRIVATE
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Add GammaRay source common/ for message.h (not in installed headers)
|
# Add GammaRay source common/ for message.h (not in installed headers)
|
||||||
target_include_directories(qml-sg-mcp-bridge PRIVATE
|
target_include_directories(gammaray-mcp-bridge PRIVATE
|
||||||
/home/blumia/Sources/GammaRay/common
|
/home/blumia/Sources/GammaRay/common
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ target_include_directories(qml-sg-mcp-bridge PRIVATE
|
|||||||
# run time (see run.sh) so Qt loads mcpserverbackend/libqmcpserverstdio.so.
|
# run time (see run.sh) so Qt loads mcpserverbackend/libqmcpserverstdio.so.
|
||||||
# Also add GammaRay's install lib dir for libgammaray_*.so.
|
# Also add GammaRay's install lib dir for libgammaray_*.so.
|
||||||
set(_GR_LIBDIR "$<TARGET_PROPERTY:gammaray_client,IMPORTED_LOCATION>")
|
set(_GR_LIBDIR "$<TARGET_PROPERTY:gammaray_client,IMPORTED_LOCATION>")
|
||||||
set_target_properties(qml-sg-mcp-bridge PROPERTIES
|
set_target_properties(gammaray-mcp-bridge PROPERTIES
|
||||||
BUILD_RPATH "$<TARGET_FILE_DIR:Qt6::McpServer>;${CMAKE_SOURCE_DIR}/../install-prefix/lib"
|
BUILD_RPATH "$<TARGET_FILE_DIR:Qt6::McpServer>;${CMAKE_SOURCE_DIR}/../install-prefix/lib"
|
||||||
INSTALL_RPATH "$ORIGIN/../lib"
|
INSTALL_RPATH "$ORIGIN/../lib"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Run the QML SceneGraph MCP bridge.
|
# Run the GammaRay MCP bridge.
|
||||||
# Sets up LD_LIBRARY_PATH (GammaRay + qtmcp libs) and QT_PLUGIN_PATH (qtmcp stdio
|
# Sets up LD_LIBRARY_PATH (GammaRay + qtmcp libs) and QT_PLUGIN_PATH (qtmcp stdio
|
||||||
# backend plugin) so the bridge can find everything at runtime.
|
# backend plugin) so the bridge can find everything at runtime.
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
@@ -10,4 +10,4 @@ BLD="$HERE/build"
|
|||||||
export LD_LIBRARY_PATH="$ROOT/install-prefix/lib:$BLD/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH:-}"
|
export LD_LIBRARY_PATH="$ROOT/install-prefix/lib:$BLD/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH:-}"
|
||||||
export QT_PLUGIN_PATH="$BLD/lib/x86_64-linux-gnu/qt6/plugins:${QT_PLUGIN_PATH:-}"
|
export QT_PLUGIN_PATH="$BLD/lib/x86_64-linux-gnu/qt6/plugins:${QT_PLUGIN_PATH:-}"
|
||||||
export QT_QPA_PLATFORM=offscreen
|
export QT_QPA_PLATFORM=offscreen
|
||||||
exec "$BLD/qml-sg-mcp-bridge" "$@"
|
exec "$BLD/gammaray-mcp-bridge" "$@"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
qml-sg-mcp-bridge — MCP server bridging QML SceneGraph introspection.
|
gammaray-mcp-bridge — MCP server bridging GammaRay introspection.
|
||||||
|
|
||||||
SPDX-FileCopyrightText: 2026 The GammaRay MCP Bridge Authors
|
SPDX-FileCopyrightText: 2026 The GammaRay MCP Bridge Authors
|
||||||
SPDX-License-Identifier: GPL-2.0-or-later
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
@@ -33,12 +33,12 @@ int main(int argc, char **argv)
|
|||||||
qSetMessagePattern(QStringLiteral("[%{type}] %{message}"));
|
qSetMessagePattern(QStringLiteral("[%{type}] %{message}"));
|
||||||
|
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
app.setApplicationName(QStringLiteral("qml-sg-mcp-bridge"));
|
app.setApplicationName(QStringLiteral("gammaray-mcp-bridge"));
|
||||||
app.setApplicationVersion(QStringLiteral("0.1.0"));
|
app.setApplicationVersion(QStringLiteral("0.1.0"));
|
||||||
app.setOrganizationName(QStringLiteral("KDAB"));
|
app.setOrganizationName(QStringLiteral("KDAB"));
|
||||||
|
|
||||||
QCommandLineParser parser;
|
QCommandLineParser parser;
|
||||||
parser.setApplicationDescription(QStringLiteral("QML SceneGraph MCP Bridge"));
|
parser.setApplicationDescription(QStringLiteral("GammaRay MCP Bridge"));
|
||||||
parser.addHelpOption();
|
parser.addHelpOption();
|
||||||
parser.addVersionOption();
|
parser.addVersionOption();
|
||||||
|
|
||||||
|
|||||||
@@ -20,13 +20,18 @@ end-to-end against a real GammaRay probe.
|
|||||||
| `test_navigation.py` | `listQuickWindows`, `selectQuickWindow`, `listQuickItems`, `listScenegraphNodes` | Yes |
|
| `test_navigation.py` | `listQuickWindows`, `selectQuickWindow`, `listQuickItems`, `listScenegraphNodes` | Yes |
|
||||||
| `test_items.py` | `selectQuickItem`, `getItemProperties`, position, visual properties, groups | Yes |
|
| `test_items.py` | `selectQuickItem`, `getItemProperties`, position, visual properties, groups | Yes |
|
||||||
| `test_scenegraph.py` | `selectScenegraphNode`, `getNodeVertices`, `getNodeAdjacency`, `getMaterialShaders`, `getMaterialProperties`, `setRenderMode`, `setSlowMode` | Yes |
|
| `test_scenegraph.py` | `selectScenegraphNode`, `getNodeVertices`, `getNodeAdjacency`, `getMaterialShaders`, `getMaterialProperties`, `setRenderMode`, `setSlowMode` | Yes |
|
||||||
|
| `test_widgets.py` | `listWidgets`, `selectWidget`, `getWidgetProperties`, `getWidgetAttributes` | Yes (--widget-app) |
|
||||||
|
|
||||||
## Test QML application
|
## Test applications
|
||||||
|
|
||||||
`testapp/main.qml` is a simple Qt Quick Controls application that exercises all
|
`testapp/main.qml` is a simple Qt Quick Controls application that exercises all
|
||||||
the tools: buttons, switches, sliders, a list view with delegate items, and
|
the QML tools: buttons, switches, sliders, a list view with delegate items, and
|
||||||
visibility/opacity controls.
|
visibility/opacity controls.
|
||||||
|
|
||||||
|
`testapp/widget_test_app.cpp` is a simple QWidget application (QLabel, QLineEdit,
|
||||||
|
QCheckBox, QGroupBox, QPushButton) for testing widget inspection tools. Use
|
||||||
|
`run_widget_tests.sh` to run tests against this app.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
**`run_tests.sh` is the single configuration point.** All paths default to
|
**`run_tests.sh` is the single configuration point.** All paths default to
|
||||||
@@ -40,7 +45,7 @@ system package), override these by editing the variables at the top of
|
|||||||
| `GAMMARAY_LIB_DIR` | `install-prefix/lib` | Runtime library directory for GammaRay |
|
| `GAMMARAY_LIB_DIR` | `install-prefix/lib` | Runtime library directory for GammaRay |
|
||||||
| `QML_RUNNER` | `/usr/lib/qt6/bin/qml` | QML runtime executable |
|
| `QML_RUNNER` | `/usr/lib/qt6/bin/qml` | QML runtime executable |
|
||||||
| `TEST_QML` | `testapp/main.qml` | Test QML application |
|
| `TEST_QML` | `testapp/main.qml` | Test QML application |
|
||||||
| `BRIDGE_EXE` | `bridge/build/qml-sg-mcp-bridge` | Bridge executable |
|
| `BRIDGE_EXE` | `bridge/build/gammaray-mcp-bridge` | Bridge executable |
|
||||||
| `BRIDGE_RUN_SCRIPT` | `bridge/run.sh` | Bridge wrapper script |
|
| `BRIDGE_RUN_SCRIPT` | `bridge/run.sh` | Bridge wrapper script |
|
||||||
|
|
||||||
You can also override any of these via environment variables when calling
|
You can also override any of these via environment variables when calling
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ WIDGET_TEST_APP_SRC = Path(os.environ.get(
|
|||||||
))
|
))
|
||||||
BRIDGE_EXE = Path(os.environ.get(
|
BRIDGE_EXE = Path(os.environ.get(
|
||||||
"BRIDGE_EXE",
|
"BRIDGE_EXE",
|
||||||
str(PROJECT_ROOT / "bridge" / "build" / "qml-sg-mcp-bridge"),
|
str(PROJECT_ROOT / "bridge" / "build" / "gammaray-mcp-bridge"),
|
||||||
))
|
))
|
||||||
BRIDGE_RUN_SCRIPT = Path(os.environ.get(
|
BRIDGE_RUN_SCRIPT = Path(os.environ.get(
|
||||||
"BRIDGE_RUN_SCRIPT",
|
"BRIDGE_RUN_SCRIPT",
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|||||||
: "${GAMMARAY_LIB_DIR:=$PROJECT_ROOT/install-prefix/lib}"
|
: "${GAMMARAY_LIB_DIR:=$PROJECT_ROOT/install-prefix/lib}"
|
||||||
: "${QML_RUNNER:=/usr/lib/qt6/bin/qml}"
|
: "${QML_RUNNER:=/usr/lib/qt6/bin/qml}"
|
||||||
: "${TEST_QML:=$SCRIPT_DIR/testapp/main.qml}"
|
: "${TEST_QML:=$SCRIPT_DIR/testapp/main.qml}"
|
||||||
: "${BRIDGE_EXE:=$PROJECT_ROOT/bridge/build/qml-sg-mcp-bridge}"
|
: "${BRIDGE_EXE:=$PROJECT_ROOT/bridge/build/gammaray-mcp-bridge}"
|
||||||
: "${BRIDGE_RUN_SCRIPT:=$PROJECT_ROOT/bridge/run.sh}"
|
: "${BRIDGE_RUN_SCRIPT:=$PROJECT_ROOT/bridge/run.sh}"
|
||||||
|
|
||||||
# Export so conftest.py can read them
|
# Export so conftest.py can read them
|
||||||
|
|||||||
@@ -20,9 +20,32 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|||||||
: "${GAMMARAY_LIB_DIR:=$PROJECT_ROOT/install-prefix/lib}"
|
: "${GAMMARAY_LIB_DIR:=$PROJECT_ROOT/install-prefix/lib}"
|
||||||
: "${WIDGET_TEST_APP_BIN:=$SCRIPT_DIR/testapp/widget_test_app}"
|
: "${WIDGET_TEST_APP_BIN:=$SCRIPT_DIR/testapp/widget_test_app}"
|
||||||
: "${WIDGET_TEST_APP_SRC:=$SCRIPT_DIR/testapp/widget_test_app.cpp}"
|
: "${WIDGET_TEST_APP_SRC:=$SCRIPT_DIR/testapp/widget_test_app.cpp}"
|
||||||
: "${BRIDGE_EXE:=$PROJECT_ROOT/bridge/build/qml-sg-mcp-bridge}"
|
: "${BRIDGE_EXE:=$PROJECT_ROOT/bridge/build/gammaray-mcp-bridge}"
|
||||||
: "${BRIDGE_RUN_SCRIPT:=$PROJECT_ROOT/bridge/run.sh}"
|
: "${BRIDGE_RUN_SCRIPT:=$PROJECT_ROOT/bridge/run.sh}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Ensure the widget test app is compiled
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
_compile_widget_app() {
|
||||||
|
echo "Compiling widget test app..."
|
||||||
|
local cflags
|
||||||
|
cflags=$(pkg-config --cflags Qt6Core Qt6Gui Qt6Widgets 2>/dev/null) || {
|
||||||
|
echo "Error: pkg-config for Qt6 not found. Install qt6-base-dev or set WIDGET_TEST_APP_BIN to a pre-built binary."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
local libs
|
||||||
|
libs=$(pkg-config --libs Qt6Core Qt6Gui Qt6Widgets 2>/dev/null) || {
|
||||||
|
echo "Error: pkg-config for Qt6 libs not found."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
g++ -std=c++17 -fPIC "$WIDGET_TEST_APP_SRC" -o "$WIDGET_TEST_APP_BIN" \
|
||||||
|
$cflags $libs
|
||||||
|
echo "Widget test app compiled: $WIDGET_TEST_APP_BIN"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ ! -f "$WIDGET_TEST_APP_BIN" ]; then
|
||||||
|
_compile_widget_app
|
||||||
|
fi
|
||||||
export GAMMARAY_BIN
|
export GAMMARAY_BIN
|
||||||
export GAMMARAY_LIB_DIR
|
export GAMMARAY_LIB_DIR
|
||||||
export WIDGET_TEST_APP_BIN
|
export WIDGET_TEST_APP_BIN
|
||||||
|
|||||||
Reference in New Issue
Block a user