109 lines
4.9 KiB
C++
109 lines
4.9 KiB
C++
|
|
/*
|
||
|
|
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, {
|
||
|
|
// Connection management. The bridge no longer hard-requires --connect:
|
||
|
|
// a client calls connectProbe() to (re)establish a probe connection,
|
||
|
|
// and any introspection tool will also auto-reconnect if a URL was
|
||
|
|
// previously established. connectProbeDefault() is the zero-arg entry
|
||
|
|
// point for the common 127.0.0.1:11732 case.
|
||
|
|
{ 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") },
|
||
|
|
// Introspection
|
||
|
|
{ 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") },
|
||
|
|
});
|
||
|
|
|
||
|
|
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();
|
||
|
|
}
|