pluginbridge / Source /Plugin /McpServer.cpp
RAM2118's picture
Upload Source/Plugin/McpServer.cpp
9836bb8 verified
Raw History Blame
6.81 kB
#include "McpServer.h"
#include "HelperConnection.h"
#include "IPCProtocol.h"
#define CPPHTTPLIB_THREAD_POOL_COUNT 2
#include "httplib.h"
#include "json.hpp"
using json = nlohmann::json;
McpServer::McpServer(int port, HelperConnection& helper)
: server(std::make_unique<httplib::Server>()), port(port), helper(helper)
{
setupRoutes();
}
McpServer::~McpServer() { stop(); }
void McpServer::setupRoutes()
{
server->Post("/mcp", [this](const httplib::Request& req, httplib::Response& res) {
res.set_header("Access-Control-Allow-Origin", "http://localhost");
try {
auto body = json::parse(req.body);
std::string method = body.value("method", "");
std::string responseStr;
if (method == "initialize")
responseStr = handleInitialize(req.body);
else if (method == "notifications/initialized") {
initialized = true; res.status = 204; return;
}
else if (method == "tools/list")
responseStr = handleToolsList(req.body);
else if (method == "tools/call")
responseStr = handleToolsCall(req.body);
else {
json err = {{"jsonrpc","2.0"},{"id",body.value("id",json(nullptr))},
{"error",{{"code",-32601},{"message","Method not found"}}}};
responseStr = err.dump();
}
if (!responseStr.empty())
res.set_content(responseStr, "application/json");
} catch (const json::exception&) {
json err = {{"jsonrpc","2.0"},{"id",nullptr},{"error",{{"code",-32700},{"message","Parse error"}}}};
res.set_content(err.dump(), "application/json");
}
});
server->Get("/health", [this](const httplib::Request&, httplib::Response& res) {
json health = {{"status","ok"},{"port",port},{"initialized",initialized},
{"helper",helper.isHelperRunning() ? "connected" : "disconnected"}};
res.set_content(health.dump(), "application/json");
});
server->Options("/mcp", [](const httplib::Request&, httplib::Response& res) {
res.set_header("Access-Control-Allow-Origin", "http://localhost");
res.set_header("Access-Control-Allow-Methods", "POST, OPTIONS");
res.set_header("Access-Control-Allow-Headers", "Content-Type");
res.status = 204;
});
}
void McpServer::start()
{
if (running.load()) return;
serverThread = std::thread([this]() { running.store(true); server->listen("127.0.0.1", port); running.store(false); });
server->wait_until_ready();
}
void McpServer::stop()
{
if (server && server->is_running()) server->stop();
if (serverThread.joinable()) serverThread.join();
running.store(false);
}
bool McpServer::isRunning() const { return running.load(); }
std::string McpServer::handleInitialize(const std::string& requestBody)
{
auto body = json::parse(requestBody);
sessionId = "pb-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
json response = {{"jsonrpc","2.0"},{"id",body.value("id",json(nullptr))},
{"result",{{"protocolVersion","2025-06-18"},{"capabilities",{{"tools",{{"listChanged",false}}}}},
{"serverInfo",{{"name","PluginBridge"},{"version","0.5.2"}}}}}};
return response.dump();
}
std::string McpServer::handleToolsList(const std::string& requestBody)
{
auto body = json::parse(requestBody);
json response = {{"jsonrpc","2.0"},{"id",body.value("id",json(nullptr))},
{"result",{{"tools",json::array({
{{"name","list_plugins"},{"description","Show loaded plugin info"},{"inputSchema",{{"type","object"},{"properties",json::object()}}}},
{{"name","search_param"},{"description","Search params by keyword"},{"inputSchema",{{"type","object"},{"properties",{{"keyword",{{"type","string"}}}}},{"required",json::array({"keyword"})}}}},
{{"name","get_params"},{"description","Get param values by index"},{"inputSchema",{{"type","object"},{"properties",{{"ids",{{"type","array"},{"items",{{"type","integer"}}}}}}},{"required",json::array({"ids"})}}}},
{{"name","set_params"},{"description","Set params (batch, 0.0-1.0)"},{"inputSchema",{{"type","object"},{"properties",{{"values",{{"type","object"}}}}},{"required",json::array({"values"})}}}},
{{"name","get_analysis"},{"description","Real-time audio analysis: LUFS, true peak, 7-band balance, stereo width, brightness. Returns compact string."},{"inputSchema",{{"type","object"},{"properties",json::object()}}}}
})}}}};
return response.dump();
}
std::string McpServer::handleToolsCall(const std::string& requestBody)
{
auto body = json::parse(requestBody);
auto params = body["params"];
std::string toolName = params["name"];
auto args = params.value("arguments", json::object());
std::string resultText;
bool isError = false;
if (!helper.isHelperRunning())
{
resultText = "helper not connected";
isError = true;
}
else if (toolName == "list_plugins")
{
resultText = "[\"loaded\"]";
}
else if (toolName == "search_param")
{
json cmd = {{"cmd","search_param"},{"keyword",args.value("keyword","")}};
auto resp = helper.sendCommand(cmd.dump());
try { auto j = json::parse(resp); resultText = j.value("ok",false) ? j["results"].dump() : "search failed"; }
catch (...) { resultText = "parse error"; isError = true; }
}
else if (toolName == "get_params")
{
json cmd = {{"cmd","get_params"},{"ids",args.value("ids",json::array())}};
auto resp = helper.sendCommand(cmd.dump());
try { auto j = json::parse(resp); resultText = j.value("ok",false) ? j["values"].dump() : "get failed"; }
catch (...) { resultText = "parse error"; isError = true; }
}
else if (toolName == "set_params")
{
json cmd = {{"cmd","set_params"},{"values",args.value("values",json::object())}};
auto resp = helper.sendCommand(cmd.dump());
try { auto j = json::parse(resp); resultText = j.value("ok",false) ? "ok" : "set failed"; }
catch (...) { resultText = "parse error"; isError = true; }
}
else if (toolName == "get_analysis")
{
if (getAnalysisCallback)
resultText = getAnalysisCallback();
else
resultText = "analyser not ready";
}
else
{
resultText = "unknown tool: " + toolName;
isError = true;
}
json response = {{"jsonrpc","2.0"},{"id",body.value("id",json(nullptr))},
{"result",{{"content",json::array({{{"type","text"},{"text",resultText}}})},{"isError",isError}}}};
return response.dump();
}