Skip to content

Commit 8ae30a3

Browse files
authored
[lldb] Adding A new Binding helper for JSONTransport. (#159160)
This adds a new Binding helper class to allow mapping of incoming and outgoing requests / events to specific handlers. This should make it easier to create new protocol implementations and allow us to create a relay in the lldb-mcp binary.
1 parent 8284e4d commit 8ae30a3

File tree

20 files changed

+1552
-629
lines changed

20 files changed

+1552
-629
lines changed

lldb/include/lldb/Host/JSONTransport.h

Lines changed: 598 additions & 28 deletions
Large diffs are not rendered by default.

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
#ifndef LLDB_PROTOCOL_MCP_MCPERROR_H
1010
#define LLDB_PROTOCOL_MCP_MCPERROR_H
1111

12-
#include "lldb/Protocol/MCP/Protocol.h"
1312
#include "llvm/Support/Error.h"
1413
#include <string>
1514

@@ -26,14 +25,12 @@ class MCPError : public llvm::ErrorInfo<MCPError> {
2625

2726
const std::string &getMessage() const { return m_message; }
2827

29-
lldb_protocol::mcp::Error toProtocolError() const;
30-
3128
static constexpr int64_t kResourceNotFound = -32002;
3229
static constexpr int64_t kInternalError = -32603;
3330

3431
private:
3532
std::string m_message;
36-
int64_t m_error_code;
33+
int m_error_code;
3734
};
3835

3936
class UnsupportedURI : public llvm::ErrorInfo<UnsupportedURI> {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#ifndef LLDB_PROTOCOL_MCP_PROTOCOL_H
1515
#define LLDB_PROTOCOL_MCP_PROTOCOL_H
1616

17+
#include "llvm/ADT/StringRef.h"
1718
#include "llvm/Support/JSON.h"
1819
#include <optional>
1920
#include <string>
@@ -322,6 +323,10 @@ struct CallToolResult {
322323
llvm::json::Value toJSON(const CallToolResult &);
323324
bool fromJSON(const llvm::json::Value &, CallToolResult &, llvm::json::Path);
324325

326+
lldb_protocol::mcp::Request
327+
MakeRequest(int64_t id, llvm::StringRef method,
328+
std::optional<llvm::json::Value> params);
329+
325330
} // namespace lldb_protocol::mcp
326331

327332
#endif

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

Lines changed: 31 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
#ifndef LLDB_PROTOCOL_MCP_SERVER_H
1010
#define LLDB_PROTOCOL_MCP_SERVER_H
1111

12-
#include "lldb/Host/JSONTransport.h"
1312
#include "lldb/Host/MainLoop.h"
1413
#include "lldb/Protocol/MCP/Protocol.h"
1514
#include "lldb/Protocol/MCP/Resource.h"
@@ -19,75 +18,66 @@
1918
#include "llvm/ADT/StringMap.h"
2019
#include "llvm/ADT/StringRef.h"
2120
#include "llvm/Support/Error.h"
21+
#include "llvm/Support/FormatVariadic.h"
2222
#include "llvm/Support/JSON.h"
2323
#include "llvm/Support/Signals.h"
24-
#include <functional>
2524
#include <memory>
2625
#include <string>
2726
#include <vector>
2827

2928
namespace lldb_protocol::mcp {
3029

31-
class Server : public MCPTransport::MessageHandler {
32-
using ClosedCallback = llvm::unique_function<void()>;
30+
class Server {
31+
32+
using MCPTransportUP = std::unique_ptr<lldb_protocol::mcp::MCPTransport>;
33+
34+
using ReadHandleUP = lldb_private::MainLoop::ReadHandleUP;
3335

3436
public:
35-
Server(std::string name, std::string version, MCPTransport &client,
36-
LogCallback log_callback = {}, ClosedCallback closed_callback = {});
37+
Server(std::string name, std::string version, LogCallback log_callback = {});
3738
~Server() = default;
3839

39-
using NotificationHandler = std::function<void(const Notification &)>;
40-
4140
void AddTool(std::unique_ptr<Tool> tool);
4241
void AddResourceProvider(std::unique_ptr<ResourceProvider> resource_provider);
43-
void AddNotificationHandler(llvm::StringRef method,
44-
NotificationHandler handler);
45-
46-
protected:
47-
ServerCapabilities GetCapabilities();
48-
49-
using RequestHandler =
50-
std::function<llvm::Expected<Response>(const Request &)>;
5142

52-
void AddRequestHandlers();
43+
llvm::Error Accept(lldb_private::MainLoop &, MCPTransportUP);
5344

54-
void AddRequestHandler(llvm::StringRef method, RequestHandler handler);
55-
56-
llvm::Expected<std::optional<Message>> HandleData(llvm::StringRef data);
57-
58-
llvm::Expected<Response> Handle(const Request &request);
59-
void Handle(const Notification &notification);
45+
protected:
46+
MCPBinderUP Bind(MCPTransport &);
6047

61-
llvm::Expected<Response> InitializeHandler(const Request &);
48+
ServerCapabilities GetCapabilities();
6249

63-
llvm::Expected<Response> ToolsListHandler(const Request &);
64-
llvm::Expected<Response> ToolsCallHandler(const Request &);
50+
llvm::Expected<InitializeResult> InitializeHandler(const InitializeParams &);
6551

66-
llvm::Expected<Response> ResourcesListHandler(const Request &);
67-
llvm::Expected<Response> ResourcesReadHandler(const Request &);
52+
llvm::Expected<ListToolsResult> ToolsListHandler();
53+
llvm::Expected<CallToolResult> ToolsCallHandler(const CallToolParams &);
6854

69-
void Received(const Request &) override;
70-
void Received(const Response &) override;
71-
void Received(const Notification &) override;
72-
void OnError(llvm::Error) override;
73-
void OnClosed() override;
55+
llvm::Expected<ListResourcesResult> ResourcesListHandler();
56+
llvm::Expected<ReadResourceResult>
57+
ResourcesReadHandler(const ReadResourceParams &);
7458

75-
protected:
76-
void Log(llvm::StringRef);
59+
template <typename... Ts> inline auto Logv(const char *Fmt, Ts &&...Vals) {
60+
Log(llvm::formatv(Fmt, std::forward<Ts>(Vals)...).str());
61+
}
62+
void Log(llvm::StringRef message) {
63+
if (m_log_callback)
64+
m_log_callback(message);
65+
}
7766

7867
private:
7968
const std::string m_name;
8069
const std::string m_version;
8170

82-
MCPTransport &m_client;
8371
LogCallback m_log_callback;
84-
ClosedCallback m_closed_callback;
72+
struct Client {
73+
ReadHandleUP handle;
74+
MCPTransportUP transport;
75+
MCPBinderUP binder;
76+
};
77+
std::map<MCPTransport *, Client> m_instances;
8578

8679
llvm::StringMap<std::unique_ptr<Tool>> m_tools;
8780
std::vector<std::unique_ptr<ResourceProvider>> m_resource_providers;
88-
89-
llvm::StringMap<RequestHandler> m_request_handlers;
90-
llvm::StringMap<NotificationHandler> m_notification_handlers;
9181
};
9282

9383
class ServerInfoHandle;
@@ -121,7 +111,7 @@ class ServerInfoHandle {
121111
ServerInfoHandle &operator=(const ServerInfoHandle &) = delete;
122112
/// @}
123113

124-
/// Remove the file.
114+
/// Remove the file on disk, if one is tracked.
125115
void Remove();
126116

127117
private:

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

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,78 @@
1010
#define LLDB_PROTOCOL_MCP_TRANSPORT_H
1111

1212
#include "lldb/Host/JSONTransport.h"
13+
#include "lldb/Protocol/MCP/MCPError.h"
1314
#include "lldb/Protocol/MCP/Protocol.h"
1415
#include "lldb/lldb-forward.h"
1516
#include "llvm/ADT/FunctionExtras.h"
1617
#include "llvm/ADT/StringRef.h"
18+
#include "llvm/Support/Error.h"
19+
#include <sys/types.h>
1720

1821
namespace lldb_protocol::mcp {
1922

23+
struct ProtocolDescriptor {
24+
using Id = int64_t;
25+
using Req = Request;
26+
using Resp = Response;
27+
using Evt = Notification;
28+
29+
static inline Id InitialId() { return 0; }
30+
static inline Request Make(Id id, llvm::StringRef method,
31+
std::optional<llvm::json::Value> params) {
32+
return Request{id, method.str(), params};
33+
}
34+
static inline Notification Make(llvm::StringRef method,
35+
std::optional<llvm::json::Value> params) {
36+
return Notification{method.str(), params};
37+
}
38+
static inline Response Make(Req req, llvm::Error error) {
39+
lldb_protocol::mcp::Error protocol_error;
40+
llvm::handleAllErrors(
41+
std::move(error), [&](const llvm::ErrorInfoBase &err) {
42+
std::error_code cerr = err.convertToErrorCode();
43+
protocol_error.code =
44+
cerr == llvm::inconvertibleErrorCode()
45+
? lldb_protocol::mcp::eErrorCodeInternalError
46+
: cerr.value();
47+
protocol_error.message = err.message();
48+
});
49+
50+
return Response{req.id, std::move(protocol_error)};
51+
}
52+
static inline Response Make(Req req,
53+
std::optional<llvm::json::Value> result) {
54+
return Response{req.id, std::move(result)};
55+
}
56+
static inline Id KeyFor(Response r) { return std::get<Id>(r.id); }
57+
static inline std::string KeyFor(Request r) { return r.method; }
58+
static inline std::string KeyFor(Notification n) { return n.method; }
59+
static inline std::optional<llvm::json::Value> Extract(Request r) {
60+
return r.params;
61+
}
62+
static inline llvm::Expected<llvm::json::Value> Extract(Response r) {
63+
if (const lldb_protocol::mcp::Error *error =
64+
std::get_if<lldb_protocol::mcp::Error>(&r.result))
65+
return llvm::make_error<lldb_protocol::mcp::MCPError>(error->message,
66+
error->code);
67+
return std::get<llvm::json::Value>(r.result);
68+
}
69+
static inline std::optional<llvm::json::Value> Extract(Notification n) {
70+
return n.params;
71+
}
72+
};
73+
2074
/// Generic transport that uses the MCP protocol.
21-
using MCPTransport = lldb_private::Transport<Request, Response, Notification>;
75+
using MCPTransport = lldb_private::transport::JSONTransport<ProtocolDescriptor>;
76+
using MCPBinder = lldb_private::transport::Binder<ProtocolDescriptor>;
77+
using MCPBinderUP = std::unique_ptr<MCPBinder>;
2278

2379
/// Generic logging callback, to allow the MCP server / client / transport layer
2480
/// to be independent of the lldb log implementation.
2581
using LogCallback = llvm::unique_function<void(llvm::StringRef message)>;
2682

2783
class Transport final
28-
: public lldb_private::JSONRPCTransport<Request, Response, Notification> {
84+
: public lldb_private::transport::JSONRPCTransport<ProtocolDescriptor> {
2985
public:
3086
Transport(lldb::IOObjectSP in, lldb::IOObjectSP out,
3187
LogCallback log_callback = {});

lldb/source/Host/common/JSONTransport.cpp

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,39 @@
1414
#include <string>
1515

1616
using namespace llvm;
17-
using namespace lldb;
18-
using namespace lldb_private;
17+
using namespace lldb_private::transport;
1918

2019
char TransportUnhandledContentsError::ID;
2120

2221
TransportUnhandledContentsError::TransportUnhandledContentsError(
2322
std::string unhandled_contents)
2423
: m_unhandled_contents(unhandled_contents) {}
2524

26-
void TransportUnhandledContentsError::log(llvm::raw_ostream &OS) const {
25+
void TransportUnhandledContentsError::log(raw_ostream &OS) const {
2726
OS << "transport EOF with unhandled contents: '" << m_unhandled_contents
2827
<< "'";
2928
}
3029
std::error_code TransportUnhandledContentsError::convertToErrorCode() const {
3130
return std::make_error_code(std::errc::bad_message);
3231
}
32+
33+
char InvalidParams::ID;
34+
35+
void InvalidParams::log(raw_ostream &OS) const {
36+
OS << "invalid parameters for method '" << m_method << "': '" << m_context
37+
<< "'";
38+
}
39+
std::error_code InvalidParams::convertToErrorCode() const {
40+
return std::make_error_code(std::errc::invalid_argument);
41+
}
42+
43+
char MethodNotFound::ID;
44+
45+
void MethodNotFound::log(raw_ostream &OS) const {
46+
OS << "method not found: '" << m_method << "'";
47+
}
48+
49+
std::error_code MethodNotFound::convertToErrorCode() const {
50+
// JSON-RPC Method not found
51+
return std::error_code(MethodNotFound::kErrorCode, std::generic_category());
52+
}

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

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,6 @@ llvm::StringRef ProtocolServerMCP::GetPluginDescriptionStatic() {
5252
}
5353

5454
void ProtocolServerMCP::Extend(lldb_protocol::mcp::Server &server) const {
55-
server.AddNotificationHandler("notifications/initialized",
56-
[](const lldb_protocol::mcp::Notification &) {
57-
LLDB_LOG(GetLog(LLDBLog::Host),
58-
"MCP initialization complete");
59-
});
6055
server.AddTool(
6156
std::make_unique<CommandTool>("command", "Run an lldb command."));
6257
server.AddTool(std::make_unique<DebuggerListTool>(
@@ -74,26 +69,9 @@ void ProtocolServerMCP::AcceptCallback(std::unique_ptr<Socket> socket) {
7469
io_sp, io_sp, [client_name](llvm::StringRef message) {
7570
LLDB_LOG(GetLog(LLDBLog::Host), "{0}: {1}", client_name, message);
7671
});
77-
MCPTransport *transport_ptr = transport_up.get();
78-
auto instance_up = std::make_unique<lldb_protocol::mcp::Server>(
79-
std::string(kName), std::string(kVersion), *transport_up,
80-
/*log_callback=*/
81-
[client_name](llvm::StringRef message) {
82-
LLDB_LOG(GetLog(LLDBLog::Host), "{0} Server: {1}", client_name,
83-
message);
84-
},
85-
/*closed_callback=*/
86-
[this, transport_ptr]() { m_instances.erase(transport_ptr); });
87-
Extend(*instance_up);
88-
llvm::Expected<MainLoop::ReadHandleUP> handle =
89-
transport_up->RegisterMessageHandler(m_loop, *instance_up);
90-
if (!handle) {
91-
LLDB_LOG_ERROR(log, handle.takeError(), "Failed to run MCP server: {0}");
92-
return;
93-
}
94-
m_instances[transport_ptr] =
95-
std::make_tuple<ServerUP, ReadHandleUP, TransportUP>(
96-
std::move(instance_up), std::move(*handle), std::move(transport_up));
72+
73+
if (auto error = m_server->Accept(m_loop, std::move(transport_up)))
74+
LLDB_LOG_ERROR(log, std::move(error), "{0}:");
9775
}
9876

9977
llvm::Error ProtocolServerMCP::Start(ProtocolServer::Connection connection) {
@@ -124,14 +102,21 @@ llvm::Error ProtocolServerMCP::Start(ProtocolServer::Connection connection) {
124102
llvm::join(m_listener->GetListeningConnectionURI(), ", ");
125103

126104
ServerInfo info{listening_uris[0]};
127-
llvm::Expected<ServerInfoHandle> handle = ServerInfo::Write(info);
128-
if (!handle)
129-
return handle.takeError();
105+
llvm::Expected<ServerInfoHandle> server_info_handle = ServerInfo::Write(info);
106+
if (!server_info_handle)
107+
return server_info_handle.takeError();
108+
109+
m_client_count = 0;
110+
m_server = std::make_unique<lldb_protocol::mcp::Server>(
111+
std::string(kName), std::string(kVersion), [](StringRef message) {
112+
LLDB_LOG(GetLog(LLDBLog::Host), "MCP Server: {0}", message);
113+
});
114+
Extend(*m_server);
130115

131116
m_running = true;
132-
m_server_info_handle = std::move(*handle);
133-
m_listen_handlers = std::move(*handles);
134-
m_loop_thread = std::thread([=] {
117+
m_server_info_handle = std::move(*server_info_handle);
118+
m_accept_handles = std::move(*handles);
119+
m_loop_thread = std::thread([this] {
135120
llvm::set_thread_name("protocol-server.mcp");
136121
m_loop.Run();
137122
});
@@ -155,9 +140,10 @@ llvm::Error ProtocolServerMCP::Stop() {
155140
if (m_loop_thread.joinable())
156141
m_loop_thread.join();
157142

143+
m_accept_handles.clear();
144+
145+
m_server.reset(nullptr);
158146
m_server_info_handle.Remove();
159-
m_listen_handlers.clear();
160-
m_instances.clear();
161147

162148
return llvm::Error::success();
163149
}

0 commit comments

Comments
 (0)