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 "mcpcontroller.h"
21 :
22 : #include "mcpserver.h"
23 :
24 : #include "log.h"
25 :
26 : using namespace muse::rcontrol::mcp;
27 : using namespace muse::rcommand;
28 :
29 0 : static TypeSchema toMcpTypeSchema(muse::rcommand::DataType type)
30 : {
31 : using CT = muse::rcommand::DataType;
32 : using MT = muse::rcontrol::mcp::DataType;
33 : using MF = muse::rcontrol::mcp::Format;
34 :
35 0 : switch (type) {
36 0 : case CT::Undefined: return TypeSchema{ MT::Undefined, MF::Undefined };
37 0 : case CT::String: return TypeSchema{ MT::String, MF::Undefined };
38 0 : case CT::Int: return TypeSchema{ MT::Integer, MF::Undefined };
39 0 : case CT::Int64: return TypeSchema{ MT::String, MF::Int64 };
40 0 : case CT::Float: return TypeSchema{ MT::Number, MF::Float };
41 0 : case CT::Double: return TypeSchema{ MT::Number, MF::Double };
42 0 : case CT::Bool: return TypeSchema{ MT::Boolean, MF::Undefined };
43 0 : case CT::List: return TypeSchema{ MT::Array, MF::Undefined };
44 0 : case CT::Map: return TypeSchema{ MT::Object, MF::Undefined };
45 : }
46 0 : return TypeSchema{ MT::Undefined, MF::Undefined };
47 : }
48 :
49 0 : McpController::McpController(const modularity::ContextPtr& iocCtx)
50 0 : : Contextable(iocCtx)
51 : {
52 0 : }
53 :
54 0 : McpController::~McpController()
55 : {
56 0 : deinit();
57 0 : }
58 :
59 : //! NOTE The tool name must be in the format: group_name
60 0 : static std::string commandToToolName(const Command& command)
61 : {
62 0 : std::string path = command.path();
63 0 : std::replace(path.begin(), path.end(), '/', '.');
64 0 : return path;
65 : }
66 :
67 0 : static Command toolNameToCommand(const std::string& name)
68 : {
69 0 : std::string path = name;
70 0 : std::replace(path.begin(), path.end(), '.', '/');
71 0 : return Command(std::string(COMMAND_SCHEME), path);
72 0 : }
73 :
74 0 : void McpController::init()
75 : {
76 0 : m_mcpServer = std::make_unique<McpServer>(application()->version().toStdString());
77 :
78 0 : m_mcpServer->onToolsListRequest([this](const McpServer::ToolsListResultHandler& onResult) {
79 0 : std::vector<Tool> tools = makeToolsList();
80 0 : onResult(tools);
81 0 : });
82 :
83 0 : m_mcpServer->onToolsCallRequest([this](const std::string& name,
84 : const std::map<std::string, Val>& args,
85 : const McpServer::ToolsCallResultHandler& onResult)
86 : {
87 0 : LOGDA() << "Tools call: " << name;
88 :
89 0 : async::Promise<Response> promise = commandsDispatcher()->dispatch(toolNameToCommand(name), args);
90 0 : promise.onResolve(this, [onResult](const Response& response) {
91 0 : ToolResult result;
92 0 : result.ret = response.ret;
93 0 : result.result = response.result;
94 0 : onResult(result);
95 0 : });
96 0 : });
97 :
98 0 : m_mcpServer->init();
99 0 : }
100 :
101 0 : void McpController::deinit()
102 : {
103 0 : if (m_mcpServer) {
104 0 : m_mcpServer->deinit();
105 0 : m_mcpServer = nullptr;
106 : }
107 0 : }
108 :
109 0 : std::vector<Tool> McpController::makeToolsList() const
110 : {
111 0 : std::vector<Tool> tools;
112 0 : auto commandList = commandsRegister()->commandInfoList();
113 0 : tools.reserve(commandList.size());
114 0 : for (const auto& info : commandList) {
115 0 : if (!info.availabilities.testFlag(Availability::MCP)) {
116 0 : continue;
117 : }
118 0 : Tool tool;
119 0 : tool.name = commandToToolName(info.command);
120 0 : tool.title = info.title.raw().translated().toStdString();
121 0 : tool.description = info.description.translated().toStdString();
122 0 : for (const auto& [name, arg] : info.inputSchema.args) {
123 0 : Property property;
124 0 : property.type = toMcpTypeSchema(arg.type);
125 0 : property.description = arg.description.toStdString();
126 0 : property.minimum = arg.min;
127 0 : property.maximum = arg.max;
128 0 : tool.inputSchema.properties[name] = property;
129 0 : }
130 :
131 0 : for (const auto& [name, arg] : info.outputSchema.args) {
132 0 : Property property;
133 0 : property.type = toMcpTypeSchema(arg.type);
134 0 : property.description = arg.description.toStdString();
135 0 : tool.outputSchema.properties[name] = property;
136 0 : }
137 0 : tools.push_back(std::move(tool));
138 0 : }
139 0 : return tools;
140 0 : }
|