#include "McpServer.h" // httplib must be included in exactly ONE .cpp file #define CPPHTTPLIB_THREAD_POOL_COUNT 2 #include "httplib.h" #include "json.hpp" using json = nlohmann::json; McpServer::McpServer(int port) : server(std::make_unique()), port(port) { setupRoutes(); } McpServer::~McpServer() { stop(); } void McpServer::setupRoutes() { // Single MCP endpoint — all JSON-RPC goes through here 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") { // Notification — no response needed initialized = true; res.status = 204; return; } else if (method == "tools/list") { responseStr = handleToolsList(); } else if (method == "tools/call") { responseStr = handleToolsCall(req.body); } else { json errResponse = { {"jsonrpc", "2.0"}, {"id", body.value("id", json(nullptr))}, {"error", {{"code", -32601}, {"message", "Method not found"}}} }; responseStr = errResponse.dump(); } if (!responseStr.empty()) { res.set_content(responseStr, "application/json"); } } catch (const json::exception& e) { json errResponse = { {"jsonrpc", "2.0"}, {"id", nullptr}, {"error", {{"code", -32700}, {"message", "Parse error"}}} }; res.set_content(errResponse.dump(), "application/json"); } }); // Health check — quick way to verify server is alive server->Get("/health", [this](const httplib::Request&, httplib::Response& res) { json health = {{"status", "ok"}, {"port", port}, {"initialized", initialized}}; res.set_content(health.dump(), "application/json"); }); // CORS preflight 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); }); // Wait until server is actually ready 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(); } void McpServer::serverThreadFunc() { server->listen("127.0.0.1", port); } // --- MCP Protocol Handlers --- std::string McpServer::handleInitialize(const std::string& requestBody) { auto body = json::parse(requestBody); // Generate session ID 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.1.0"} }} }} }; return response.dump(); } std::string McpServer::handleToolsList() { json response = { {"jsonrpc", "2.0"}, {"id", 1}, {"result", { {"tools", json::array({ { {"name", "list_plugins"}, {"description", "List all loaded plugin instances"}, {"inputSchema", {{"type", "object"}, {"properties", json::object()}}} }, { {"name", "search_param"}, {"description", "Search plugin parameters by keyword"}, {"inputSchema", { {"type", "object"}, {"properties", { {"plugin", {{"type", "string"}, {"description", "Plugin name"}}}, {"keyword", {{"type", "string"}, {"description", "Search term"}}} }}, {"required", json::array({"plugin", "keyword"})} }} }, { {"name", "get_params"}, {"description", "Get current values for parameter IDs"}, {"inputSchema", { {"type", "object"}, {"properties", { {"plugin", {{"type", "string"}}}, {"ids", {{"type", "array"}, {"items", {{"type", "integer"}}}}} }}, {"required", json::array({"plugin", "ids"})} }} }, { {"name", "set_params"}, {"description", "Set parameter values by ID (batch)"}, {"inputSchema", { {"type", "object"}, {"properties", { {"plugin", {{"type", "string"}}}, {"values", {{"type", "object"}, {"description", "param_id → value (0.0-1.0)"}}} }}, {"required", json::array({"plugin", "values"})} }} }, { {"name", "get_analysis"}, {"description", "Get real-time audio analysis (LUFS, bands, stereo)"}, {"inputSchema", {{"type", "object"}, {"properties", json::object()}}} } })} }} }; return response.dump(); } std::string McpServer::handleToolsCall(const std::string& requestBody) { auto body = json::parse(requestBody); std::string toolName = body["params"]["name"]; json result; if (toolName == "list_plugins") { // Sprint 1: no plugins loaded yet result = { {"content", json::array({{{"type", "text"}, {"text", "[]"}}})}, {"isError", false} }; } else if (toolName == "search_param") { result = { {"content", json::array({{{"type", "text"}, {"text", "no plugin loaded"}}})}, {"isError", true} }; } else if (toolName == "get_params") { result = { {"content", json::array({{{"type", "text"}, {"text", "no plugin loaded"}}})}, {"isError", true} }; } else if (toolName == "set_params") { result = { {"content", json::array({{{"type", "text"}, {"text", "no plugin loaded"}}})}, {"isError", true} }; } else if (toolName == "get_analysis") { // Sprint 1: stub response result = { {"content", json::array({{{"type", "text"}, {"text", "silent"}}})}, {"isError", false} }; } else { result = { {"content", json::array({{{"type", "text"}, {"text", "unknown tool: " + toolName}}})}, {"isError", true} }; } json response = { {"jsonrpc", "2.0"}, {"id", body.value("id", json(nullptr))}, {"result", result} }; return response.dump(); }