/* qml-sg-mcp-bridge — MCP server bridging QML SceneGraph introspection. SPDX-FileCopyrightText: 2026 The GammaRay MCP Bridge Authors SPDX-License-Identifier: GPL-2.0-or-later The bridge is a GammaRay CLIENT peer: it connects to a probe injected into a target Qt/QML app and exposes the resulting introspection data as MCP tools (JSON-RPC over stdio) via qtmcp. See PLAN.md for the architecture. IMPORTANT: must use QApplication (not QCoreApplication) — GammaRay's RemoteModel ctor calls QApplication::style()->sizeFromContents(). With QCoreApplication the bridge segfaults the moment ObjectBroker::model() is first called after ClientConnectionManager::ready(). */ #include "gammaray_session.h" #include "scenegraph_tools.h" #include #include #include #include #include #include int main(int argc, char **argv) { // stdio is the MCP transport — keep stdout clean. Qt log messages go to // stderr by default, but suppress debug noise to be safe. qSetMessagePattern(QStringLiteral("[%{type}] %{message}")); QApplication app(argc, argv); app.setApplicationName(QStringLiteral("qml-sg-mcp-bridge")); app.setApplicationVersion(QStringLiteral("0.1.0")); app.setOrganizationName(QStringLiteral("KDAB")); QCommandLineParser parser; parser.setApplicationDescription(QStringLiteral("QML SceneGraph MCP Bridge")); parser.addHelpOption(); parser.addVersionOption(); QCommandLineOption connectOption( QStringList() << QStringLiteral("connect"), QStringLiteral("GammaRay probe URL, e.g. tcp://127.0.0.1:11732"), QStringLiteral("url")); parser.addOption(connectOption); QCommandLineOption envFallback( QStringList() << QStringLiteral("env-url"), QStringLiteral("read probe URL from GAMMARAY_PROBE_URL if --connect is not given")); Q_UNUSED(envFallback); parser.process(app); QUrl probeUrl; if (parser.isSet(connectOption)) { probeUrl = QUrl::fromUserInput(parser.value(connectOption)); } else { const QByteArray env = qgetenv("GAMMARAY_PROBE_URL"); if (!env.isEmpty()) probeUrl = QUrl::fromUserInput(QString::fromUtf8(env)); } // One-time init of GammaRay stream operators + factory callbacks. GammaRaySession::initOnce(); GammaRaySession session(&app); QMcpServer server(QStringLiteral("stdio")); server.setInstructions(QStringLiteral( "QML SceneGraph introspection via GammaRay. Connects to a GammaRay probe " "injected into a Qt/QML application and exposes the scene graph, items, " "geometry, materials and shaders as tools.")); SceneGraphTools tools(&session, &server); server.registerToolSet(&tools, { // Connection management { QStringLiteral("connectProbe"), QStringLiteral("Connect to a GammaRay probe. host defaults to 127.0.0.1; pass port=0 to use the default 11732.") }, { QStringLiteral("connectProbe/host"), QStringLiteral("Probe host (default 127.0.0.1)") }, { QStringLiteral("connectProbe/port"), QStringLiteral("Probe port (default 11732; pass 0 to use default)") }, { QStringLiteral("connectProbeDefault"), QStringLiteral("Connect to a GammaRay probe at 127.0.0.1:11732 (convenience for connectProbe with no args)") }, { QStringLiteral("disconnectProbe"), QStringLiteral("Drop the current probe connection and forget the URL") }, { QStringLiteral("probeStatus"), QStringLiteral("Report current probe connection state, last URL and last error") }, // Window-level navigation { QStringLiteral("listQuickWindows"), QStringLiteral("List QQuickWindows in the target app") }, { QStringLiteral("selectQuickWindow"), QStringLiteral("Select a Quick window (by index into listQuickWindows) so the scene graph is introspected for it") }, { QStringLiteral("selectQuickWindow/index"), QStringLiteral("0-based index of the window in listQuickWindows") }, { QStringLiteral("listScenegraphNodes"), QStringLiteral("List the QSGNode tree of the selected Quick window") }, // QML item tree { QStringLiteral("listQuickItems"), QStringLiteral("List the QQuickItem tree (QML item types, names, hierarchy)") }, // SG node selection { QStringLiteral("selectScenegraphNode"), QStringLiteral("Select a SG node by address (from listScenegraphNodes) so geometry/material sub-models populate for it") }, { QStringLiteral("selectScenegraphNode/address"), QStringLiteral("Node address (e.g. 0x56396e6d7400, from listScenegraphNodes)") }, // Geometry { QStringLiteral("getNodeVertices"), QStringLiteral("Get vertex data for a GeometryNode (vertices, attributes, isCoordinate flags)") }, { QStringLiteral("getNodeVertices/address"), QStringLiteral("Node address (must be a GeometryNode)") }, { QStringLiteral("getNodeAdjacency"), QStringLiteral("Get adjacency/index data for a GeometryNode (drawing mode + index list)") }, { QStringLiteral("getNodeAdjacency/address"), QStringLiteral("Node address (must be a GeometryNode)") }, // Material/Shader { QStringLiteral("getMaterialShaders"), QStringLiteral("List shader stages (Vertex, Fragment, ...) for a node's material") }, { QStringLiteral("getMaterialShaders/address"), QStringLiteral("Node address (must be a GeometryNode with a material)") }, { QStringLiteral("getShaderSource"), QStringLiteral("Get shader source code for a shader stage (async: waits for probe response)") }, { QStringLiteral("getShaderSource/row"), QStringLiteral("Row index from getMaterialShaders (0 = first shader stage)") }, { QStringLiteral("getMaterialProperties"), QStringLiteral("Get material properties (name/value pairs) for a node's material") }, { QStringLiteral("getMaterialProperties/address"), QStringLiteral("Node address (must be a GeometryNode with a material)") }, // Rendering visualization { QStringLiteral("setRenderMode"), QStringLiteral("Set custom render mode for visualization") }, { QStringLiteral("setRenderMode/mode"), QStringLiteral("Render mode: NormalRendering, VisualizeClipping, VisualizeOverdraw, VisualizeBatches, VisualizeChanges, or VisualizeTraces") }, { QStringLiteral("setSlowMode"), QStringLiteral("Toggle slow animations mode (renders continuously instead of on-demand)") }, { QStringLiteral("setSlowMode/enabled"), QStringLiteral("true to enable slow mode, false to disable") }, }); QObject::connect(&server, &QMcpServer::finished, &app, &QCoreApplication::quit); if (probeUrl.isValid()) { // Best-effort initial connection. If the probe isn't up yet, GammaRay's // ClientConnectionManager retries every 1s for 60s; tools will also // transparently retry via ensureConnected() once a URL is known. session.connectToHost(probeUrl); } server.start(); return app.exec(); }