RAM2118 commited on
Commit
862564a
·
verified ·
1 Parent(s): db82307

Sprint 2: Update McpServer.cpp \u2014 wire all 5 MCP tools to HostedPluginManager

Browse files
Files changed (1) hide show
  1. Source/McpServer.cpp +129 -70
Source/McpServer.cpp CHANGED
@@ -1,14 +1,17 @@
1
  #include "McpServer.h"
 
2
 
3
- // httplib must be included in exactly ONE .cpp file
4
  #define CPPHTTPLIB_THREAD_POOL_COUNT 2
5
  #include "httplib.h"
6
  #include "json.hpp"
7
 
8
  using json = nlohmann::json;
9
 
10
- McpServer::McpServer(int port)
11
- : server(std::make_unique<httplib::Server>()), port(port)
 
 
12
  {
13
  setupRoutes();
14
  }
@@ -20,7 +23,7 @@ McpServer::~McpServer()
20
 
21
  void McpServer::setupRoutes()
22
  {
23
- // Single MCP endpoint — all JSON-RPC goes through here
24
  server->Post("/mcp", [this](const httplib::Request& req, httplib::Response& res) {
25
  res.set_header("Access-Control-Allow-Origin", "http://localhost");
26
 
@@ -33,13 +36,12 @@ void McpServer::setupRoutes()
33
  responseStr = handleInitialize(req.body);
34
  }
35
  else if (method == "notifications/initialized") {
36
- // Notification — no response needed
37
  initialized = true;
38
  res.status = 204;
39
  return;
40
  }
41
  else if (method == "tools/list") {
42
- responseStr = handleToolsList();
43
  }
44
  else if (method == "tools/call") {
45
  responseStr = handleToolsCall(req.body);
@@ -53,11 +55,10 @@ void McpServer::setupRoutes()
53
  responseStr = errResponse.dump();
54
  }
55
 
56
- if (!responseStr.empty()) {
57
  res.set_content(responseStr, "application/json");
58
- }
59
  }
60
- catch (const json::exception& e) {
61
  json errResponse = {
62
  {"jsonrpc", "2.0"},
63
  {"id", nullptr},
@@ -67,9 +68,15 @@ void McpServer::setupRoutes()
67
  }
68
  });
69
 
70
- // Health check — quick way to verify server is alive
71
  server->Get("/health", [this](const httplib::Request&, httplib::Response& res) {
72
- json health = {{"status", "ok"}, {"port", port}, {"initialized", initialized}};
 
 
 
 
 
 
73
  res.set_content(health.dump(), "application/json");
74
  });
75
 
@@ -92,18 +99,15 @@ void McpServer::start()
92
  running.store(false);
93
  });
94
 
95
- // Wait until server is actually ready
96
  server->wait_until_ready();
97
  }
98
 
99
  void McpServer::stop()
100
  {
101
- if (server && server->is_running()) {
102
  server->stop();
103
- }
104
- if (serverThread.joinable()) {
105
  serverThread.join();
106
- }
107
  running.store(false);
108
  }
109
 
@@ -112,18 +116,11 @@ bool McpServer::isRunning() const
112
  return running.load();
113
  }
114
 
115
- void McpServer::serverThreadFunc()
116
- {
117
- server->listen("127.0.0.1", port);
118
- }
119
-
120
  // --- MCP Protocol Handlers ---
121
 
122
  std::string McpServer::handleInitialize(const std::string& requestBody)
123
  {
124
  auto body = json::parse(requestBody);
125
-
126
- // Generate session ID
127
  sessionId = "pb-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
128
 
129
  json response = {
@@ -131,39 +128,35 @@ std::string McpServer::handleInitialize(const std::string& requestBody)
131
  {"id", body.value("id", json(nullptr))},
132
  {"result", {
133
  {"protocolVersion", "2025-06-18"},
134
- {"capabilities", {
135
- {"tools", {{"listChanged", false}}}
136
- }},
137
- {"serverInfo", {
138
- {"name", "PluginBridge"},
139
- {"version", "0.1.0"}
140
- }}
141
  }}
142
  };
143
-
144
  return response.dump();
145
  }
146
 
147
- std::string McpServer::handleToolsList()
148
  {
 
 
149
  json response = {
150
  {"jsonrpc", "2.0"},
151
- {"id", 1},
152
  {"result", {
153
  {"tools", json::array({
154
  {
155
  {"name", "list_plugins"},
156
- {"description", "List all loaded plugin instances"},
157
  {"inputSchema", {{"type", "object"}, {"properties", json::object()}}}
158
  },
159
  {
160
  {"name", "search_param"},
161
- {"description", "Search plugin parameters by keyword"},
162
  {"inputSchema", {
163
  {"type", "object"},
164
  {"properties", {
165
  {"plugin", {{"type", "string"}, {"description", "Plugin name"}}},
166
- {"keyword", {{"type", "string"}, {"description", "Search term"}}}
167
  }},
168
  {"required", json::array({"plugin", "keyword"})}
169
  }}
@@ -182,79 +175,145 @@ std::string McpServer::handleToolsList()
182
  },
183
  {
184
  {"name", "set_params"},
185
- {"description", "Set parameter values by ID (batch)"},
186
  {"inputSchema", {
187
  {"type", "object"},
188
  {"properties", {
189
  {"plugin", {{"type", "string"}}},
190
- {"values", {{"type", "object"}, {"description", "param_id → value (0.0-1.0)"}}}
191
  }},
192
  {"required", json::array({"plugin", "values"})}
193
  }}
194
  },
195
  {
196
  {"name", "get_analysis"},
197
- {"description", "Get real-time audio analysis (LUFS, bands, stereo)"},
198
  {"inputSchema", {{"type", "object"}, {"properties", json::object()}}}
199
  }
200
  })}
201
  }}
202
  };
203
-
204
  return response.dump();
205
  }
206
 
207
  std::string McpServer::handleToolsCall(const std::string& requestBody)
208
  {
209
  auto body = json::parse(requestBody);
210
- std::string toolName = body["params"]["name"];
 
 
211
 
212
- json result;
 
213
 
214
  if (toolName == "list_plugins") {
215
- // Sprint 1: no plugins loaded yet
216
- result = {
217
- {"content", json::array({{{"type", "text"}, {"text", "[]"}}})},
218
- {"isError", false}
219
- };
220
  }
221
  else if (toolName == "search_param") {
222
- result = {
223
- {"content", json::array({{{"type", "text"}, {"text", "no plugin loaded"}}})},
224
- {"isError", true}
225
- };
226
  }
227
  else if (toolName == "get_params") {
228
- result = {
229
- {"content", json::array({{{"type", "text"}, {"text", "no plugin loaded"}}})},
230
- {"isError", true}
231
- };
232
  }
233
  else if (toolName == "set_params") {
234
- result = {
235
- {"content", json::array({{{"type", "text"}, {"text", "no plugin loaded"}}})},
236
- {"isError", true}
237
- };
238
  }
239
  else if (toolName == "get_analysis") {
240
- // Sprint 1: stub response
241
- result = {
242
- {"content", json::array({{{"type", "text"}, {"text", "silent"}}})},
243
- {"isError", false}
244
- };
245
  }
246
  else {
247
- result = {
248
- {"content", json::array({{{"type", "text"}, {"text", "unknown tool: " + toolName}}})},
249
- {"isError", true}
250
- };
251
  }
252
 
253
  json response = {
254
  {"jsonrpc", "2.0"},
255
  {"id", body.value("id", json(nullptr))},
256
- {"result", result}
 
 
 
257
  };
258
-
259
  return response.dump();
260
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  #include "McpServer.h"
2
+ #include "HostedPluginManager.h"
3
 
4
+ // httplib included in exactly ONE .cpp
5
  #define CPPHTTPLIB_THREAD_POOL_COUNT 2
6
  #include "httplib.h"
7
  #include "json.hpp"
8
 
9
  using json = nlohmann::json;
10
 
11
+ McpServer::McpServer(int port, HostedPluginManager& pluginManager)
12
+ : server(std::make_unique<httplib::Server>()),
13
+ port(port),
14
+ pluginManager(pluginManager)
15
  {
16
  setupRoutes();
17
  }
 
23
 
24
  void McpServer::setupRoutes()
25
  {
26
+ // Single MCP endpoint
27
  server->Post("/mcp", [this](const httplib::Request& req, httplib::Response& res) {
28
  res.set_header("Access-Control-Allow-Origin", "http://localhost");
29
 
 
36
  responseStr = handleInitialize(req.body);
37
  }
38
  else if (method == "notifications/initialized") {
 
39
  initialized = true;
40
  res.status = 204;
41
  return;
42
  }
43
  else if (method == "tools/list") {
44
+ responseStr = handleToolsList(req.body);
45
  }
46
  else if (method == "tools/call") {
47
  responseStr = handleToolsCall(req.body);
 
55
  responseStr = errResponse.dump();
56
  }
57
 
58
+ if (!responseStr.empty())
59
  res.set_content(responseStr, "application/json");
 
60
  }
61
+ catch (const json::exception&) {
62
  json errResponse = {
63
  {"jsonrpc", "2.0"},
64
  {"id", nullptr},
 
68
  }
69
  });
70
 
71
+ // Health check
72
  server->Get("/health", [this](const httplib::Request&, httplib::Response& res) {
73
+ json health = {
74
+ {"status", "ok"},
75
+ {"port", port},
76
+ {"initialized", initialized},
77
+ {"plugin", pluginManager.isLoaded() ? pluginManager.getPluginName().toStdString() : "none"},
78
+ {"params", pluginManager.getParameterCount()}
79
+ };
80
  res.set_content(health.dump(), "application/json");
81
  });
82
 
 
99
  running.store(false);
100
  });
101
 
 
102
  server->wait_until_ready();
103
  }
104
 
105
  void McpServer::stop()
106
  {
107
+ if (server && server->is_running())
108
  server->stop();
109
+ if (serverThread.joinable())
 
110
  serverThread.join();
 
111
  running.store(false);
112
  }
113
 
 
116
  return running.load();
117
  }
118
 
 
 
 
 
 
119
  // --- MCP Protocol Handlers ---
120
 
121
  std::string McpServer::handleInitialize(const std::string& requestBody)
122
  {
123
  auto body = json::parse(requestBody);
 
 
124
  sessionId = "pb-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
125
 
126
  json response = {
 
128
  {"id", body.value("id", json(nullptr))},
129
  {"result", {
130
  {"protocolVersion", "2025-06-18"},
131
+ {"capabilities", {{"tools", {{"listChanged", false}}}}},
132
+ {"serverInfo", {{"name", "PluginBridge"}, {"version", "0.2.0"}}}
 
 
 
 
 
133
  }}
134
  };
 
135
  return response.dump();
136
  }
137
 
138
+ std::string McpServer::handleToolsList(const std::string& requestBody)
139
  {
140
+ auto body = json::parse(requestBody);
141
+
142
  json response = {
143
  {"jsonrpc", "2.0"},
144
+ {"id", body.value("id", json(nullptr))},
145
  {"result", {
146
  {"tools", json::array({
147
  {
148
  {"name", "list_plugins"},
149
+ {"description", "List loaded plugin instances"},
150
  {"inputSchema", {{"type", "object"}, {"properties", json::object()}}}
151
  },
152
  {
153
  {"name", "search_param"},
154
+ {"description", "Search plugin parameters by keyword. Returns matching param IDs and values."},
155
  {"inputSchema", {
156
  {"type", "object"},
157
  {"properties", {
158
  {"plugin", {{"type", "string"}, {"description", "Plugin name"}}},
159
+ {"keyword", {{"type", "string"}, {"description", "Search term (e.g. 'band 1', 'gain', 'cutoff')"}}}
160
  }},
161
  {"required", json::array({"plugin", "keyword"})}
162
  }}
 
175
  },
176
  {
177
  {"name", "set_params"},
178
+ {"description", "Set parameter values by ID (batch). Values are 0.0-1.0 normalized."},
179
  {"inputSchema", {
180
  {"type", "object"},
181
  {"properties", {
182
  {"plugin", {{"type", "string"}}},
183
+ {"values", {{"type", "object"}, {"description", "Map of param_index (string) to value (0.0-1.0)"}}}
184
  }},
185
  {"required", json::array({"plugin", "values"})}
186
  }}
187
  },
188
  {
189
  {"name", "get_analysis"},
190
+ {"description", "Get real-time audio analysis (LUFS, frequency bands, stereo width)"},
191
  {"inputSchema", {{"type", "object"}, {"properties", json::object()}}}
192
  }
193
  })}
194
  }}
195
  };
 
196
  return response.dump();
197
  }
198
 
199
  std::string McpServer::handleToolsCall(const std::string& requestBody)
200
  {
201
  auto body = json::parse(requestBody);
202
+ auto params = body["params"];
203
+ std::string toolName = params["name"];
204
+ auto args = params.value("arguments", json::object());
205
 
206
+ std::string resultText;
207
+ bool isError = false;
208
 
209
  if (toolName == "list_plugins") {
210
+ resultText = toolListPlugins();
 
 
 
 
211
  }
212
  else if (toolName == "search_param") {
213
+ resultText = toolSearchParam(
214
+ args.value("plugin", ""),
215
+ args.value("keyword", ""));
 
216
  }
217
  else if (toolName == "get_params") {
218
+ resultText = toolGetParams(
219
+ args.value("plugin", ""),
220
+ args.value("ids", json::array()).dump());
 
221
  }
222
  else if (toolName == "set_params") {
223
+ resultText = toolSetParams(
224
+ args.value("plugin", ""),
225
+ args.value("values", json::object()).dump());
 
226
  }
227
  else if (toolName == "get_analysis") {
228
+ resultText = toolGetAnalysis();
 
 
 
 
229
  }
230
  else {
231
+ resultText = "unknown tool: " + toolName;
232
+ isError = true;
 
 
233
  }
234
 
235
  json response = {
236
  {"jsonrpc", "2.0"},
237
  {"id", body.value("id", json(nullptr))},
238
+ {"result", {
239
+ {"content", json::array({{{"type", "text"}, {"text", resultText}}})},
240
+ {"isError", isError}
241
+ }}
242
  };
 
243
  return response.dump();
244
  }
245
+
246
+ // --- Tool Implementations ---
247
+
248
+ std::string McpServer::toolListPlugins()
249
+ {
250
+ if (!pluginManager.isLoaded())
251
+ return "[]";
252
+
253
+ json result = json::array({pluginManager.getPluginName().toStdString()});
254
+ return result.dump();
255
+ }
256
+
257
+ std::string McpServer::toolSearchParam(const std::string& /*plugin*/, const std::string& keyword)
258
+ {
259
+ if (!pluginManager.isLoaded())
260
+ return "no plugin loaded";
261
+
262
+ auto results = pluginManager.searchParams(juce::String(keyword));
263
+
264
+ if (results.empty())
265
+ return "no matches for '" + keyword + "'";
266
+
267
+ // Compact output: [{id, name, value}]
268
+ json output = json::array();
269
+ for (auto& p : results)
270
+ {
271
+ output.push_back({
272
+ {"i", p.index},
273
+ {"n", p.name.toStdString()},
274
+ {"v", std::round(p.value * 1000.0f) / 1000.0f},
275
+ {"t", p.displayText.toStdString()}
276
+ });
277
+ }
278
+ return output.dump();
279
+ }
280
+
281
+ std::string McpServer::toolGetParams(const std::string& /*plugin*/, const std::string& idsJson)
282
+ {
283
+ if (!pluginManager.isLoaded())
284
+ return "no plugin loaded";
285
+
286
+ auto idsArray = json::parse(idsJson);
287
+ std::vector<int> ids;
288
+ for (auto& id : idsArray)
289
+ ids.push_back(id.get<int>());
290
+
291
+ auto results = pluginManager.getParams(ids);
292
+
293
+ // Compact: {id: value, id: value}
294
+ json output = json::object();
295
+ for (auto& [idx, val] : results)
296
+ output[std::to_string(idx)] = std::round(val * 1000.0f) / 1000.0f;
297
+
298
+ return output.dump();
299
+ }
300
+
301
+ std::string McpServer::toolSetParams(const std::string& /*plugin*/, const std::string& valuesJson)
302
+ {
303
+ if (!pluginManager.isLoaded())
304
+ return "no plugin loaded";
305
+
306
+ auto valuesObj = json::parse(valuesJson);
307
+ std::map<int, float> values;
308
+ for (auto& [key, val] : valuesObj.items())
309
+ values[std::stoi(key)] = val.get<float>();
310
+
311
+ bool ok = pluginManager.setParams(values);
312
+ return ok ? "ok" : "error";
313
+ }
314
+
315
+ std::string McpServer::toolGetAnalysis()
316
+ {
317
+ // Sprint 4: will return real analysis
318
+ return "silent";
319
+ }