2026-07-06 20:32:47 +08:00
/*
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 <common/endpoint.h>
# include <QApplication>
# include <QCommandLineOption>
# include <QCommandLineParser>
# include <QLoggingCategory>
# include <QtMcpServer/QMcpServer>
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 , {
2026-07-07 16:48:44 +08:00
// Connection management
2026-07-06 20:32:47 +08:00
{ 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 " ) } ,
2026-07-07 16:48:44 +08:00
// Window-level navigation
2026-07-06 20:32:47 +08:00
{ 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 " ) } ,
2026-07-07 17:30:04 +08:00
// QML item tree
{ QStringLiteral ( " listQuickItems " ) , QStringLiteral ( " List the QQuickItem tree (QML item types, names, hierarchy) " ) } ,
2026-07-07 16:48:44 +08:00
// SG node selection
2026-07-07 20:22:12 +08:00
{ 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) " ) } ,
2026-07-07 16:48:44 +08:00
// Geometry
2026-07-07 20:22:12 +08:00
{ 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) " ) } ,
2026-07-07 16:48:44 +08:00
// Material/Shader
2026-07-07 20:22:12 +08:00
{ 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) " ) } ,
2026-07-07 16:48:44 +08:00
// 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 " ) } ,
2026-07-06 20:32:47 +08:00
} ) ;
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 ( ) ;
}