mirror of https://github.com/WerWolv/ImHex
feat: Add initial MCP Server support
This commit is contained in:
parent
932c281223
commit
e696d384c2
|
|
@ -1 +1 @@
|
||||||
Subproject commit c9108712ba16a024dc7aee75f3fc9882629b2703
|
Subproject commit b0c9416568475a784838e3af8b0021a542e47cd7
|
||||||
|
|
@ -57,6 +57,9 @@ set(LIBIMHEX_SOURCES
|
||||||
source/ui/toast.cpp
|
source/ui/toast.cpp
|
||||||
source/ui/banner.cpp
|
source/ui/banner.cpp
|
||||||
|
|
||||||
|
source/mcp/client.cpp
|
||||||
|
source/mcp/server.cpp
|
||||||
|
|
||||||
source/subcommands/subcommands.cpp
|
source/subcommands/subcommands.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <hex/mcp/server.hpp>
|
||||||
|
|
||||||
EXPORT_MODULE namespace hex {
|
EXPORT_MODULE namespace hex {
|
||||||
|
|
||||||
/* Network Communication Interface Registry. Allows adding new communication interface endpoints */
|
/* Network Communication Interface Registry. Allows adding new communication interface endpoints */
|
||||||
|
|
@ -22,4 +24,19 @@ EXPORT_MODULE namespace hex {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace ContentRegistry::MCP {
|
||||||
|
|
||||||
|
namespace impl {
|
||||||
|
mcp::Server& getMcpServerInstance();
|
||||||
|
|
||||||
|
void setEnabled(bool enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEnabled();
|
||||||
|
bool isConnected();
|
||||||
|
|
||||||
|
void registerTool(std::string_view capabilities, std::function<nlohmann::json(const nlohmann::json ¶ms)> function);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace hex::mcp {
|
||||||
|
|
||||||
|
class Client {
|
||||||
|
public:
|
||||||
|
Client() = default;
|
||||||
|
~Client() = default;
|
||||||
|
|
||||||
|
int run(std::istream &input, std::ostream &output);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <wolv/net/socket_server.hpp>
|
||||||
|
|
||||||
|
namespace hex::mcp {
|
||||||
|
|
||||||
|
class Server {
|
||||||
|
public:
|
||||||
|
constexpr static auto McpInternalPort = 19743;
|
||||||
|
|
||||||
|
Server();
|
||||||
|
~Server();
|
||||||
|
|
||||||
|
void listen();
|
||||||
|
void shutdown();
|
||||||
|
void disconnect();
|
||||||
|
bool isConnected();
|
||||||
|
|
||||||
|
void addPrimitive(std::string type, std::string_view capabilities, std::function<nlohmann::json(const nlohmann::json ¶ms)> function);
|
||||||
|
|
||||||
|
private:
|
||||||
|
nlohmann::json handleInitialize();
|
||||||
|
void handleNotifications(const std::string &method, const nlohmann::json ¶ms);
|
||||||
|
|
||||||
|
struct Primitive {
|
||||||
|
nlohmann::json capabilities;
|
||||||
|
std::function<nlohmann::json(const nlohmann::json ¶ms)> function;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::map<std::string, std::map<std::string, Primitive>> m_primitives;
|
||||||
|
|
||||||
|
wolv::net::SocketServer m_server;
|
||||||
|
bool m_connected = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1413,6 +1413,40 @@ namespace hex {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace ContentRegistry::MCP {
|
||||||
|
|
||||||
|
namespace impl {
|
||||||
|
|
||||||
|
mcp::Server& getMcpServerInstance() {
|
||||||
|
static AutoReset<std::unique_ptr<mcp::Server>> server;
|
||||||
|
|
||||||
|
if (*server == nullptr)
|
||||||
|
server = std::make_unique<mcp::Server>();
|
||||||
|
|
||||||
|
return **server;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool s_mcpEnabled = false;
|
||||||
|
void setEnabled(bool enabled) {
|
||||||
|
s_mcpEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEnabled() {
|
||||||
|
return impl::s_mcpEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isConnected() {
|
||||||
|
return impl::getMcpServerInstance().isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerTool(std::string_view capabilities, std::function<nlohmann::json(const nlohmann::json ¶ms)> function) {
|
||||||
|
impl::getMcpServerInstance().addPrimitive("tools", capabilities, function);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
namespace ContentRegistry::Experiments {
|
namespace ContentRegistry::Experiments {
|
||||||
|
|
||||||
namespace impl {
|
namespace impl {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
#include <hex/mcp/client.hpp>
|
||||||
|
#include <hex/mcp/server.hpp>
|
||||||
|
|
||||||
|
#include <hex.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <hex/helpers/logger.hpp>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <wolv/net/socket_client.hpp>
|
||||||
|
|
||||||
|
namespace hex::mcp {
|
||||||
|
|
||||||
|
int Client::run(std::istream &input, std::ostream &output) {
|
||||||
|
wolv::net::SocketClient client(wolv::net::SocketClient::Type::TCP, true);
|
||||||
|
client.connect("127.0.0.1", Server::McpInternalPort);
|
||||||
|
|
||||||
|
if (!client.isConnected()) {
|
||||||
|
log::resumeLogging();
|
||||||
|
log::error("Cannot connect to ImHex. Do you have an instance running and is the MCP server enabled?");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
std::string request;
|
||||||
|
std::getline(input, request);
|
||||||
|
|
||||||
|
client.writeString(request);
|
||||||
|
auto response = client.readString();
|
||||||
|
if (!response.empty() && response.front() != 0x00)
|
||||||
|
output << response << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,226 @@
|
||||||
|
#include <hex/mcp/server.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <hex/helpers/logger.hpp>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <wolv/net/socket_client.hpp>
|
||||||
|
#include <hex/api/imhex_api/system.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
namespace hex::mcp {
|
||||||
|
|
||||||
|
class JsonRpc {
|
||||||
|
public:
|
||||||
|
explicit JsonRpc(const std::string &request) : m_request(request) { }
|
||||||
|
|
||||||
|
struct MethodNotFoundException : std::exception {};
|
||||||
|
struct InvalidParametersException : std::exception {};
|
||||||
|
|
||||||
|
std::optional<std::string> execute(auto callback) {
|
||||||
|
try {
|
||||||
|
auto requestJson = nlohmann::json::parse(m_request);
|
||||||
|
|
||||||
|
if (requestJson.is_array()) {
|
||||||
|
return handleBatchedMessages(requestJson, callback).transform([](const auto &response) { return response.dump(); });
|
||||||
|
} else {
|
||||||
|
return handleMessage(requestJson, callback).transform([](const auto &response) { return response.dump(); });
|
||||||
|
}
|
||||||
|
} catch (const MethodNotFoundException &) {
|
||||||
|
return createErrorMessage(ErrorCode::MethodNotFound, "Method not found").dump();
|
||||||
|
} catch (const InvalidParametersException &) {
|
||||||
|
return createErrorMessage(ErrorCode::InvalidParams, "Invalid params").dump();
|
||||||
|
} catch (const nlohmann::json::parse_error &) {
|
||||||
|
return createErrorMessage(ErrorCode::ParseError, "Parse error").dump();
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
return createErrorMessage(ErrorCode::InternalError, e.what()).dump();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<nlohmann::json> handleMessage(const nlohmann::json &request, auto callback) {
|
||||||
|
// Validate JSON-RPC request
|
||||||
|
if (!request.contains("jsonrpc") || request["jsonrpc"] != "2.0" ||
|
||||||
|
!request.contains("method") || !request["method"].is_string()) {
|
||||||
|
m_id = request.contains("id") ? std::optional(request["id"].get<int>()) : std::nullopt;
|
||||||
|
|
||||||
|
return createErrorMessage(ErrorCode::InvalidRequest, "Invalid Request").dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_id = request.contains("id") ? std::optional(request["id"].get<int>()) : std::nullopt;
|
||||||
|
|
||||||
|
// Execute the method
|
||||||
|
auto result = callback(request["method"].get<std::string>(), request.value("params", nlohmann::json::object()));
|
||||||
|
|
||||||
|
if (!m_id.has_value())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return createResponseMessage(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<nlohmann::json> handleBatchedMessages(const nlohmann::json &request, auto callback) {
|
||||||
|
if (!request.is_array()) {
|
||||||
|
return createErrorMessage(ErrorCode::InvalidRequest, "Invalid Request").dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json responses = nlohmann::json::array();
|
||||||
|
for (const auto &message : request) {
|
||||||
|
auto response = handleMessage(message, callback);
|
||||||
|
if (response.has_value())
|
||||||
|
responses.push_back(*response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responses.empty())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return responses.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ErrorCode {
|
||||||
|
ParseError = -32700,
|
||||||
|
InvalidRequest = -32600,
|
||||||
|
MethodNotFound = -32601,
|
||||||
|
InvalidParams = -32602,
|
||||||
|
InternalError = -32603,
|
||||||
|
};
|
||||||
|
|
||||||
|
nlohmann::json createDefaultMessage() {
|
||||||
|
nlohmann::json message;
|
||||||
|
message["jsonrpc"] = "2.0";
|
||||||
|
if (m_id.has_value())
|
||||||
|
message["id"] = m_id.value();
|
||||||
|
else
|
||||||
|
message["id"] = nullptr;
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json createErrorMessage(ErrorCode code, const std::string &message) {
|
||||||
|
auto json = createDefaultMessage();
|
||||||
|
json["error"] = {
|
||||||
|
{ "code", int(code) },
|
||||||
|
{ "message", message }
|
||||||
|
};
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json createResponseMessage(const nlohmann::json &result) {
|
||||||
|
auto json = createDefaultMessage();
|
||||||
|
json["result"] = result;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_request;
|
||||||
|
std::optional<int> m_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
Server::Server() : m_server(McpInternalPort, 1024, 1, true) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Server::~Server() {
|
||||||
|
this->shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::listen() {
|
||||||
|
m_server.accept([this](auto, const std::vector<u8> &data) -> std::vector<u8> {
|
||||||
|
std::string request(data.begin(), data.end());
|
||||||
|
|
||||||
|
log::debug("MCP ----> {}", request);
|
||||||
|
|
||||||
|
JsonRpc rpc(request);
|
||||||
|
auto response = rpc.execute([this](const std::string &method, const nlohmann::json ¶ms) -> nlohmann::json {
|
||||||
|
if (method == "initialize") {
|
||||||
|
return handleInitialize();
|
||||||
|
} else if (method.starts_with("notifications/")) {
|
||||||
|
handleNotifications(method.substr(14), params);
|
||||||
|
return {};
|
||||||
|
} else if (method.ends_with("/list")) {
|
||||||
|
auto primitiveName = method.substr(0, method.size() - 5);
|
||||||
|
if (m_primitives.contains(primitiveName)) {
|
||||||
|
nlohmann::json capabilitiesList = nlohmann::json::array();
|
||||||
|
for (const auto &[name, primitive] : m_primitives[primitiveName]) {
|
||||||
|
capabilitiesList.push_back(primitive.capabilities);
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json result;
|
||||||
|
result[primitiveName] = capabilitiesList;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} else if (method.ends_with("/call")) {
|
||||||
|
auto primitive = method.substr(0, method.size() - 5);
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw JsonRpc::MethodNotFoundException();
|
||||||
|
});
|
||||||
|
|
||||||
|
log::debug("MCP <---- {}", response.value_or("<Nothing>"));
|
||||||
|
|
||||||
|
if (response.has_value())
|
||||||
|
return { response->begin(), response->end() };
|
||||||
|
else
|
||||||
|
return std::vector<u8>{ 0x00 };
|
||||||
|
}, [this](auto) {
|
||||||
|
log::info("MCP client disconnected");
|
||||||
|
m_connected = false;
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::shutdown() {
|
||||||
|
m_server.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::disconnect() {
|
||||||
|
m_server.disconnectClients();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::addPrimitive(std::string type, std::string_view capabilities, std::function<nlohmann::json(const nlohmann::json ¶ms)> function) {
|
||||||
|
auto json = nlohmann::json::parse(capabilities);
|
||||||
|
auto name = json["name"].get<std::string>();
|
||||||
|
|
||||||
|
m_primitives[type][name] = {
|
||||||
|
json,
|
||||||
|
function
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nlohmann::json Server::handleInitialize() {
|
||||||
|
constexpr static auto ServerName = "ImHex";
|
||||||
|
constexpr static auto ProtocolVersion = "2025-06-18";
|
||||||
|
|
||||||
|
return {
|
||||||
|
{ "protocolVersion", ProtocolVersion },
|
||||||
|
{
|
||||||
|
"capabilities",
|
||||||
|
{
|
||||||
|
{ "tools", nlohmann::json::object() },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"serverInfo", {
|
||||||
|
{ "name", ServerName },
|
||||||
|
{ "version", ImHexApi::System::getImHexVersion().get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::handleNotifications(const std::string &method, [[maybe_unused]] const nlohmann::json ¶ms) {
|
||||||
|
if (method == "initialized") {
|
||||||
|
m_connected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Server::isConnected() {
|
||||||
|
return m_connected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,6 +30,7 @@ namespace hex::plugin::builtin {
|
||||||
void handleValidatePluginCommand(const std::vector<std::string> &args);
|
void handleValidatePluginCommand(const std::vector<std::string> &args);
|
||||||
void handleSaveEditorCommand(const std::vector<std::string> &args);
|
void handleSaveEditorCommand(const std::vector<std::string> &args);
|
||||||
void handleFileInfoCommand(const std::vector<std::string> &args);
|
void handleFileInfoCommand(const std::vector<std::string> &args);
|
||||||
|
void handleMCPCommand(const std::vector<std::string> &args);
|
||||||
|
|
||||||
void registerCommandForwarders();
|
void registerCommandForwarders();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@
|
||||||
"hex.builtin.achievement.misc.download_from_store.name": "There's an app for that",
|
"hex.builtin.achievement.misc.download_from_store.name": "There's an app for that",
|
||||||
"hex.builtin.achievement.misc.download_from_store.desc": "Download any item from the Content Store",
|
"hex.builtin.achievement.misc.download_from_store.desc": "Download any item from the Content Store",
|
||||||
"hex.builtin.background_service.network_interface": "Network Interface",
|
"hex.builtin.background_service.network_interface": "Network Interface",
|
||||||
|
"hex.builtin.setting.general.mcp_server": "MCP Server support",
|
||||||
"hex.builtin.background_service.auto_backup": "Auto Backup",
|
"hex.builtin.background_service.auto_backup": "Auto Backup",
|
||||||
"hex.builtin.command.calc.desc": "Calculator",
|
"hex.builtin.command.calc.desc": "Calculator",
|
||||||
"hex.builtin.command.convert.desc": "Unit conversion",
|
"hex.builtin.command.convert.desc": "Unit conversion",
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
#include <fmt/chrono.h>
|
#include <fmt/chrono.h>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <romfs/romfs.hpp>
|
||||||
|
#include <toasts/toast_notification.hpp>
|
||||||
|
|
||||||
namespace hex::plugin::builtin {
|
namespace hex::plugin::builtin {
|
||||||
|
|
||||||
|
|
@ -103,6 +105,16 @@ namespace hex::plugin::builtin {
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleMCPServer() {
|
||||||
|
if (!ContentRegistry::MCP::isEnabled()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
ContentRegistry::MCP::impl::getMcpServerInstance().disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentRegistry::MCP::impl::getMcpServerInstance().listen();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerBackgroundServices() {
|
void registerBackgroundServices() {
|
||||||
|
|
@ -110,12 +122,17 @@ namespace hex::plugin::builtin {
|
||||||
s_networkInterfaceServiceEnabled = value.get<bool>(false);
|
s_networkInterfaceServiceEnabled = value.get<bool>(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ContentRegistry::Settings::onChange("hex.builtin.setting.general", "hex.builtin.setting.general.mcp_server", [](const ContentRegistry::Settings::SettingsValue &value) {
|
||||||
|
ContentRegistry::MCP::impl::setEnabled(value.get<bool>(false));
|
||||||
|
});
|
||||||
|
|
||||||
ContentRegistry::Settings::onChange("hex.builtin.setting.general", "hex.builtin.setting.general.backups.auto_backup_time", [](const ContentRegistry::Settings::SettingsValue &value) {
|
ContentRegistry::Settings::onChange("hex.builtin.setting.general", "hex.builtin.setting.general.backups.auto_backup_time", [](const ContentRegistry::Settings::SettingsValue &value) {
|
||||||
s_autoBackupTime = value.get<int>(0) * 30;
|
s_autoBackupTime = value.get<int>(0) * 30;
|
||||||
});
|
});
|
||||||
|
|
||||||
ContentRegistry::BackgroundServices::registerService("hex.builtin.background_service.network_interface", handleNetworkInterfaceService);
|
ContentRegistry::BackgroundServices::registerService("hex.builtin.background_service.network_interface", handleNetworkInterfaceService);
|
||||||
ContentRegistry::BackgroundServices::registerService("hex.builtin.background_service.auto_backup", handleAutoBackup);
|
ContentRegistry::BackgroundServices::registerService("hex.builtin.background_service.auto_backup", handleAutoBackup);
|
||||||
|
ContentRegistry::BackgroundServices::registerService("hex.builtin.background_service.mcp", handleMCPServer);
|
||||||
|
|
||||||
EventProviderDirtied::subscribe([](prv::Provider *) {
|
EventProviderDirtied::subscribe([](prv::Provider *) {
|
||||||
s_dataDirty = true;
|
s_dataDirty = true;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
#include <iostream>
|
||||||
#include <content/command_line_interface.hpp>
|
#include <content/command_line_interface.hpp>
|
||||||
|
#include <hex/mcp/client.hpp>
|
||||||
|
|
||||||
#include <hex/api/imhex_api/system.hpp>
|
#include <hex/api/imhex_api/system.hpp>
|
||||||
#include <hex/api/imhex_api/hex_editor.hpp>
|
#include <hex/api/imhex_api/hex_editor.hpp>
|
||||||
|
|
@ -530,6 +532,14 @@ namespace hex::plugin::builtin {
|
||||||
ContentRegistry::Views::setFullScreenView<ViewFullScreenFileInfo>(path);
|
ContentRegistry::Views::setFullScreenView<ViewFullScreenFileInfo>(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleMCPCommand(const std::vector<std::string> &) {
|
||||||
|
mcp::Client client;
|
||||||
|
|
||||||
|
auto result = client.run(std::cin, std::cout);
|
||||||
|
std::fprintf(stderr, "MCP Client disconnected!\n");
|
||||||
|
std::exit(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void registerCommandForwarders() {
|
void registerCommandForwarders() {
|
||||||
hex::subcommands::registerSubCommand("open", [](const std::vector<std::string> &args){
|
hex::subcommands::registerSubCommand("open", [](const std::vector<std::string> &args){
|
||||||
|
|
|
||||||
|
|
@ -768,6 +768,8 @@ namespace hex::plugin::builtin {
|
||||||
|
|
||||||
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.network_interface", false);
|
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.network_interface", false);
|
||||||
|
|
||||||
|
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.mcp_server", false);
|
||||||
|
|
||||||
#if !defined(OS_WEB)
|
#if !defined(OS_WEB)
|
||||||
ContentRegistry::Settings::add<ServerContactWidget>("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.server_contact");
|
ContentRegistry::Settings::add<ServerContactWidget>("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.server_contact");
|
||||||
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.upload_crash_logs", true);
|
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.upload_crash_logs", true);
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
|
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <fonts/tabler_icons.hpp>
|
#include <fonts/tabler_icons.hpp>
|
||||||
|
#include <hex/api/content_registry/communication_interface.hpp>
|
||||||
|
|
||||||
namespace hex::plugin::builtin {
|
namespace hex::plugin::builtin {
|
||||||
|
|
||||||
|
|
@ -239,6 +240,20 @@ namespace hex::plugin::builtin {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContentRegistry::UserInterface::addFooterItem([] {
|
||||||
|
if (ContentRegistry::MCP::isConnected()) {
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiExt::GetCustomColorU32(ImGuiCustomCol_Highlight));
|
||||||
|
} else {
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ContentRegistry::MCP::isEnabled()) {
|
||||||
|
ImGui::TextUnformatted(ICON_VS_MCP);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
});
|
||||||
|
|
||||||
if (dbg::debugModeEnabled()) {
|
if (dbg::debugModeEnabled()) {
|
||||||
ContentRegistry::UserInterface::addFooterItem([] {
|
ContentRegistry::UserInterface::addFooterItem([] {
|
||||||
static float framerate = 0;
|
static float framerate = 0;
|
||||||
|
|
|
||||||
|
|
@ -73,8 +73,8 @@ IMHEX_PLUGIN_SUBCOMMANDS() {
|
||||||
{ "open", "o", "Open files passed as argument. [default]", hex::plugin::builtin::handleOpenCommand },
|
{ "open", "o", "Open files passed as argument. [default]", hex::plugin::builtin::handleOpenCommand },
|
||||||
{ "new", "n", "Create a new empty file", hex::plugin::builtin::handleNewCommand },
|
{ "new", "n", "Create a new empty file", hex::plugin::builtin::handleNewCommand },
|
||||||
|
|
||||||
{ "select", "s", "Select a range of bytes in the Hex Editor", hex::plugin::builtin::handleSelectCommand },
|
{ "select", "s", "Select a range of bytes in the Hex Editor", hex::plugin::builtin::handleSelectCommand },
|
||||||
{ "pattern", "p", "Sets the loaded pattern", hex::plugin::builtin::handlePatternCommand },
|
{ "pattern", "p", "Sets the loaded pattern", hex::plugin::builtin::handlePatternCommand },
|
||||||
{ "calc", "", "Evaluate a mathematical expression", hex::plugin::builtin::handleCalcCommand },
|
{ "calc", "", "Evaluate a mathematical expression", hex::plugin::builtin::handleCalcCommand },
|
||||||
{ "hash", "", "Calculate the hash of a file", hex::plugin::builtin::handleHashCommand },
|
{ "hash", "", "Calculate the hash of a file", hex::plugin::builtin::handleHashCommand },
|
||||||
{ "encode", "", "Encode a string", hex::plugin::builtin::handleEncodeCommand },
|
{ "encode", "", "Encode a string", hex::plugin::builtin::handleEncodeCommand },
|
||||||
|
|
@ -88,6 +88,7 @@ IMHEX_PLUGIN_SUBCOMMANDS() {
|
||||||
{ "validate-plugin", "", "Validates that a plugin can be loaded", hex::plugin::builtin::handleValidatePluginCommand },
|
{ "validate-plugin", "", "Validates that a plugin can be loaded", hex::plugin::builtin::handleValidatePluginCommand },
|
||||||
{ "save-editor", "", "Opens a pattern file for save file editing", hex::plugin::builtin::handleSaveEditorCommand },
|
{ "save-editor", "", "Opens a pattern file for save file editing", hex::plugin::builtin::handleSaveEditorCommand },
|
||||||
{ "file-info", "i", "Displays information about a file", hex::plugin::builtin::handleFileInfoCommand },
|
{ "file-info", "i", "Displays information about a file", hex::plugin::builtin::handleFileInfoCommand },
|
||||||
|
{ "mcp", "", "Starts a MCP Server for AI to interact with", hex::plugin::builtin::handleMCPCommand },
|
||||||
};
|
};
|
||||||
|
|
||||||
IMHEX_PLUGIN_SETUP_BUILTIN("Built-in", "WerWolv", "Default ImHex functionality") {
|
IMHEX_PLUGIN_SETUP_BUILTIN("Built-in", "WerWolv", "Default ImHex functionality") {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue