1//===- ProtocolServerMCP.cpp ----------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "ProtocolServerMCP.h"
10#include "MCPError.h"
11#include "lldb/Core/PluginManager.h"
12#include "lldb/Utility/LLDBLog.h"
13#include "lldb/Utility/Log.h"
14#include "llvm/ADT/StringExtras.h"
15#include "llvm/Support/Threading.h"
16#include <thread>
17#include <variant>
18
19using namespace lldb_private;
20using namespace lldb_private::mcp;
21using namespace llvm;
22
23LLDB_PLUGIN_DEFINE(ProtocolServerMCP)
24
25static constexpr size_t kChunkSize = 1024;
26
27ProtocolServerMCP::ProtocolServerMCP() : ProtocolServer() {
28 AddRequestHandler(method: "initialize",
29 handler: std::bind(f: &ProtocolServerMCP::InitializeHandler, args: this,
30 args: std::placeholders::_1));
31
32 AddRequestHandler(method: "tools/list",
33 handler: std::bind(f: &ProtocolServerMCP::ToolsListHandler, args: this,
34 args: std::placeholders::_1));
35 AddRequestHandler(method: "tools/call",
36 handler: std::bind(f: &ProtocolServerMCP::ToolsCallHandler, args: this,
37 args: std::placeholders::_1));
38
39 AddRequestHandler(method: "resources/list",
40 handler: std::bind(f: &ProtocolServerMCP::ResourcesListHandler, args: this,
41 args: std::placeholders::_1));
42 AddRequestHandler(method: "resources/read",
43 handler: std::bind(f: &ProtocolServerMCP::ResourcesReadHandler, args: this,
44 args: std::placeholders::_1));
45 AddNotificationHandler(
46 method: "notifications/initialized", handler: [](const protocol::Notification &) {
47 LLDB_LOG(GetLog(LLDBLog::Host), "MCP initialization complete");
48 });
49
50 AddTool(
51 tool: std::make_unique<CommandTool>(args: "lldb_command", args: "Run an lldb command."));
52
53 AddResourceProvider(resource_provider: std::make_unique<DebuggerResourceProvider>());
54}
55
56ProtocolServerMCP::~ProtocolServerMCP() { llvm::consumeError(Err: Stop()); }
57
58void ProtocolServerMCP::Initialize() {
59 PluginManager::RegisterPlugin(name: GetPluginNameStatic(),
60 description: GetPluginDescriptionStatic(), create_callback: CreateInstance);
61}
62
63void ProtocolServerMCP::Terminate() {
64 PluginManager::UnregisterPlugin(create_callback: CreateInstance);
65}
66
67lldb::ProtocolServerUP ProtocolServerMCP::CreateInstance() {
68 return std::make_unique<ProtocolServerMCP>();
69}
70
71llvm::StringRef ProtocolServerMCP::GetPluginDescriptionStatic() {
72 return "MCP Server.";
73}
74
75llvm::Expected<protocol::Response>
76ProtocolServerMCP::Handle(protocol::Request request) {
77 auto it = m_request_handlers.find(Key: request.method);
78 if (it != m_request_handlers.end()) {
79 llvm::Expected<protocol::Response> response = it->second(request);
80 if (!response)
81 return response;
82 response->id = request.id;
83 return *response;
84 }
85
86 return make_error<MCPError>(
87 Args: llvm::formatv(Fmt: "no handler for request: {0}", Vals&: request.method).str());
88}
89
90void ProtocolServerMCP::Handle(protocol::Notification notification) {
91 auto it = m_notification_handlers.find(Key: notification.method);
92 if (it != m_notification_handlers.end()) {
93 it->second(notification);
94 return;
95 }
96
97 LLDB_LOG(GetLog(LLDBLog::Host), "MPC notification: {0} ({1})",
98 notification.method, notification.params);
99}
100
101void ProtocolServerMCP::AcceptCallback(std::unique_ptr<Socket> socket) {
102 LLDB_LOG(GetLog(LLDBLog::Host), "New MCP client ({0}) connected",
103 m_clients.size() + 1);
104
105 lldb::IOObjectSP io_sp = std::move(socket);
106 auto client_up = std::make_unique<Client>();
107 client_up->io_sp = io_sp;
108 Client *client = client_up.get();
109
110 Status status;
111 auto read_handle_up = m_loop.RegisterReadObject(
112 object_sp: io_sp,
113 callback: [this, client](MainLoopBase &loop) {
114 if (Error error = ReadCallback(client&: *client)) {
115 LLDB_LOG_ERROR(GetLog(LLDBLog::Host), std::move(error), "{0}");
116 client->read_handle_up.reset();
117 }
118 },
119 error&: status);
120 if (status.Fail())
121 return;
122
123 client_up->read_handle_up = std::move(read_handle_up);
124 m_clients.emplace_back(args: std::move(client_up));
125}
126
127llvm::Error ProtocolServerMCP::ReadCallback(Client &client) {
128 char chunk[kChunkSize];
129 size_t bytes_read = sizeof(chunk);
130 if (Status status = client.io_sp->Read(buf: chunk, num_bytes&: bytes_read); status.Fail())
131 return status.takeError();
132 client.buffer.append(s: chunk, n: bytes_read);
133
134 for (std::string::size_type pos;
135 (pos = client.buffer.find(c: '\n')) != std::string::npos;) {
136 llvm::Expected<std::optional<protocol::Message>> message =
137 HandleData(data: StringRef(client.buffer.data(), pos));
138 client.buffer = client.buffer.erase(pos: 0, n: pos + 1);
139 if (!message)
140 return message.takeError();
141
142 if (*message) {
143 std::string Output;
144 llvm::raw_string_ostream OS(Output);
145 OS << llvm::formatv(Fmt: "{0}", Vals: toJSON(**message)) << '\n';
146 size_t num_bytes = Output.size();
147 return client.io_sp->Write(buf: Output.data(), num_bytes).takeError();
148 }
149 }
150
151 return llvm::Error::success();
152}
153
154llvm::Error ProtocolServerMCP::Start(ProtocolServer::Connection connection) {
155 std::lock_guard<std::mutex> guard(m_server_mutex);
156
157 if (m_running)
158 return llvm::createStringError(Fmt: "the MCP server is already running");
159
160 Status status;
161 m_listener = Socket::Create(protocol: connection.protocol, error&: status);
162 if (status.Fail())
163 return status.takeError();
164
165 status = m_listener->Listen(name: connection.name, /*backlog=*/5);
166 if (status.Fail())
167 return status.takeError();
168
169 auto handles =
170 m_listener->Accept(loop&: m_loop, sock_cb: std::bind(f: &ProtocolServerMCP::AcceptCallback,
171 args: this, args: std::placeholders::_1));
172 if (llvm::Error error = handles.takeError())
173 return error;
174
175 m_running = true;
176 m_listen_handlers = std::move(*handles);
177 m_loop_thread = std::thread([=] {
178 llvm::set_thread_name("protocol-server.mcp");
179 m_loop.Run();
180 });
181
182 return llvm::Error::success();
183}
184
185llvm::Error ProtocolServerMCP::Stop() {
186 {
187 std::lock_guard<std::mutex> guard(m_server_mutex);
188 if (!m_running)
189 return createStringError(Fmt: "the MCP sever is not running");
190 m_running = false;
191 }
192
193 // Stop the main loop.
194 m_loop.AddPendingCallback(
195 callback: [](MainLoopBase &loop) { loop.RequestTermination(); });
196
197 // Wait for the main loop to exit.
198 if (m_loop_thread.joinable())
199 m_loop_thread.join();
200
201 {
202 std::lock_guard<std::mutex> guard(m_server_mutex);
203 m_listener.reset();
204 m_listen_handlers.clear();
205 m_clients.clear();
206 }
207
208 return llvm::Error::success();
209}
210
211llvm::Expected<std::optional<protocol::Message>>
212ProtocolServerMCP::HandleData(llvm::StringRef data) {
213 auto message = llvm::json::parse<protocol::Message>(/*JSON=*/data);
214 if (!message)
215 return message.takeError();
216
217 if (const protocol::Request *request =
218 std::get_if<protocol::Request>(ptr: &(*message))) {
219 llvm::Expected<protocol::Response> response = Handle(request: *request);
220
221 // Handle failures by converting them into an Error message.
222 if (!response) {
223 protocol::Error protocol_error;
224 llvm::handleAllErrors(
225 E: response.takeError(),
226 Handlers: [&](const MCPError &err) { protocol_error = err.toProtcolError(); },
227 Handlers: [&](const llvm::ErrorInfoBase &err) {
228 protocol_error.error.code = MCPError::kInternalError;
229 protocol_error.error.message = err.message();
230 });
231 protocol_error.id = request->id;
232 return protocol_error;
233 }
234
235 return *response;
236 }
237
238 if (const protocol::Notification *notification =
239 std::get_if<protocol::Notification>(ptr: &(*message))) {
240 Handle(notification: *notification);
241 return std::nullopt;
242 }
243
244 if (std::get_if<protocol::Error>(ptr: &(*message)))
245 return llvm::createStringError(Fmt: "unexpected MCP message: error");
246
247 if (std::get_if<protocol::Response>(ptr: &(*message)))
248 return llvm::createStringError(Fmt: "unexpected MCP message: response");
249
250 llvm_unreachable("all message types handled");
251}
252
253protocol::Capabilities ProtocolServerMCP::GetCapabilities() {
254 protocol::Capabilities capabilities;
255 capabilities.tools.listChanged = true;
256 // FIXME: Support sending notifications when a debugger/target are
257 // added/removed.
258 capabilities.resources.listChanged = false;
259 return capabilities;
260}
261
262void ProtocolServerMCP::AddTool(std::unique_ptr<Tool> tool) {
263 std::lock_guard<std::mutex> guard(m_server_mutex);
264
265 if (!tool)
266 return;
267 m_tools[tool->GetName()] = std::move(tool);
268}
269
270void ProtocolServerMCP::AddResourceProvider(
271 std::unique_ptr<ResourceProvider> resource_provider) {
272 std::lock_guard<std::mutex> guard(m_server_mutex);
273
274 if (!resource_provider)
275 return;
276 m_resource_providers.push_back(x: std::move(resource_provider));
277}
278
279void ProtocolServerMCP::AddRequestHandler(llvm::StringRef method,
280 RequestHandler handler) {
281 std::lock_guard<std::mutex> guard(m_server_mutex);
282 m_request_handlers[method] = std::move(handler);
283}
284
285void ProtocolServerMCP::AddNotificationHandler(llvm::StringRef method,
286 NotificationHandler handler) {
287 std::lock_guard<std::mutex> guard(m_server_mutex);
288 m_notification_handlers[method] = std::move(handler);
289}
290
291llvm::Expected<protocol::Response>
292ProtocolServerMCP::InitializeHandler(const protocol::Request &request) {
293 protocol::Response response;
294 response.result.emplace(args: llvm::json::Object{
295 {.K: "protocolVersion", .V: protocol::kVersion},
296 {.K: "capabilities", .V: GetCapabilities()},
297 {.K: "serverInfo",
298 .V: llvm::json::Object{{.K: "name", .V: kName}, {.K: "version", .V: kVersion}}}});
299 return response;
300}
301
302llvm::Expected<protocol::Response>
303ProtocolServerMCP::ToolsListHandler(const protocol::Request &request) {
304 protocol::Response response;
305
306 llvm::json::Array tools;
307 for (const auto &tool : m_tools)
308 tools.emplace_back(A: toJSON(tool.second->GetDefinition()));
309
310 response.result.emplace(args: llvm::json::Object{{.K: "tools", .V: std::move(tools)}});
311
312 return response;
313}
314
315llvm::Expected<protocol::Response>
316ProtocolServerMCP::ToolsCallHandler(const protocol::Request &request) {
317 protocol::Response response;
318
319 if (!request.params)
320 return llvm::createStringError(Fmt: "no tool parameters");
321
322 const json::Object *param_obj = request.params->getAsObject();
323 if (!param_obj)
324 return llvm::createStringError(Fmt: "no tool parameters");
325
326 const json::Value *name = param_obj->get(K: "name");
327 if (!name)
328 return llvm::createStringError(Fmt: "no tool name");
329
330 llvm::StringRef tool_name = name->getAsString().value_or(u: "");
331 if (tool_name.empty())
332 return llvm::createStringError(Fmt: "no tool name");
333
334 auto it = m_tools.find(Key: tool_name);
335 if (it == m_tools.end())
336 return llvm::createStringError(S: llvm::formatv(Fmt: "no tool \"{0}\"", Vals&: tool_name));
337
338 protocol::ToolArguments tool_args;
339 if (const json::Value *args = param_obj->get(K: "arguments"))
340 tool_args = *args;
341
342 llvm::Expected<protocol::TextResult> text_result =
343 it->second->Call(args: tool_args);
344 if (!text_result)
345 return text_result.takeError();
346
347 response.result.emplace(args: toJSON(*text_result));
348
349 return response;
350}
351
352llvm::Expected<protocol::Response>
353ProtocolServerMCP::ResourcesListHandler(const protocol::Request &request) {
354 protocol::Response response;
355
356 llvm::json::Array resources;
357
358 std::lock_guard<std::mutex> guard(m_server_mutex);
359 for (std::unique_ptr<ResourceProvider> &resource_provider_up :
360 m_resource_providers) {
361 for (const protocol::Resource &resource :
362 resource_provider_up->GetResources())
363 resources.push_back(E: resource);
364 }
365 response.result.emplace(
366 args: llvm::json::Object{{.K: "resources", .V: std::move(resources)}});
367
368 return response;
369}
370
371llvm::Expected<protocol::Response>
372ProtocolServerMCP::ResourcesReadHandler(const protocol::Request &request) {
373 protocol::Response response;
374
375 if (!request.params)
376 return llvm::createStringError(Fmt: "no resource parameters");
377
378 const json::Object *param_obj = request.params->getAsObject();
379 if (!param_obj)
380 return llvm::createStringError(Fmt: "no resource parameters");
381
382 const json::Value *uri = param_obj->get(K: "uri");
383 if (!uri)
384 return llvm::createStringError(Fmt: "no resource uri");
385
386 llvm::StringRef uri_str = uri->getAsString().value_or(u: "");
387 if (uri_str.empty())
388 return llvm::createStringError(Fmt: "no resource uri");
389
390 std::lock_guard<std::mutex> guard(m_server_mutex);
391 for (std::unique_ptr<ResourceProvider> &resource_provider_up :
392 m_resource_providers) {
393 llvm::Expected<protocol::ResourceResult> result =
394 resource_provider_up->ReadResource(uri: uri_str);
395 if (result.errorIsA<UnsupportedURI>()) {
396 llvm::consumeError(Err: result.takeError());
397 continue;
398 }
399 if (!result)
400 return result.takeError();
401
402 protocol::Response response;
403 response.result.emplace(args: std::move(*result));
404 return response;
405 }
406
407 return make_error<MCPError>(
408 Args: llvm::formatv(Fmt: "no resource handler for uri: {0}", Vals&: uri_str).str(),
409 Args: MCPError::kResourceNotFound);
410}
411

source code of lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp