LCOV - code coverage report
Current view: top level - muse/framework/rcontrol/mcp - mcpserver.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 177 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore/Audacity CLA applies
       4              :  *
       5              :  * Copyright (C) 2026 MuseScore/Audacity and others
       6              :  *
       7              :  * This program is free software: you can redistribute it and/or modify
       8              :  * it under the terms of the GNU General Public License version 3 as
       9              :  * published by the Free Software Foundation.
      10              :  *
      11              :  * This program is distributed in the hope that it will be useful,
      12              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      13              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14              :  * GNU General Public License for more details.
      15              :  *
      16              :  * You should have received a copy of the GNU General Public License
      17              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      18              :  */
      19              : 
      20              :  #include "mcpserver.h"
      21              : 
      22              :  #include "serialization/json.h"
      23              : 
      24              :  #include "transport/tcptransport.h"
      25              :  #include "transport/httptransport.h"
      26              : 
      27              :  #include "log.h"
      28              : 
      29              : static const char* PROTOCOL_VERSION = "2025-11-25";
      30              : static const char* SERVER_NAME = "MuseMCP";
      31              : 
      32              : using namespace muse::rcontrol::mcp;
      33              : 
      34            0 : static std::map<std::string, muse::Val> toArgs(const muse::JsonObject& obj)
      35              : {
      36            0 :     std::map<std::string, muse::Val> args;
      37            0 :     for (const auto& key : obj.keys()) {
      38            0 :         args[key] = obj[key].toVal();
      39            0 :     }
      40            0 :     return args;
      41            0 : }
      42              : 
      43            0 : McpServer::McpServer(const std::string& version, ITransport* transport)
      44            0 :     : m_version(version),
      45            0 :     m_transport(transport)
      46              : {
      47            0 :     if (!m_transport) {
      48            0 :         m_transport = new HttpTransport();
      49              :     }
      50            0 : }
      51              : 
      52            0 : McpServer::~McpServer()
      53              : {
      54            0 :     deinit();
      55            0 :     delete m_transport;
      56            0 :     m_transport = nullptr;
      57            0 : }
      58              : 
      59            0 : void McpServer::init()
      60              : {
      61            0 :     IF_ASSERT_FAILED(m_transport) {
      62            0 :         return;
      63              :     }
      64              : 
      65            0 :     m_transport->onRequest([this](const ByteArray& request, const ITransport::ResponseHandler& onResponse) {
      66            0 :         JsonObject req = JsonDocument::fromJson(request).rootObject();
      67            0 :         onRequest(req, [onResponse](const JsonObject& res) {
      68            0 :             JsonDocument doc(res);
      69            0 :             onResponse(doc.toJson(JsonDocument::Format::Compact));
      70            0 :         });
      71            0 :     });
      72              : 
      73            0 :     m_transport->start();
      74              : }
      75              : 
      76            0 : void McpServer::deinit()
      77              : {
      78            0 :     m_transport->stop();
      79            0 : }
      80              : 
      81            0 : void McpServer::onRequest(const JsonObject& request, const std::function<void(const JsonObject&)>& onResponse)
      82              : {
      83            0 :     const String method = request.value("method").toString();
      84            0 :     if (method == u"initialize") {
      85            0 :         onInitialize(request, onResponse);
      86            0 :     } else if (method == u"tools/list") {
      87            0 :         onToolsList(request, onResponse);
      88            0 :     } else if (method == u"tools/call") {
      89            0 :         onToolsCall(request, onResponse);
      90            0 :     } else if (method.startsWith(u"notifications/")) {
      91            0 :         onNotifications(method);
      92            0 :         onResponse(JsonObject());
      93              :     } else {
      94            0 :         LOGE() << "Unknown method: " << method;
      95            0 :         replyError(request, -32601, "Method not found", onResponse);
      96              :     }
      97            0 : }
      98              : 
      99            0 : void McpServer::onInitialize(const JsonObject& request, const std::function<void(const JsonObject&)>& onResponse)
     100              : {
     101            0 :     JsonObject response;
     102            0 :     response["jsonrpc"] = "2.0";
     103            0 :     response["id"] = request.value("id");
     104              : 
     105            0 :     JsonObject result;
     106            0 :     result["protocolVersion"] = PROTOCOL_VERSION;
     107              : 
     108            0 :     JsonObject toolCapabilities;
     109            0 :     toolCapabilities["listChanged"] = false;
     110              : 
     111            0 :     JsonObject capabilities;
     112            0 :     capabilities["tools"] = toolCapabilities;
     113            0 :     result["capabilities"] = capabilities;
     114              : 
     115            0 :     JsonObject serverInfo;
     116            0 :     serverInfo["name"] = SERVER_NAME;
     117            0 :     serverInfo["version"] = m_version;
     118            0 :     result["serverInfo"] = serverInfo;
     119              : 
     120            0 :     response["result"] = result;
     121              : 
     122            0 :     onResponse(response);
     123            0 : }
     124              : 
     125            0 : void McpServer::onToolsListRequest(const ToolsListHandler& onToolsList)
     126              : {
     127            0 :     m_onToolsList = onToolsList;
     128            0 : }
     129              : 
     130            0 : void McpServer::onToolsList(const JsonObject& request, const std::function<void(const JsonObject&)>& onResponse)
     131              : {
     132            0 :     if (!m_onToolsList) {
     133            0 :         replyError(request, -32603, "No tools list handler", onResponse);
     134            0 :         return;
     135              :     }
     136              : 
     137            0 :     const JsonValue id = request.value("id");
     138            0 :     m_onToolsList([id, onResponse](const std::vector<Tool>& tools) {
     139            0 :         JsonObject response;
     140            0 :         response["jsonrpc"] = "2.0";
     141            0 :         response["id"] = id;
     142              : 
     143            0 :         JsonArray arr;
     144            0 :         for (const Tool& tool : tools) {
     145            0 :             JsonObject obj;
     146            0 :             obj["name"] = tool.name;
     147            0 :             obj["title"] = tool.title;
     148            0 :             obj["description"] = tool.description;
     149              : 
     150              :             // input
     151              :             {
     152            0 :                 const InputSchema& schema = tool.inputSchema;
     153            0 :                 JsonObject schemaObj;
     154            0 :                 schemaObj["type"] = schema.type;
     155            0 :                 JsonObject properties;
     156            0 :                 for (const auto& [name, property] : schema.properties) {
     157            0 :                     JsonObject propertyObj;
     158            0 :                     propertyObj["type"] = to_string(property.type.type);
     159            0 :                     if (property.type.format != Format::Undefined) {
     160            0 :                         propertyObj["format"] = to_string(property.type.format);
     161              :                     }
     162            0 :                     propertyObj["description"] = property.description;
     163            0 :                     if (!property.minimum.isNull()) {
     164            0 :                         propertyObj["minimum"] = JsonValue::fromVal(property.minimum);
     165              :                     }
     166            0 :                     if (!property.maximum.isNull()) {
     167            0 :                         propertyObj["maximum"] = JsonValue::fromVal(property.maximum);
     168              :                     }
     169            0 :                     properties[name] = propertyObj;
     170            0 :                 }
     171            0 :                 schemaObj["properties"] = properties;
     172            0 :                 obj["inputSchema"] = schemaObj;
     173            0 :             }
     174              : 
     175              :             // output
     176              :             {
     177            0 :                 const OutputSchema& schema = tool.outputSchema;
     178            0 :                 JsonObject schemaObj;
     179            0 :                 schemaObj["type"] = schema.type;
     180            0 :                 JsonObject properties;
     181            0 :                 for (const auto& [name, property] : schema.properties) {
     182            0 :                     JsonObject propertyObj;
     183            0 :                     propertyObj["type"] = to_string(property.type.type);
     184            0 :                     if (property.type.format != Format::Undefined) {
     185            0 :                         propertyObj["format"] = to_string(property.type.format);
     186              :                     }
     187            0 :                     propertyObj["description"] = property.description;
     188            0 :                     properties[name] = propertyObj;
     189            0 :                 }
     190            0 :                 schemaObj["properties"] = properties;
     191            0 :                 obj["outputSchema"] = schemaObj;
     192            0 :             }
     193              : 
     194            0 :             arr << obj;
     195            0 :         }
     196              : 
     197            0 :         JsonObject result;
     198            0 :         result["tools"] = arr;
     199              : 
     200            0 :         response["result"] = result;
     201              : 
     202            0 :         onResponse(response);
     203            0 :     });
     204            0 : }
     205              : 
     206            0 : void McpServer::onToolsCallRequest(const ToolsCallHandler& onToolsCall)
     207              : {
     208            0 :     m_onToolsCall = onToolsCall;
     209            0 : }
     210              : 
     211            0 : void McpServer::onToolsCall(const JsonObject& request, const std::function<void(const JsonObject&)>& onResponse)
     212              : {
     213            0 :     if (!m_onToolsCall) {
     214            0 :         replyError(request, -32603, "No tools call handler", onResponse);
     215            0 :         return;
     216              :     }
     217              : 
     218            0 :     const JsonValue id = request.value("id");
     219            0 :     const JsonObject params = request.value("params").toObject();
     220            0 :     const std::string name = params.value("name").toString().toStdString();
     221            0 :     const JsonObject arguments = params.value("arguments").toObject();
     222              : 
     223            0 :     m_onToolsCall(name, toArgs(arguments), [id, onResponse](const ToolResult& toolResult) {
     224            0 :         JsonObject response;
     225            0 :         response["jsonrpc"] = "2.0";
     226            0 :         response["id"] = id;
     227              : 
     228            0 :         JsonObject resObj;
     229            0 :         JsonArray content;
     230            0 :         bool isError = false;
     231              : 
     232            0 :         if (toolResult.ret) {
     233            0 :             JsonObject structuredContent;
     234            0 :             for (const auto& [name, val] : toolResult.result) {
     235            0 :                 structuredContent[name] = JsonValue::fromVal(val);
     236              :             }
     237            0 :             resObj["structuredContent"] = structuredContent;
     238              : 
     239            0 :             JsonObject contentObj;
     240            0 :             contentObj["type"] = "text";
     241            0 :             contentObj["text"] = JsonDocument(structuredContent).toJson(JsonDocument::Format::Compact).constChar();
     242            0 :             content.append(contentObj);
     243            0 :         } else {
     244            0 :             isError = true;
     245            0 :             JsonObject errText;
     246            0 :             errText["type"] = "text";
     247            0 :             errText["text"] = toolResult.ret.toString();
     248            0 :             content.append(errText);
     249            0 :         }
     250              : 
     251            0 :         resObj["content"] = content;
     252            0 :         resObj["isError"] = isError;
     253              : 
     254            0 :         response["result"] = resObj;
     255            0 :         onResponse(response);
     256            0 :     });
     257            0 : }
     258              : 
     259            0 : void McpServer::onNotifications(const String& method)
     260              : {
     261            0 :     LOGI() << "Notification: " << method;
     262            0 : }
     263              : 
     264            0 : void McpServer::replyError(const JsonObject& request, int code, const std::string& message,
     265              :                            const std::function<void(const JsonObject&)>& onResponse)
     266              : {
     267            0 :     JsonObject error;
     268            0 :     error["code"] = code;
     269            0 :     error["message"] = message;
     270              : 
     271            0 :     JsonObject response;
     272            0 :     response["jsonrpc"] = "2.0";
     273            0 :     response["id"] = request.value("id");
     274            0 :     response["error"] = error;
     275              : 
     276            0 :     onResponse(response);
     277            0 : }
        

Generated by: LCOV version 2.0-1