Skip to content

Commit 44aedac

Browse files
authored
[lldb] Move the generic MCP server code into Protocol/MCP (NFC) (#152396)
This is a continuation of #152188, which started splitting up the MCP implementation into a generic implementation in Protocol/MCP that will be shared between LLDB and lldb-mcp. For now I kept all the networking code in the MCP server plugin. Once the changes to JSONTransport land, we might be able to move more of it into the Protocol library.
1 parent d95433b commit 44aedac

File tree

6 files changed

+320
-296
lines changed

6 files changed

+320
-296
lines changed

lldb/include/lldb/Protocol/MCP/Protocol.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
namespace lldb_protocol::mcp {
2323

24-
static llvm::StringLiteral kVersion = "2024-11-05";
24+
static llvm::StringLiteral kProtocolVersion = "2024-11-05";
2525

2626
/// A request that expects a response.
2727
struct Request {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//===----------------------------------------------------------------------===//
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+
#ifndef LLDB_PROTOCOL_MCP_SERVER_H
10+
#define LLDB_PROTOCOL_MCP_SERVER_H
11+
12+
#include "lldb/Protocol/MCP/Protocol.h"
13+
#include "lldb/Protocol/MCP/Resource.h"
14+
#include "lldb/Protocol/MCP/Tool.h"
15+
#include "llvm/ADT/StringMap.h"
16+
#include "llvm/Support/Error.h"
17+
#include <mutex>
18+
19+
namespace lldb_protocol::mcp {
20+
21+
class Server {
22+
public:
23+
Server(std::string name, std::string version);
24+
virtual ~Server() = default;
25+
26+
void AddTool(std::unique_ptr<Tool> tool);
27+
void AddResourceProvider(std::unique_ptr<ResourceProvider> resource_provider);
28+
29+
protected:
30+
virtual Capabilities GetCapabilities() = 0;
31+
32+
using RequestHandler =
33+
std::function<llvm::Expected<Response>(const Request &)>;
34+
using NotificationHandler = std::function<void(const Notification &)>;
35+
36+
void AddRequestHandlers();
37+
38+
void AddRequestHandler(llvm::StringRef method, RequestHandler handler);
39+
void AddNotificationHandler(llvm::StringRef method,
40+
NotificationHandler handler);
41+
42+
llvm::Expected<std::optional<Message>> HandleData(llvm::StringRef data);
43+
44+
llvm::Expected<Response> Handle(Request request);
45+
void Handle(Notification notification);
46+
47+
llvm::Expected<Response> InitializeHandler(const Request &);
48+
49+
llvm::Expected<Response> ToolsListHandler(const Request &);
50+
llvm::Expected<Response> ToolsCallHandler(const Request &);
51+
52+
llvm::Expected<Response> ResourcesListHandler(const Request &);
53+
llvm::Expected<Response> ResourcesReadHandler(const Request &);
54+
55+
std::mutex m_mutex;
56+
57+
private:
58+
const std::string m_name;
59+
const std::string m_version;
60+
61+
llvm::StringMap<std::unique_ptr<Tool>> m_tools;
62+
std::vector<std::unique_ptr<ResourceProvider>> m_resource_providers;
63+
64+
llvm::StringMap<RequestHandler> m_request_handlers;
65+
llvm::StringMap<NotificationHandler> m_notification_handlers;
66+
};
67+
68+
} // namespace lldb_protocol::mcp
69+
70+
#endif

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

Lines changed: 8 additions & 244 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,12 @@ using namespace llvm;
2727
LLDB_PLUGIN_DEFINE(ProtocolServerMCP)
2828

2929
static constexpr size_t kChunkSize = 1024;
30+
static constexpr llvm::StringLiteral kName = "lldb-mcp";
31+
static constexpr llvm::StringLiteral kVersion = "0.1.0";
3032

31-
ProtocolServerMCP::ProtocolServerMCP() : ProtocolServer() {
32-
AddRequestHandler("initialize",
33-
std::bind(&ProtocolServerMCP::InitializeHandler, this,
34-
std::placeholders::_1));
35-
36-
AddRequestHandler("tools/list",
37-
std::bind(&ProtocolServerMCP::ToolsListHandler, this,
38-
std::placeholders::_1));
39-
AddRequestHandler("tools/call",
40-
std::bind(&ProtocolServerMCP::ToolsCallHandler, this,
41-
std::placeholders::_1));
42-
43-
AddRequestHandler("resources/list",
44-
std::bind(&ProtocolServerMCP::ResourcesListHandler, this,
45-
std::placeholders::_1));
46-
AddRequestHandler("resources/read",
47-
std::bind(&ProtocolServerMCP::ResourcesReadHandler, this,
48-
std::placeholders::_1));
33+
ProtocolServerMCP::ProtocolServerMCP()
34+
: ProtocolServer(),
35+
lldb_protocol::mcp::Server(std::string(kName), std::string(kVersion)) {
4936
AddNotificationHandler("notifications/initialized",
5037
[](const lldb_protocol::mcp::Notification &) {
5138
LLDB_LOG(GetLog(LLDBLog::Host),
@@ -77,32 +64,6 @@ llvm::StringRef ProtocolServerMCP::GetPluginDescriptionStatic() {
7764
return "MCP Server.";
7865
}
7966

80-
llvm::Expected<lldb_protocol::mcp::Response>
81-
ProtocolServerMCP::Handle(lldb_protocol::mcp::Request request) {
82-
auto it = m_request_handlers.find(request.method);
83-
if (it != m_request_handlers.end()) {
84-
llvm::Expected<lldb_protocol::mcp::Response> response = it->second(request);
85-
if (!response)
86-
return response;
87-
response->id = request.id;
88-
return *response;
89-
}
90-
91-
return make_error<MCPError>(
92-
llvm::formatv("no handler for request: {0}", request.method).str());
93-
}
94-
95-
void ProtocolServerMCP::Handle(lldb_protocol::mcp::Notification notification) {
96-
auto it = m_notification_handlers.find(notification.method);
97-
if (it != m_notification_handlers.end()) {
98-
it->second(notification);
99-
return;
100-
}
101-
102-
LLDB_LOG(GetLog(LLDBLog::Host), "MPC notification: {0} ({1})",
103-
notification.method, notification.params);
104-
}
105-
10667
void ProtocolServerMCP::AcceptCallback(std::unique_ptr<Socket> socket) {
10768
LLDB_LOG(GetLog(LLDBLog::Host), "New MCP client ({0}) connected",
10869
m_clients.size() + 1);
@@ -157,7 +118,7 @@ llvm::Error ProtocolServerMCP::ReadCallback(Client &client) {
157118
}
158119

159120
llvm::Error ProtocolServerMCP::Start(ProtocolServer::Connection connection) {
160-
std::lock_guard<std::mutex> guard(m_server_mutex);
121+
std::lock_guard<std::mutex> guard(m_mutex);
161122

162123
if (m_running)
163124
return llvm::createStringError("the MCP server is already running");
@@ -189,7 +150,7 @@ llvm::Error ProtocolServerMCP::Start(ProtocolServer::Connection connection) {
189150

190151
llvm::Error ProtocolServerMCP::Stop() {
191152
{
192-
std::lock_guard<std::mutex> guard(m_server_mutex);
153+
std::lock_guard<std::mutex> guard(m_mutex);
193154
if (!m_running)
194155
return createStringError("the MCP sever is not running");
195156
m_running = false;
@@ -204,7 +165,7 @@ llvm::Error ProtocolServerMCP::Stop() {
204165
m_loop_thread.join();
205166

206167
{
207-
std::lock_guard<std::mutex> guard(m_server_mutex);
168+
std::lock_guard<std::mutex> guard(m_mutex);
208169
m_listener.reset();
209170
m_listen_handlers.clear();
210171
m_clients.clear();
@@ -213,48 +174,6 @@ llvm::Error ProtocolServerMCP::Stop() {
213174
return llvm::Error::success();
214175
}
215176

216-
llvm::Expected<std::optional<lldb_protocol::mcp::Message>>
217-
ProtocolServerMCP::HandleData(llvm::StringRef data) {
218-
auto message = llvm::json::parse<lldb_protocol::mcp::Message>(/*JSON=*/data);
219-
if (!message)
220-
return message.takeError();
221-
222-
if (const lldb_protocol::mcp::Request *request =
223-
std::get_if<lldb_protocol::mcp::Request>(&(*message))) {
224-
llvm::Expected<lldb_protocol::mcp::Response> response = Handle(*request);
225-
226-
// Handle failures by converting them into an Error message.
227-
if (!response) {
228-
lldb_protocol::mcp::Error protocol_error;
229-
llvm::handleAllErrors(
230-
response.takeError(),
231-
[&](const MCPError &err) { protocol_error = err.toProtcolError(); },
232-
[&](const llvm::ErrorInfoBase &err) {
233-
protocol_error.error.code = MCPError::kInternalError;
234-
protocol_error.error.message = err.message();
235-
});
236-
protocol_error.id = request->id;
237-
return protocol_error;
238-
}
239-
240-
return *response;
241-
}
242-
243-
if (const lldb_protocol::mcp::Notification *notification =
244-
std::get_if<lldb_protocol::mcp::Notification>(&(*message))) {
245-
Handle(*notification);
246-
return std::nullopt;
247-
}
248-
249-
if (std::get_if<lldb_protocol::mcp::Error>(&(*message)))
250-
return llvm::createStringError("unexpected MCP message: error");
251-
252-
if (std::get_if<lldb_protocol::mcp::Response>(&(*message)))
253-
return llvm::createStringError("unexpected MCP message: response");
254-
255-
llvm_unreachable("all message types handled");
256-
}
257-
258177
lldb_protocol::mcp::Capabilities ProtocolServerMCP::GetCapabilities() {
259178
lldb_protocol::mcp::Capabilities capabilities;
260179
capabilities.tools.listChanged = true;
@@ -263,158 +182,3 @@ lldb_protocol::mcp::Capabilities ProtocolServerMCP::GetCapabilities() {
263182
capabilities.resources.listChanged = false;
264183
return capabilities;
265184
}
266-
267-
void ProtocolServerMCP::AddTool(std::unique_ptr<Tool> tool) {
268-
std::lock_guard<std::mutex> guard(m_server_mutex);
269-
270-
if (!tool)
271-
return;
272-
m_tools[tool->GetName()] = std::move(tool);
273-
}
274-
275-
void ProtocolServerMCP::AddResourceProvider(
276-
std::unique_ptr<ResourceProvider> resource_provider) {
277-
std::lock_guard<std::mutex> guard(m_server_mutex);
278-
279-
if (!resource_provider)
280-
return;
281-
m_resource_providers.push_back(std::move(resource_provider));
282-
}
283-
284-
void ProtocolServerMCP::AddRequestHandler(llvm::StringRef method,
285-
RequestHandler handler) {
286-
std::lock_guard<std::mutex> guard(m_server_mutex);
287-
m_request_handlers[method] = std::move(handler);
288-
}
289-
290-
void ProtocolServerMCP::AddNotificationHandler(llvm::StringRef method,
291-
NotificationHandler handler) {
292-
std::lock_guard<std::mutex> guard(m_server_mutex);
293-
m_notification_handlers[method] = std::move(handler);
294-
}
295-
296-
llvm::Expected<lldb_protocol::mcp::Response>
297-
ProtocolServerMCP::InitializeHandler(
298-
const lldb_protocol::mcp::Request &request) {
299-
lldb_protocol::mcp::Response response;
300-
response.result.emplace(llvm::json::Object{
301-
{"protocolVersion", lldb_protocol::mcp::kVersion},
302-
{"capabilities", GetCapabilities()},
303-
{"serverInfo",
304-
llvm::json::Object{{"name", kName}, {"version", kVersion}}}});
305-
return response;
306-
}
307-
308-
llvm::Expected<lldb_protocol::mcp::Response>
309-
ProtocolServerMCP::ToolsListHandler(
310-
const lldb_protocol::mcp::Request &request) {
311-
lldb_protocol::mcp::Response response;
312-
313-
llvm::json::Array tools;
314-
for (const auto &tool : m_tools)
315-
tools.emplace_back(toJSON(tool.second->GetDefinition()));
316-
317-
response.result.emplace(llvm::json::Object{{"tools", std::move(tools)}});
318-
319-
return response;
320-
}
321-
322-
llvm::Expected<lldb_protocol::mcp::Response>
323-
ProtocolServerMCP::ToolsCallHandler(
324-
const lldb_protocol::mcp::Request &request) {
325-
lldb_protocol::mcp::Response response;
326-
327-
if (!request.params)
328-
return llvm::createStringError("no tool parameters");
329-
330-
const json::Object *param_obj = request.params->getAsObject();
331-
if (!param_obj)
332-
return llvm::createStringError("no tool parameters");
333-
334-
const json::Value *name = param_obj->get("name");
335-
if (!name)
336-
return llvm::createStringError("no tool name");
337-
338-
llvm::StringRef tool_name = name->getAsString().value_or("");
339-
if (tool_name.empty())
340-
return llvm::createStringError("no tool name");
341-
342-
auto it = m_tools.find(tool_name);
343-
if (it == m_tools.end())
344-
return llvm::createStringError(llvm::formatv("no tool \"{0}\"", tool_name));
345-
346-
lldb_protocol::mcp::ToolArguments tool_args;
347-
if (const json::Value *args = param_obj->get("arguments"))
348-
tool_args = *args;
349-
350-
llvm::Expected<lldb_protocol::mcp::TextResult> text_result =
351-
it->second->Call(tool_args);
352-
if (!text_result)
353-
return text_result.takeError();
354-
355-
response.result.emplace(toJSON(*text_result));
356-
357-
return response;
358-
}
359-
360-
llvm::Expected<lldb_protocol::mcp::Response>
361-
ProtocolServerMCP::ResourcesListHandler(
362-
const lldb_protocol::mcp::Request &request) {
363-
lldb_protocol::mcp::Response response;
364-
365-
llvm::json::Array resources;
366-
367-
std::lock_guard<std::mutex> guard(m_server_mutex);
368-
for (std::unique_ptr<ResourceProvider> &resource_provider_up :
369-
m_resource_providers) {
370-
for (const lldb_protocol::mcp::Resource &resource :
371-
resource_provider_up->GetResources())
372-
resources.push_back(resource);
373-
}
374-
response.result.emplace(
375-
llvm::json::Object{{"resources", std::move(resources)}});
376-
377-
return response;
378-
}
379-
380-
llvm::Expected<lldb_protocol::mcp::Response>
381-
ProtocolServerMCP::ResourcesReadHandler(
382-
const lldb_protocol::mcp::Request &request) {
383-
lldb_protocol::mcp::Response response;
384-
385-
if (!request.params)
386-
return llvm::createStringError("no resource parameters");
387-
388-
const json::Object *param_obj = request.params->getAsObject();
389-
if (!param_obj)
390-
return llvm::createStringError("no resource parameters");
391-
392-
const json::Value *uri = param_obj->get("uri");
393-
if (!uri)
394-
return llvm::createStringError("no resource uri");
395-
396-
llvm::StringRef uri_str = uri->getAsString().value_or("");
397-
if (uri_str.empty())
398-
return llvm::createStringError("no resource uri");
399-
400-
std::lock_guard<std::mutex> guard(m_server_mutex);
401-
for (std::unique_ptr<ResourceProvider> &resource_provider_up :
402-
m_resource_providers) {
403-
llvm::Expected<lldb_protocol::mcp::ResourceResult> result =
404-
resource_provider_up->ReadResource(uri_str);
405-
if (result.errorIsA<UnsupportedURI>()) {
406-
llvm::consumeError(result.takeError());
407-
continue;
408-
}
409-
if (!result)
410-
return result.takeError();
411-
412-
lldb_protocol::mcp::Response response;
413-
response.result.emplace(std::move(*result));
414-
return response;
415-
}
416-
417-
return make_error<MCPError>(
418-
llvm::formatv("no resource handler for uri: {0}", uri_str).str(),
419-
MCPError::kResourceNotFound);
420-
}

0 commit comments

Comments
 (0)