Skip to content

Commit 74cc459

Browse files
committed
[lldb] Expose debuggers and target as resources through MCP
Expose debuggers and target as resources through MCP. This has two advantages: 1. Enables returning data in a structured way. Although tools can return structured data with the latest revision of the protocol, we might not be able to update before the majority of clients has adopted it. 2. Enables the user to specify a resource themselves, rather than letting the model guess which debugger instance it should use. This PR exposes a resource for debuggers and targets. The following URI returns information about a given debugger instance: ``` lldb://debugger/<debugger id> ``` For example: ``` { uri: "lldb://debugger/0" mimeType: "application/json" text: "{"debugger_id":0,"num_targets":2}" } ``` The following URI returns information about a given target: ``` lldb://debugger/<debugger id>/target/<target id> ``` For example: ``` { uri: "lldb://debugger/0/target/0" mimeType: "application/json" text: "{"arch":"arm64-apple-macosx26.0.0","debugger_id":0,"path":"/Users/jonas/llvm/build-ra/bin/count","target_id":0}" } ```
1 parent 535d691 commit 74cc459

File tree

13 files changed

+581
-63
lines changed

13 files changed

+581
-63
lines changed

lldb/source/Plugins/Protocol/MCP/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ add_lldb_library(lldbPluginProtocolServerMCP PLUGIN
22
MCPError.cpp
33
Protocol.cpp
44
ProtocolServerMCP.cpp
5+
Resource.cpp
56
Tool.cpp
67

78
LINK_COMPONENTS

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace lldb_private::mcp {
1515

1616
char MCPError::ID;
17+
char UnsupportedURI::ID;
1718

1819
MCPError::MCPError(std::string message, int64_t error_code)
1920
: m_message(message), m_error_code(error_code) {}
@@ -31,4 +32,14 @@ protocol::Error MCPError::toProtcolError() const {
3132
return error;
3233
}
3334

35+
UnsupportedURI::UnsupportedURI(std::string uri) : m_uri(uri) {}
36+
37+
void UnsupportedURI::log(llvm::raw_ostream &OS) const {
38+
OS << "unsupported uri: " << m_uri;
39+
}
40+
41+
std::error_code UnsupportedURI::convertToErrorCode() const {
42+
return llvm::inconvertibleErrorCode();
43+
}
44+
3445
} // namespace lldb_private::mcp

lldb/source/Plugins/Protocol/MCP/MCPError.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "Protocol.h"
1010
#include "llvm/Support/Error.h"
11+
#include "llvm/Support/FormatVariadic.h"
1112
#include <string>
1213

1314
namespace lldb_private::mcp {
@@ -30,4 +31,17 @@ class MCPError : public llvm::ErrorInfo<MCPError> {
3031
int64_t m_error_code;
3132
};
3233

34+
class UnsupportedURI : public llvm::ErrorInfo<UnsupportedURI> {
35+
public:
36+
static char ID;
37+
38+
UnsupportedURI(std::string uri);
39+
40+
void log(llvm::raw_ostream &OS) const override;
41+
std::error_code convertToErrorCode() const override;
42+
43+
private:
44+
std::string m_uri;
45+
};
46+
3347
} // namespace lldb_private::mcp

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

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,67 @@ bool fromJSON(const llvm::json::Value &V, ToolCapability &TC,
107107
return O && O.map("listChanged", TC.listChanged);
108108
}
109109

110+
llvm::json::Value toJSON(const ResourceCapability &RC) {
111+
return llvm::json::Object{{"listChanged", RC.listChanged},
112+
{"subscribe", RC.subscribe}};
113+
}
114+
115+
bool fromJSON(const llvm::json::Value &V, ResourceCapability &RC,
116+
llvm::json::Path P) {
117+
llvm::json::ObjectMapper O(V, P);
118+
return O && O.map("listChanged", RC.listChanged) &&
119+
O.map("subscribe", RC.subscribe);
120+
}
121+
110122
llvm::json::Value toJSON(const Capabilities &C) {
111-
return llvm::json::Object{{"tools", C.tools}};
123+
return llvm::json::Object{{"tools", C.tools}, {"resources", C.resources}};
124+
}
125+
126+
bool fromJSON(const llvm::json::Value &V, Resource &R, llvm::json::Path P) {
127+
llvm::json::ObjectMapper O(V, P);
128+
return O && O.map("uri", R.uri) && O.map("name", R.name) &&
129+
O.mapOptional("description", R.description) &&
130+
O.mapOptional("mimeType", R.mimeType);
131+
}
132+
133+
llvm::json::Value toJSON(const Resource &R) {
134+
llvm::json::Object Result{{"uri", R.uri}, {"name", R.name}};
135+
if (R.description)
136+
Result.insert({"description", R.description});
137+
if (R.mimeType)
138+
Result.insert({"mimeType", R.mimeType});
139+
return Result;
112140
}
113141

114142
bool fromJSON(const llvm::json::Value &V, Capabilities &C, llvm::json::Path P) {
115143
llvm::json::ObjectMapper O(V, P);
116144
return O && O.map("tools", C.tools);
117145
}
118146

147+
llvm::json::Value toJSON(const ResourceContents &RC) {
148+
llvm::json::Object Result{{"uri", RC.uri}, {"text", RC.text}};
149+
if (RC.mimeType)
150+
Result.insert({"mimeType", RC.mimeType});
151+
return Result;
152+
}
153+
154+
bool fromJSON(const llvm::json::Value &V, ResourceContents &RC,
155+
llvm::json::Path P) {
156+
llvm::json::ObjectMapper O(V, P);
157+
return O && O.map("uri", RC.uri) && O.map("text", RC.text) &&
158+
O.mapOptional("mimeType", RC.mimeType);
159+
}
160+
161+
llvm::json::Value toJSON(const ResourceResult &RR) {
162+
return llvm::json::Object{{"contents", RR.contents}};
163+
}
164+
165+
bool fromJSON(const llvm::json::Value &V, ResourceResult &RR,
166+
llvm::json::Path P) {
167+
llvm::json::ObjectMapper O(V, P);
168+
return O && O.map("contents", RR.contents);
169+
}
170+
119171
llvm::json::Value toJSON(const TextContent &TC) {
120172
return llvm::json::Object{{"type", "text"}, {"text", TC.text}};
121173
}

lldb/source/Plugins/Protocol/MCP/Protocol.h

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,75 @@ struct ToolCapability {
7676
llvm::json::Value toJSON(const ToolCapability &);
7777
bool fromJSON(const llvm::json::Value &, ToolCapability &, llvm::json::Path);
7878

79+
struct ResourceCapability {
80+
/// Whether this server supports notifications for changes to the resources
81+
/// list.
82+
bool listChanged = false;
83+
84+
/// Whether subscriptions are supported.
85+
bool subscribe = false;
86+
};
87+
88+
llvm::json::Value toJSON(const ResourceCapability &);
89+
bool fromJSON(const llvm::json::Value &, ResourceCapability &,
90+
llvm::json::Path);
91+
7992
/// Capabilities that a server may support. Known capabilities are defined here,
8093
/// in this schema, but this is not a closed set: any server can define its own,
8194
/// additional capabilities.
8295
struct Capabilities {
83-
/// Present if the server offers any tools to call.
96+
/// Tool capabilities of the server.
8497
ToolCapability tools;
98+
99+
/// Resource capabilities of the server.
100+
ResourceCapability resources;
85101
};
86102

87103
llvm::json::Value toJSON(const Capabilities &);
88104
bool fromJSON(const llvm::json::Value &, Capabilities &, llvm::json::Path);
89105

106+
/// A known resource that the server is capable of reading.
107+
struct Resource {
108+
/// The URI of this resource.
109+
std::string uri;
110+
111+
/// A human-readable name for this resource.
112+
std::string name;
113+
114+
/// A description of what this resource represents.
115+
std::optional<std::string> description;
116+
117+
/// The MIME type of this resource, if known.
118+
std::optional<std::string> mimeType;
119+
};
120+
121+
llvm::json::Value toJSON(const Resource &);
122+
bool fromJSON(const llvm::json::Value &, Resource &, llvm::json::Path);
123+
124+
/// The contents of a specific resource or sub-resource.
125+
struct ResourceContents {
126+
/// The URI of this resource.
127+
std::string uri;
128+
129+
/// The text of the item. This must only be set if the item can actually be
130+
/// represented as text (not binary data).
131+
std::string text;
132+
133+
/// The MIME type of this resource, if known.
134+
std::optional<std::string> mimeType;
135+
};
136+
137+
llvm::json::Value toJSON(const ResourceContents &);
138+
bool fromJSON(const llvm::json::Value &, ResourceContents &, llvm::json::Path);
139+
140+
/// The server's response to a resources/read request from the client.
141+
struct ResourceResult {
142+
std::vector<ResourceContents> contents;
143+
};
144+
145+
llvm::json::Value toJSON(const ResourceResult &);
146+
bool fromJSON(const llvm::json::Value &, ResourceResult &, llvm::json::Path);
147+
90148
/// Text provided to or from an LLM.
91149
struct TextContent {
92150
/// The text content of the message.

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

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,29 @@ ProtocolServerMCP::ProtocolServerMCP() : ProtocolServer() {
2828
AddRequestHandler("initialize",
2929
std::bind(&ProtocolServerMCP::InitializeHandler, this,
3030
std::placeholders::_1));
31+
3132
AddRequestHandler("tools/list",
3233
std::bind(&ProtocolServerMCP::ToolsListHandler, this,
3334
std::placeholders::_1));
3435
AddRequestHandler("tools/call",
3536
std::bind(&ProtocolServerMCP::ToolsCallHandler, this,
3637
std::placeholders::_1));
38+
39+
AddRequestHandler("resources/list",
40+
std::bind(&ProtocolServerMCP::ResourcesListHandler, this,
41+
std::placeholders::_1));
42+
AddRequestHandler("resources/read",
43+
std::bind(&ProtocolServerMCP::ResourcesReadHandler, this,
44+
std::placeholders::_1));
3745
AddNotificationHandler(
3846
"notifications/initialized", [](const protocol::Notification &) {
3947
LLDB_LOG(GetLog(LLDBLog::Host), "MCP initialization complete");
4048
});
49+
4150
AddTool(
4251
std::make_unique<CommandTool>("lldb_command", "Run an lldb command."));
43-
AddTool(std::make_unique<DebuggerListTool>(
44-
"lldb_debugger_list", "List debugger instances with their debugger_id."));
52+
53+
AddResourceProvider(std::make_unique<DebuggerResourceProvider>());
4554
}
4655

4756
ProtocolServerMCP::~ProtocolServerMCP() { llvm::consumeError(Stop()); }
@@ -244,6 +253,7 @@ ProtocolServerMCP::HandleData(llvm::StringRef data) {
244253
protocol::Capabilities ProtocolServerMCP::GetCapabilities() {
245254
protocol::Capabilities capabilities;
246255
capabilities.tools.listChanged = true;
256+
capabilities.resources.listChanged = false;
247257
return capabilities;
248258
}
249259

@@ -255,6 +265,15 @@ void ProtocolServerMCP::AddTool(std::unique_ptr<Tool> tool) {
255265
m_tools[tool->GetName()] = std::move(tool);
256266
}
257267

268+
void ProtocolServerMCP::AddResourceProvider(
269+
std::unique_ptr<ResourceProvider> resource_provider) {
270+
std::lock_guard<std::mutex> guard(m_server_mutex);
271+
272+
if (!resource_provider)
273+
return;
274+
m_resource_providers.push_back(std::move(resource_provider));
275+
}
276+
258277
void ProtocolServerMCP::AddRequestHandler(llvm::StringRef method,
259278
RequestHandler handler) {
260279
std::lock_guard<std::mutex> guard(m_server_mutex);
@@ -327,3 +346,62 @@ ProtocolServerMCP::ToolsCallHandler(const protocol::Request &request) {
327346

328347
return response;
329348
}
349+
350+
llvm::Expected<protocol::Response>
351+
ProtocolServerMCP::ResourcesListHandler(const protocol::Request &request) {
352+
protocol::Response response;
353+
354+
llvm::json::Array resources;
355+
356+
std::lock_guard<std::mutex> guard(m_server_mutex);
357+
for (std::unique_ptr<ResourceProvider> &resource_provider_up :
358+
m_resource_providers) {
359+
for (const protocol::Resource &resource :
360+
resource_provider_up->GetResources())
361+
resources.push_back(resource);
362+
}
363+
response.result.emplace(
364+
llvm::json::Object{{"resources", std::move(resources)}});
365+
366+
return response;
367+
}
368+
369+
llvm::Expected<protocol::Response>
370+
ProtocolServerMCP::ResourcesReadHandler(const protocol::Request &request) {
371+
protocol::Response response;
372+
373+
if (!request.params)
374+
return llvm::createStringError("no resource parameters");
375+
376+
const json::Object *param_obj = request.params->getAsObject();
377+
if (!param_obj)
378+
return llvm::createStringError("no resource parameters");
379+
380+
const json::Value *uri = param_obj->get("uri");
381+
if (!uri)
382+
return llvm::createStringError("no resource uri");
383+
384+
llvm::StringRef uri_str = uri->getAsString().value_or("");
385+
if (uri_str.empty())
386+
return llvm::createStringError("no resource uri");
387+
388+
std::lock_guard<std::mutex> guard(m_server_mutex);
389+
for (std::unique_ptr<ResourceProvider> &resource_provider_up :
390+
m_resource_providers) {
391+
llvm::Expected<protocol::ResourceResult> result =
392+
resource_provider_up->ReadResource(uri_str);
393+
if (result.errorIsA<UnsupportedURI>()) {
394+
llvm::consumeError(result.takeError());
395+
continue;
396+
}
397+
if (!result)
398+
return result.takeError();
399+
400+
protocol::Response response;
401+
response.result.emplace(std::move(*result));
402+
return response;
403+
}
404+
405+
return make_error<MCPError>(
406+
llvm::formatv("no resource handler for uri: {0}", uri_str).str(), 1);
407+
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define LLDB_PLUGINS_PROTOCOL_MCP_PROTOCOLSERVERMCP_H
1111

1212
#include "Protocol.h"
13+
#include "Resource.h"
1314
#include "Tool.h"
1415
#include "lldb/Core/ProtocolServer.h"
1516
#include "lldb/Host/MainLoop.h"
@@ -46,6 +47,8 @@ class ProtocolServerMCP : public ProtocolServer {
4647
std::function<void(const protocol::Notification &)>;
4748

4849
void AddTool(std::unique_ptr<Tool> tool);
50+
void AddResourceProvider(std::unique_ptr<ResourceProvider> resource_provider);
51+
4952
void AddRequestHandler(llvm::StringRef method, RequestHandler handler);
5053
void AddNotificationHandler(llvm::StringRef method,
5154
NotificationHandler handler);
@@ -61,11 +64,17 @@ class ProtocolServerMCP : public ProtocolServer {
6164

6265
llvm::Expected<protocol::Response>
6366
InitializeHandler(const protocol::Request &);
67+
6468
llvm::Expected<protocol::Response>
6569
ToolsListHandler(const protocol::Request &);
6670
llvm::Expected<protocol::Response>
6771
ToolsCallHandler(const protocol::Request &);
6872

73+
llvm::Expected<protocol::Response>
74+
ResourcesListHandler(const protocol::Request &);
75+
llvm::Expected<protocol::Response>
76+
ResourcesReadHandler(const protocol::Request &);
77+
6978
protocol::Capabilities GetCapabilities();
7079

7180
llvm::StringLiteral kName = "lldb-mcp";
@@ -89,6 +98,7 @@ class ProtocolServerMCP : public ProtocolServer {
8998

9099
std::mutex m_server_mutex;
91100
llvm::StringMap<std::unique_ptr<Tool>> m_tools;
101+
std::vector<std::unique_ptr<ResourceProvider>> m_resource_providers;
92102

93103
llvm::StringMap<RequestHandler> m_request_handlers;
94104
llvm::StringMap<NotificationHandler> m_notification_handlers;

0 commit comments

Comments
 (0)