pluginbridge / Source /Plugin /McpServer.cpp
RAM2118's picture
Upload Source/Plugin/McpServer.cpp
bbd0e6f verified
Raw History Blame
7.75 kB
#include "McpServer.h"
#include "PluginBridgeProcessor.h"
#define CPPHTTPLIB_THREAD_POOL_COUNT 2
#include "httplib.h"
#include "json.hpp"
using json = nlohmann::json;
McpServer::McpServer(int port, PluginBridgeProcessor& processor)
: server(std::make_unique<httplib::Server>()), port(port), processor(processor)
{
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},
{"plugin", processor.isPluginLoaded() ? processor.getLoadedPluginName().toStdString() : "none"},
{"params", processor.getLoadedPluginParamCount()}
};
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, Mcp-Session-Id");
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.3.0"}}}
}}
};
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"},{"description","e.g. 'gain', 'band 1', 'cutoff'"}}}
}},{"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"},{"description","index→value map"}}}
}},{"required",json::array({"values"})}}}},
{{"name","get_analysis"},{"description","Real-time audio analysis (future)"},
{"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 (!processor.isPluginLoaded())
{
resultText = "no plugin loaded";
isError = true;
}
else if (toolName == "list_plugins")
{
json info = {
{"name", processor.getLoadedPluginName().toStdString()},
{"params", processor.getLoadedPluginParamCount()},
{"path", processor.getLastPluginPath().toStdString()}
};
resultText = info.dump();
}
else if (toolName == "search_param")
{
auto keyword = args.value("keyword", "");
auto results = processor.searchParams(juce::String(keyword));
json arr = json::array();
for (auto& p : results)
{
arr.push_back({
{"i", p.index},
{"id", p.id.toStdString()},
{"n", p.name.toStdString()},
{"v", std::round(p.value * 1000.0f) / 1000.0f},
{"t", p.displayText.toStdString()}
});
}
resultText = arr.dump();
}
else if (toolName == "get_params")
{
std::vector<int> ids;
for (auto& id : args["ids"])
ids.push_back(id.get<int>());
auto results = processor.getParams(ids);
json values = json::object();
for (auto& [idx, val] : results)
values[std::to_string(idx)] = std::round(val * 1000.0f) / 1000.0f;
resultText = values.dump();
}
else if (toolName == "set_params")
{
std::map<int, float> values;
for (auto& [key, val] : args["values"].items())
values[std::stoi(key)] = val.get<float>();
bool ok = processor.setParams(values);
resultText = ok ? "ok" : "set failed";
}
else if (toolName == "get_analysis")
{
resultText = "silent"; // Sprint 4
}
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();
}