Merge pull request #9 from noita-player/feature/peb-teb-mcp

Add process.info MCP tool for PEB/TEB enumeration and peb/tebs API for providers to implement
This commit is contained in:
IChooseYou
2026-03-07 09:42:51 -07:00
committed by GitHub
5 changed files with 178 additions and 0 deletions

View File

@@ -447,6 +447,22 @@ QJsonObject McpBridge::handleToolsList(const QJsonValue& id) {
}}
});
// process.info
tools.append(QJsonObject{
{"name", "process.info"},
{"description", "Returns PEB address and enumerates all Thread Environment Blocks (TEBs) for the attached process. "
"TEBs are discovered via NtQuerySystemInformation and NtQueryInformationThread. "
"Each TEB entry includes: address, threadId. "
"Requires a live process provider with PEB support."},
{"inputSchema", QJsonObject{
{"type", "object"},
{"properties", QJsonObject{
{"tabIndex", QJsonObject{{"type", "integer"},
{"description", "MDI tab index (0-based). Omit for active tab."}}}
}}
}}
});
return okReply(id, QJsonObject{{"tools", tools}});
}
@@ -472,6 +488,7 @@ QJsonObject McpBridge::handleToolsCall(const QJsonValue& id, const QJsonObject&
else if (toolName == "ui.action") result = toolUiAction(args);
else if (toolName == "tree.search") result = toolTreeSearch(args);
else if (toolName == "node.history") result = toolNodeHistory(args);
else if (toolName == "process.info") result = toolProcessInfo(args);
else return errReply(id, -32601, "Unknown tool: " + toolName);
m_mainWindow->clearMcpStatus();
@@ -1327,6 +1344,39 @@ QJsonObject McpBridge::toolNodeHistory(const QJsonObject& args) {
QJsonDocument(result).toJson(QJsonDocument::Compact)));
}
// ════════════════════════════════════════════════════════════════════
// TOOL: process.info — PEB address + TEB enumeration
// ════════════════════════════════════════════════════════════════════
QJsonObject McpBridge::toolProcessInfo(const QJsonObject& args) {
auto* tab = resolveTab(args);
if (!tab) return makeTextResult("No active tab", true);
auto* prov = tab->doc->provider.get();
if (!prov) return makeTextResult("No data source attached", true);
if (!prov->isLive()) return makeTextResult("Not a live provider", true);
uint64_t pebAddr = prov->peb();
if (!pebAddr) return makeTextResult("PEB not available for this provider", true);
QJsonObject out;
out["peb"] = "0x" + QString::number(pebAddr, 16).toUpper();
auto tebList = prov->tebs();
QJsonArray tebArr;
for (const auto& t : tebList) {
tebArr.append(QJsonObject{
{"address", "0x" + QString::number(t.tebAddress, 16).toUpper()},
{"threadId", (qint64)t.threadId}
});
}
out["tebs"] = tebArr;
out["tebCount"] = tebArr.size();
return makeTextResult(QString::fromUtf8(QJsonDocument(out).toJson(QJsonDocument::Indented)));
}
// ════════════════════════════════════════════════════════════════════
// Notifications (call from MainWindow/Controller hooks)
// ════════════════════════════════════════════════════════════════════

View File

@@ -60,6 +60,7 @@ private:
QJsonObject toolUiAction(const QJsonObject& args);
QJsonObject toolTreeSearch(const QJsonObject& args);
QJsonObject toolNodeHistory(const QJsonObject& args);
QJsonObject toolProcessInfo(const QJsonObject& args);
// Helpers
QJsonObject makeTextResult(const QString& text, bool isError = false);

View File

@@ -73,6 +73,13 @@ public:
// Default: returns empty (scan engine falls back to [0, size())).
virtual QVector<MemoryRegion> enumerateRegions() const { return {}; }
// Process Environment Block address (x64 PEB VA in target process).
// Only meaningful for live process providers. Returns 0 if unavailable.
virtual uint64_t peb() const { return 0; }
struct ThreadInfo { uint64_t tebAddress; uint32_t threadId; };
virtual QVector<ThreadInfo> tebs() const { return {}; }
// --- Derived convenience (non-virtual, never override) ---
bool isValid() const { return size() > 0; }