mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-03-27 23:37:05 -05:00
feat: Add initial MCP commands to query, open select and read data
This commit is contained in:
@@ -6,6 +6,40 @@
|
||||
|
||||
namespace hex::mcp {
|
||||
|
||||
struct TextContent {
|
||||
std::string text;
|
||||
|
||||
operator nlohmann::json() const {
|
||||
nlohmann::json result;
|
||||
result["content"] = nlohmann::json::array({
|
||||
nlohmann::json::object({
|
||||
{ "type", "text" },
|
||||
{ "text", text }
|
||||
})
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct StructuredContent {
|
||||
std::string text;
|
||||
nlohmann::json data;
|
||||
|
||||
operator nlohmann::json() const {
|
||||
nlohmann::json result;
|
||||
result["content"] = nlohmann::json::array({
|
||||
nlohmann::json::object({
|
||||
{ "type", "text" },
|
||||
{ "text", text }
|
||||
})
|
||||
});
|
||||
result["structuredContent"] = data;
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
class Server {
|
||||
public:
|
||||
constexpr static auto McpInternalPort = 19743;
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace hex::mcp {
|
||||
if (!m_id.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
return createResponseMessage(result);
|
||||
return createResponseMessage(result.is_null() ? nlohmann::json::object() : result);
|
||||
}
|
||||
|
||||
std::optional<nlohmann::json> handleBatchedMessages(const nlohmann::json &request, auto callback) {
|
||||
@@ -154,7 +154,9 @@ namespace hex::mcp {
|
||||
if (auto primitiveIt = m_primitives.find(primitive); primitiveIt != m_primitives.end()) {
|
||||
auto name = params.value("name", "");
|
||||
if (auto functionIt = primitiveIt->second.find(name); functionIt != primitiveIt->second.end()) {
|
||||
return functionIt->second.function(params.value("arguments", nlohmann::json::object()));
|
||||
auto result = functionIt->second.function(params.value("arguments", nlohmann::json::object()));
|
||||
|
||||
return result.is_null() ? nlohmann::json::object() : result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ add_imhex_plugin(
|
||||
source/content/command_palette_commands.cpp
|
||||
source/content/command_line_interface.cpp
|
||||
source/content/communication_interface.cpp
|
||||
source/content/mcp_tools.cpp
|
||||
source/content/data_inspector.cpp
|
||||
source/content/differing_byte_searcher.cpp
|
||||
source/content/pl_builtin_functions.cpp
|
||||
|
||||
50
plugins/builtin/romfs/mcp/tools/list_open_data_sources.json
Normal file
50
plugins/builtin/romfs/mcp/tools/list_open_data_sources.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "list_open_data_sources",
|
||||
"title": "List Open Data Sources",
|
||||
"description": "Lists all currently open data sources with their name and a handle that can be used to reference them in other tools.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"outputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data_sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the data source"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Internal type of the data source"
|
||||
},
|
||||
"size": {
|
||||
"type": "number",
|
||||
"description": "Size of the data source in bytes"
|
||||
},
|
||||
"is_writable": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the data source is writable"
|
||||
},
|
||||
"handle": {
|
||||
"type": "number",
|
||||
"description": "Handle of the data source to reference it in other tools"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"type",
|
||||
"size",
|
||||
"is_writable",
|
||||
"handle"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["data_sources"]
|
||||
}
|
||||
}
|
||||
19
plugins/builtin/romfs/mcp/tools/open_file.json
Normal file
19
plugins/builtin/romfs/mcp/tools/open_file.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "open_file",
|
||||
"title": "Open File",
|
||||
"description": "Opens a file from the filesystem, given the file path in ImHex. This is the first step that always needs to be done first before any of the other tools can be used. A file stays open until it's closed by the user.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file_path": {
|
||||
"type": "string",
|
||||
"description": "Path of the file to open"
|
||||
}
|
||||
},
|
||||
"required": ["file_path"]
|
||||
},
|
||||
"outputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
37
plugins/builtin/romfs/mcp/tools/read_data.json
Normal file
37
plugins/builtin/romfs/mcp/tools/read_data.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "read_data",
|
||||
"title": "Read Binary Data",
|
||||
"description": "Reads data from the currently selected data source at the specified address and length. The data is returned as a base64-encoded string. The maximum size that can be read at once is 16MiB, if more data is requested, the call will only return the first 16MiB.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {
|
||||
"type": "number",
|
||||
"description": "Address to read from in the selected data source"
|
||||
},
|
||||
"size": {
|
||||
"type": "number",
|
||||
"description": "Number of bytes to read from the selected data source"
|
||||
}
|
||||
},
|
||||
"required": ["address", "size"]
|
||||
},
|
||||
"outputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"handle": {
|
||||
"type": "number",
|
||||
"description": "Handle of the data source the data was read from"
|
||||
},
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "Base64-encoded string of the read data"
|
||||
},
|
||||
"data_size": {
|
||||
"type": "number",
|
||||
"description": "Number of bytes that were actually read"
|
||||
}
|
||||
},
|
||||
"required": ["handle", "data"]
|
||||
}
|
||||
}
|
||||
25
plugins/builtin/romfs/mcp/tools/select_data_source.json
Normal file
25
plugins/builtin/romfs/mcp/tools/select_data_source.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "select_data_source",
|
||||
"title": "Select a Data Source given its handle",
|
||||
"description": "Selects one of the currently open data sources by its handle so that it can be used in subsequent operations. When running other tools that operate on a data source, this selected data source will be used. The returned handle is the one that was selected. If it failed to select the data source, the old handle is returned. That usually means that that handle doesn't exist (because it hasn't been opened yet or because it was closed already)",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"handle": {
|
||||
"type": "number",
|
||||
"description": "Handle of the data source to select"
|
||||
}
|
||||
},
|
||||
"required": ["handle"]
|
||||
},
|
||||
"outputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"selected_handle": {
|
||||
"type": "number",
|
||||
"description": "Handle of the selected data source"
|
||||
}
|
||||
},
|
||||
"required": ["selected_handle"]
|
||||
}
|
||||
}
|
||||
93
plugins/builtin/source/content/mcp_tools.cpp
Normal file
93
plugins/builtin/source/content/mcp_tools.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#include <content/providers/file_provider.hpp>
|
||||
#include <hex/api/content_registry/communication_interface.hpp>
|
||||
#include <hex/api/imhex_api/provider.hpp>
|
||||
#include <hex/helpers/crypto.hpp>
|
||||
#include <romfs/romfs.hpp>
|
||||
#include <wolv/literals.hpp>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
using namespace wolv::literals;
|
||||
|
||||
void registerMCPTools() {
|
||||
ContentRegistry::MCP::registerTool(romfs::get("mcp/tools/open_file.json").string(), [](const nlohmann::json &data) -> nlohmann::json {
|
||||
auto filePath = data.at("file_path").get<std::string>();
|
||||
|
||||
auto provider = ImHexApi::Provider::createProvider("hex.builtin.provider.file", true);
|
||||
if (auto *fileProvider = dynamic_cast<FileProvider*>(provider.get()); fileProvider != nullptr) {
|
||||
fileProvider->setPath(filePath);
|
||||
|
||||
ImHexApi::Provider::openProvider(provider);
|
||||
}
|
||||
|
||||
return mcp::TextContent {
|
||||
.text = "File opened"
|
||||
};
|
||||
});
|
||||
|
||||
ContentRegistry::MCP::registerTool(romfs::get("mcp/tools/list_open_data_sources.json").string(), [](const nlohmann::json &) -> nlohmann::json {
|
||||
const auto &providers = ImHexApi::Provider::getProviders();
|
||||
nlohmann::json array = nlohmann::json::array();
|
||||
for (const auto &provider : providers) {
|
||||
nlohmann::json providerInfo;
|
||||
providerInfo["name"] = provider->getName();
|
||||
providerInfo["type"] = provider->getTypeName().get();
|
||||
providerInfo["size"] = provider->getSize();
|
||||
providerInfo["is_writable"] = provider->isWritable();
|
||||
providerInfo["handle"] = provider->getID();
|
||||
|
||||
array.push_back(providerInfo);
|
||||
}
|
||||
|
||||
nlohmann::json result;
|
||||
result["data_sources"] = array;
|
||||
|
||||
return mcp::StructuredContent {
|
||||
.text = result.dump(),
|
||||
.data = result
|
||||
};
|
||||
});
|
||||
|
||||
ContentRegistry::MCP::registerTool(romfs::get("mcp/tools/select_data_source.json").string(), [](const nlohmann::json &data) -> nlohmann::json {
|
||||
const auto &providers = ImHexApi::Provider::getProviders();
|
||||
auto handle = data.at("handle").get<u64>();
|
||||
|
||||
for (size_t i = 0; i < providers.size(); i++) {
|
||||
if (providers[i]->getID() == handle) {
|
||||
ImHexApi::Provider::setCurrentProvider(static_cast<i64>(i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json result = { "selected_handle", ImHexApi::Provider::get()->getID() };
|
||||
return mcp::StructuredContent {
|
||||
.text = result.dump(),
|
||||
.data = result
|
||||
};
|
||||
});
|
||||
|
||||
ContentRegistry::MCP::registerTool(romfs::get("mcp/tools/read_data.json").string(), [](const nlohmann::json &data) -> nlohmann::json {
|
||||
auto address = data.at("address").get<u64>();
|
||||
auto size = data.at("size").get<u64>();
|
||||
|
||||
size = std::min<size_t>(size, 16_MiB);
|
||||
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
std::vector<u8> buffer(std::min(size, provider->getActualSize() - address));
|
||||
provider->read(address, buffer.data(), buffer.size());
|
||||
|
||||
auto base64 = crypt::encode64(buffer);
|
||||
|
||||
nlohmann::json result = {
|
||||
"handle", provider->getID(),
|
||||
"data", std::string(base64.begin(), base64.end()),
|
||||
"data_size", buffer.size()
|
||||
};
|
||||
return mcp::StructuredContent {
|
||||
.text = result.dump(),
|
||||
.data = result
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,6 +40,7 @@ namespace hex::plugin::builtin {
|
||||
void registerThemes();
|
||||
void registerBackgroundServices();
|
||||
void registerNetworkEndpoints();
|
||||
void registerMCPTools();
|
||||
void registerFileHandlers();
|
||||
void registerProjectHandlers();
|
||||
void registerAchievements();
|
||||
@@ -140,6 +141,7 @@ IMHEX_PLUGIN_SETUP_BUILTIN("Built-in", "WerWolv", "Default ImHex functionality")
|
||||
registerStyleHandlers();
|
||||
registerBackgroundServices();
|
||||
registerNetworkEndpoints();
|
||||
registerMCPTools();
|
||||
registerFileHandlers();
|
||||
registerProjectHandlers();
|
||||
registerCommandForwarders();
|
||||
|
||||
Reference in New Issue
Block a user