Skip to content

Commit 3c4c2fa

Browse files
authored
[lldb] Expose debuggers and target as resources through MCP (#148075)
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 4811552 commit 3c4c2fa

File tree

15 files changed

+645
-70
lines changed

15 files changed

+645
-70
lines changed

lldb/include/lldb/Core/Debugger.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
367367

368368
bool GetNotifyVoid() const;
369369

370-
const std::string &GetInstanceName() { return m_instance_name; }
370+
const std::string &GetInstanceName() const { return m_instance_name; }
371371

372372
bool GetShowInlineDiagnostics() const;
373373

lldb/include/lldb/Target/Target.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1093,7 +1093,7 @@ class Target : public std::enable_shared_from_this<Target>,
10931093

10941094
Architecture *GetArchitecturePlugin() const { return m_arch.GetPlugin(); }
10951095

1096-
Debugger &GetDebugger() { return m_debugger; }
1096+
Debugger &GetDebugger() const { return m_debugger; }
10971097

10981098
size_t ReadMemoryFromFileCache(const Address &addr, void *dst, size_t dst_len,
10991099
Status &error);

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: 18 additions & 1 deletion
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 {
@@ -16,7 +17,7 @@ class MCPError : public llvm::ErrorInfo<MCPError> {
1617
public:
1718
static char ID;
1819

19-
MCPError(std::string message, int64_t error_code);
20+
MCPError(std::string message, int64_t error_code = kInternalError);
2021

2122
void log(llvm::raw_ostream &OS) const override;
2223
std::error_code convertToErrorCode() const override;
@@ -25,9 +26,25 @@ class MCPError : public llvm::ErrorInfo<MCPError> {
2526

2627
protocol::Error toProtcolError() const;
2728

29+
static constexpr int64_t kResourceNotFound = -32002;
30+
static constexpr int64_t kInternalError = -32603;
31+
2832
private:
2933
std::string m_message;
3034
int64_t m_error_code;
3135
};
3236

37+
class UnsupportedURI : public llvm::ErrorInfo<UnsupportedURI> {
38+
public:
39+
static char ID;
40+
41+
UnsupportedURI(std::string uri);
42+
43+
void log(llvm::raw_ostream &OS) const override;
44+
std::error_code convertToErrorCode() const override;
45+
46+
private:
47+
std::string m_uri;
48+
};
49+
3350
} // 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: 85 additions & 4 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()); }
@@ -75,7 +84,7 @@ ProtocolServerMCP::Handle(protocol::Request request) {
7584
}
7685

7786
return make_error<MCPError>(
78-
llvm::formatv("no handler for request: {0}", request.method).str(), 1);
87+
llvm::formatv("no handler for request: {0}", request.method).str());
7988
}
8089

8190
void ProtocolServerMCP::Handle(protocol::Notification notification) {
@@ -216,7 +225,7 @@ ProtocolServerMCP::HandleData(llvm::StringRef data) {
216225
response.takeError(),
217226
[&](const MCPError &err) { protocol_error = err.toProtcolError(); },
218227
[&](const llvm::ErrorInfoBase &err) {
219-
protocol_error.error.code = -1;
228+
protocol_error.error.code = MCPError::kInternalError;
220229
protocol_error.error.message = err.message();
221230
});
222231
protocol_error.id = request->id;
@@ -244,6 +253,9 @@ ProtocolServerMCP::HandleData(llvm::StringRef data) {
244253
protocol::Capabilities ProtocolServerMCP::GetCapabilities() {
245254
protocol::Capabilities capabilities;
246255
capabilities.tools.listChanged = true;
256+
// FIXME: Support sending notifications when a debugger/target are
257+
// added/removed.
258+
capabilities.resources.listChanged = false;
247259
return capabilities;
248260
}
249261

@@ -255,6 +267,15 @@ void ProtocolServerMCP::AddTool(std::unique_ptr<Tool> tool) {
255267
m_tools[tool->GetName()] = std::move(tool);
256268
}
257269

270+
void 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(std::move(resource_provider));
277+
}
278+
258279
void ProtocolServerMCP::AddRequestHandler(llvm::StringRef method,
259280
RequestHandler handler) {
260281
std::lock_guard<std::mutex> guard(m_server_mutex);
@@ -327,3 +348,63 @@ ProtocolServerMCP::ToolsCallHandler(const protocol::Request &request) {
327348

328349
return response;
329350
}
351+
352+
llvm::Expected<protocol::Response>
353+
ProtocolServerMCP::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(resource);
364+
}
365+
response.result.emplace(
366+
llvm::json::Object{{"resources", std::move(resources)}});
367+
368+
return response;
369+
}
370+
371+
llvm::Expected<protocol::Response>
372+
ProtocolServerMCP::ResourcesReadHandler(const protocol::Request &request) {
373+
protocol::Response response;
374+
375+
if (!request.params)
376+
return llvm::createStringError("no resource parameters");
377+
378+
const json::Object *param_obj = request.params->getAsObject();
379+
if (!param_obj)
380+
return llvm::createStringError("no resource parameters");
381+
382+
const json::Value *uri = param_obj->get("uri");
383+
if (!uri)
384+
return llvm::createStringError("no resource uri");
385+
386+
llvm::StringRef uri_str = uri->getAsString().value_or("");
387+
if (uri_str.empty())
388+
return llvm::createStringError("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_str);
395+
if (result.errorIsA<UnsupportedURI>()) {
396+
llvm::consumeError(result.takeError());
397+
continue;
398+
}
399+
if (!result)
400+
return result.takeError();
401+
402+
protocol::Response response;
403+
response.result.emplace(std::move(*result));
404+
return response;
405+
}
406+
407+
return make_error<MCPError>(
408+
llvm::formatv("no resource handler for uri: {0}", uri_str).str(),
409+
MCPError::kResourceNotFound);
410+
}

0 commit comments

Comments
 (0)