Skip to content

Commit 231d76a

Browse files
committed
[lldb] Move the generic MCP server code into Protocol/MCP (NFC) (llvm#152396)
This is a continuation of llvm#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. (cherry picked from commit 44aedac)
1 parent 6934935 commit 231d76a

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)