-
Notifications
You must be signed in to change notification settings - Fork 14.7k
[lldb] Expose debuggers and target as resources through MCP #148075
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-lldb Author: Jonas Devlieghere (JDevlieghere) ChangesExpose debuggers and target as resources through MCP. This has two advantages:
This PR exposes a resource for debuggers and targets. The following URI returns information about a given debugger instance:
For example:
The following URI returns information about a given target:
For example:
Patch is 33.55 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/148075.diff 13 Files Affected:
diff --git a/lldb/source/Plugins/Protocol/MCP/CMakeLists.txt b/lldb/source/Plugins/Protocol/MCP/CMakeLists.txt
index db31a7a69cb33..e104fb527e57a 100644
--- a/lldb/source/Plugins/Protocol/MCP/CMakeLists.txt
+++ b/lldb/source/Plugins/Protocol/MCP/CMakeLists.txt
@@ -2,6 +2,7 @@ add_lldb_library(lldbPluginProtocolServerMCP PLUGIN
MCPError.cpp
Protocol.cpp
ProtocolServerMCP.cpp
+ Resource.cpp
Tool.cpp
LINK_COMPONENTS
diff --git a/lldb/source/Plugins/Protocol/MCP/MCPError.cpp b/lldb/source/Plugins/Protocol/MCP/MCPError.cpp
index 5ed850066b659..659b53a14fe23 100644
--- a/lldb/source/Plugins/Protocol/MCP/MCPError.cpp
+++ b/lldb/source/Plugins/Protocol/MCP/MCPError.cpp
@@ -14,6 +14,7 @@
namespace lldb_private::mcp {
char MCPError::ID;
+char UnsupportedURI::ID;
MCPError::MCPError(std::string message, int64_t error_code)
: m_message(message), m_error_code(error_code) {}
@@ -31,4 +32,14 @@ protocol::Error MCPError::toProtcolError() const {
return error;
}
+UnsupportedURI::UnsupportedURI(std::string uri) : m_uri(uri) {}
+
+void UnsupportedURI::log(llvm::raw_ostream &OS) const {
+ OS << "unsupported uri: " << m_uri;
+}
+
+std::error_code UnsupportedURI::convertToErrorCode() const {
+ return llvm::inconvertibleErrorCode();
+}
+
} // namespace lldb_private::mcp
diff --git a/lldb/source/Plugins/Protocol/MCP/MCPError.h b/lldb/source/Plugins/Protocol/MCP/MCPError.h
index 2a76a7b087e20..05a047ec881a9 100644
--- a/lldb/source/Plugins/Protocol/MCP/MCPError.h
+++ b/lldb/source/Plugins/Protocol/MCP/MCPError.h
@@ -8,6 +8,7 @@
#include "Protocol.h"
#include "llvm/Support/Error.h"
+#include "llvm/Support/FormatVariadic.h"
#include <string>
namespace lldb_private::mcp {
@@ -30,4 +31,17 @@ class MCPError : public llvm::ErrorInfo<MCPError> {
int64_t m_error_code;
};
+class UnsupportedURI : public llvm::ErrorInfo<UnsupportedURI> {
+public:
+ static char ID;
+
+ UnsupportedURI(std::string uri);
+
+ void log(llvm::raw_ostream &OS) const override;
+ std::error_code convertToErrorCode() const override;
+
+private:
+ std::string m_uri;
+};
+
} // namespace lldb_private::mcp
diff --git a/lldb/source/Plugins/Protocol/MCP/Protocol.cpp b/lldb/source/Plugins/Protocol/MCP/Protocol.cpp
index d66c931a0b284..e42e1bf1118cf 100644
--- a/lldb/source/Plugins/Protocol/MCP/Protocol.cpp
+++ b/lldb/source/Plugins/Protocol/MCP/Protocol.cpp
@@ -107,8 +107,36 @@ bool fromJSON(const llvm::json::Value &V, ToolCapability &TC,
return O && O.map("listChanged", TC.listChanged);
}
+llvm::json::Value toJSON(const ResourceCapability &RC) {
+ return llvm::json::Object{{"listChanged", RC.listChanged},
+ {"subscribe", RC.subscribe}};
+}
+
+bool fromJSON(const llvm::json::Value &V, ResourceCapability &RC,
+ llvm::json::Path P) {
+ llvm::json::ObjectMapper O(V, P);
+ return O && O.map("listChanged", RC.listChanged) &&
+ O.map("subscribe", RC.subscribe);
+}
+
llvm::json::Value toJSON(const Capabilities &C) {
- return llvm::json::Object{{"tools", C.tools}};
+ return llvm::json::Object{{"tools", C.tools}, {"resources", C.resources}};
+}
+
+bool fromJSON(const llvm::json::Value &V, Resource &R, llvm::json::Path P) {
+ llvm::json::ObjectMapper O(V, P);
+ return O && O.map("uri", R.uri) && O.map("name", R.name) &&
+ O.mapOptional("description", R.description) &&
+ O.mapOptional("mimeType", R.mimeType);
+}
+
+llvm::json::Value toJSON(const Resource &R) {
+ llvm::json::Object Result{{"uri", R.uri}, {"name", R.name}};
+ if (R.description)
+ Result.insert({"description", R.description});
+ if (R.mimeType)
+ Result.insert({"mimeType", R.mimeType});
+ return Result;
}
bool fromJSON(const llvm::json::Value &V, Capabilities &C, llvm::json::Path P) {
@@ -116,6 +144,30 @@ bool fromJSON(const llvm::json::Value &V, Capabilities &C, llvm::json::Path P) {
return O && O.map("tools", C.tools);
}
+llvm::json::Value toJSON(const ResourceContents &RC) {
+ llvm::json::Object Result{{"uri", RC.uri}, {"text", RC.text}};
+ if (RC.mimeType)
+ Result.insert({"mimeType", RC.mimeType});
+ return Result;
+}
+
+bool fromJSON(const llvm::json::Value &V, ResourceContents &RC,
+ llvm::json::Path P) {
+ llvm::json::ObjectMapper O(V, P);
+ return O && O.map("uri", RC.uri) && O.map("text", RC.text) &&
+ O.mapOptional("mimeType", RC.mimeType);
+}
+
+llvm::json::Value toJSON(const ResourceResult &RR) {
+ return llvm::json::Object{{"contents", RR.contents}};
+}
+
+bool fromJSON(const llvm::json::Value &V, ResourceResult &RR,
+ llvm::json::Path P) {
+ llvm::json::ObjectMapper O(V, P);
+ return O && O.map("contents", RR.contents);
+}
+
llvm::json::Value toJSON(const TextContent &TC) {
return llvm::json::Object{{"type", "text"}, {"text", TC.text}};
}
diff --git a/lldb/source/Plugins/Protocol/MCP/Protocol.h b/lldb/source/Plugins/Protocol/MCP/Protocol.h
index cb790dc4e5596..ffe621bee1c2a 100644
--- a/lldb/source/Plugins/Protocol/MCP/Protocol.h
+++ b/lldb/source/Plugins/Protocol/MCP/Protocol.h
@@ -76,17 +76,75 @@ struct ToolCapability {
llvm::json::Value toJSON(const ToolCapability &);
bool fromJSON(const llvm::json::Value &, ToolCapability &, llvm::json::Path);
+struct ResourceCapability {
+ /// Whether this server supports notifications for changes to the resources
+ /// list.
+ bool listChanged = false;
+
+ /// Whether subscriptions are supported.
+ bool subscribe = false;
+};
+
+llvm::json::Value toJSON(const ResourceCapability &);
+bool fromJSON(const llvm::json::Value &, ResourceCapability &,
+ llvm::json::Path);
+
/// Capabilities that a server may support. Known capabilities are defined here,
/// in this schema, but this is not a closed set: any server can define its own,
/// additional capabilities.
struct Capabilities {
- /// Present if the server offers any tools to call.
+ /// Tool capabilities of the server.
ToolCapability tools;
+
+ /// Resource capabilities of the server.
+ ResourceCapability resources;
};
llvm::json::Value toJSON(const Capabilities &);
bool fromJSON(const llvm::json::Value &, Capabilities &, llvm::json::Path);
+/// A known resource that the server is capable of reading.
+struct Resource {
+ /// The URI of this resource.
+ std::string uri;
+
+ /// A human-readable name for this resource.
+ std::string name;
+
+ /// A description of what this resource represents.
+ std::optional<std::string> description;
+
+ /// The MIME type of this resource, if known.
+ std::optional<std::string> mimeType;
+};
+
+llvm::json::Value toJSON(const Resource &);
+bool fromJSON(const llvm::json::Value &, Resource &, llvm::json::Path);
+
+/// The contents of a specific resource or sub-resource.
+struct ResourceContents {
+ /// The URI of this resource.
+ std::string uri;
+
+ /// The text of the item. This must only be set if the item can actually be
+ /// represented as text (not binary data).
+ std::string text;
+
+ /// The MIME type of this resource, if known.
+ std::optional<std::string> mimeType;
+};
+
+llvm::json::Value toJSON(const ResourceContents &);
+bool fromJSON(const llvm::json::Value &, ResourceContents &, llvm::json::Path);
+
+/// The server's response to a resources/read request from the client.
+struct ResourceResult {
+ std::vector<ResourceContents> contents;
+};
+
+llvm::json::Value toJSON(const ResourceResult &);
+bool fromJSON(const llvm::json::Value &, ResourceResult &, llvm::json::Path);
+
/// Text provided to or from an LLM.
struct TextContent {
/// The text content of the message.
diff --git a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp
index 3180341b50b91..6099d429672bb 100644
--- a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp
+++ b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp
@@ -28,20 +28,29 @@ ProtocolServerMCP::ProtocolServerMCP() : ProtocolServer() {
AddRequestHandler("initialize",
std::bind(&ProtocolServerMCP::InitializeHandler, this,
std::placeholders::_1));
+
AddRequestHandler("tools/list",
std::bind(&ProtocolServerMCP::ToolsListHandler, this,
std::placeholders::_1));
AddRequestHandler("tools/call",
std::bind(&ProtocolServerMCP::ToolsCallHandler, this,
std::placeholders::_1));
+
+ AddRequestHandler("resources/list",
+ std::bind(&ProtocolServerMCP::ResourcesListHandler, this,
+ std::placeholders::_1));
+ AddRequestHandler("resources/read",
+ std::bind(&ProtocolServerMCP::ResourcesReadHandler, this,
+ std::placeholders::_1));
AddNotificationHandler(
"notifications/initialized", [](const protocol::Notification &) {
LLDB_LOG(GetLog(LLDBLog::Host), "MCP initialization complete");
});
+
AddTool(
std::make_unique<CommandTool>("lldb_command", "Run an lldb command."));
- AddTool(std::make_unique<DebuggerListTool>(
- "lldb_debugger_list", "List debugger instances with their debugger_id."));
+
+ AddResourceProvider(std::make_unique<DebuggerResourceProvider>());
}
ProtocolServerMCP::~ProtocolServerMCP() { llvm::consumeError(Stop()); }
@@ -244,6 +253,7 @@ ProtocolServerMCP::HandleData(llvm::StringRef data) {
protocol::Capabilities ProtocolServerMCP::GetCapabilities() {
protocol::Capabilities capabilities;
capabilities.tools.listChanged = true;
+ capabilities.resources.listChanged = false;
return capabilities;
}
@@ -255,6 +265,15 @@ void ProtocolServerMCP::AddTool(std::unique_ptr<Tool> tool) {
m_tools[tool->GetName()] = std::move(tool);
}
+void ProtocolServerMCP::AddResourceProvider(
+ std::unique_ptr<ResourceProvider> resource_provider) {
+ std::lock_guard<std::mutex> guard(m_server_mutex);
+
+ if (!resource_provider)
+ return;
+ m_resource_providers.push_back(std::move(resource_provider));
+}
+
void ProtocolServerMCP::AddRequestHandler(llvm::StringRef method,
RequestHandler handler) {
std::lock_guard<std::mutex> guard(m_server_mutex);
@@ -327,3 +346,62 @@ ProtocolServerMCP::ToolsCallHandler(const protocol::Request &request) {
return response;
}
+
+llvm::Expected<protocol::Response>
+ProtocolServerMCP::ResourcesListHandler(const protocol::Request &request) {
+ protocol::Response response;
+
+ llvm::json::Array resources;
+
+ std::lock_guard<std::mutex> guard(m_server_mutex);
+ for (std::unique_ptr<ResourceProvider> &resource_provider_up :
+ m_resource_providers) {
+ for (const protocol::Resource &resource :
+ resource_provider_up->GetResources())
+ resources.push_back(resource);
+ }
+ response.result.emplace(
+ llvm::json::Object{{"resources", std::move(resources)}});
+
+ return response;
+}
+
+llvm::Expected<protocol::Response>
+ProtocolServerMCP::ResourcesReadHandler(const protocol::Request &request) {
+ protocol::Response response;
+
+ if (!request.params)
+ return llvm::createStringError("no resource parameters");
+
+ const json::Object *param_obj = request.params->getAsObject();
+ if (!param_obj)
+ return llvm::createStringError("no resource parameters");
+
+ const json::Value *uri = param_obj->get("uri");
+ if (!uri)
+ return llvm::createStringError("no resource uri");
+
+ llvm::StringRef uri_str = uri->getAsString().value_or("");
+ if (uri_str.empty())
+ return llvm::createStringError("no resource uri");
+
+ std::lock_guard<std::mutex> guard(m_server_mutex);
+ for (std::unique_ptr<ResourceProvider> &resource_provider_up :
+ m_resource_providers) {
+ llvm::Expected<protocol::ResourceResult> result =
+ resource_provider_up->ReadResource(uri_str);
+ if (result.errorIsA<UnsupportedURI>()) {
+ llvm::consumeError(result.takeError());
+ continue;
+ }
+ if (!result)
+ return result.takeError();
+
+ protocol::Response response;
+ response.result.emplace(std::move(*result));
+ return response;
+ }
+
+ return make_error<MCPError>(
+ llvm::formatv("no resource handler for uri: {0}", uri_str).str(), 1);
+}
diff --git a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.h b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.h
index d55882cc8ab09..e273f6e2a8d37 100644
--- a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.h
+++ b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.h
@@ -10,6 +10,7 @@
#define LLDB_PLUGINS_PROTOCOL_MCP_PROTOCOLSERVERMCP_H
#include "Protocol.h"
+#include "Resource.h"
#include "Tool.h"
#include "lldb/Core/ProtocolServer.h"
#include "lldb/Host/MainLoop.h"
@@ -46,6 +47,8 @@ class ProtocolServerMCP : public ProtocolServer {
std::function<void(const protocol::Notification &)>;
void AddTool(std::unique_ptr<Tool> tool);
+ void AddResourceProvider(std::unique_ptr<ResourceProvider> resource_provider);
+
void AddRequestHandler(llvm::StringRef method, RequestHandler handler);
void AddNotificationHandler(llvm::StringRef method,
NotificationHandler handler);
@@ -61,11 +64,17 @@ class ProtocolServerMCP : public ProtocolServer {
llvm::Expected<protocol::Response>
InitializeHandler(const protocol::Request &);
+
llvm::Expected<protocol::Response>
ToolsListHandler(const protocol::Request &);
llvm::Expected<protocol::Response>
ToolsCallHandler(const protocol::Request &);
+ llvm::Expected<protocol::Response>
+ ResourcesListHandler(const protocol::Request &);
+ llvm::Expected<protocol::Response>
+ ResourcesReadHandler(const protocol::Request &);
+
protocol::Capabilities GetCapabilities();
llvm::StringLiteral kName = "lldb-mcp";
@@ -89,6 +98,7 @@ class ProtocolServerMCP : public ProtocolServer {
std::mutex m_server_mutex;
llvm::StringMap<std::unique_ptr<Tool>> m_tools;
+ std::vector<std::unique_ptr<ResourceProvider>> m_resource_providers;
llvm::StringMap<RequestHandler> m_request_handlers;
llvm::StringMap<NotificationHandler> m_notification_handlers;
diff --git a/lldb/source/Plugins/Protocol/MCP/Resource.cpp b/lldb/source/Plugins/Protocol/MCP/Resource.cpp
new file mode 100644
index 0000000000000..8cb4e3916b1b1
--- /dev/null
+++ b/lldb/source/Plugins/Protocol/MCP/Resource.cpp
@@ -0,0 +1,166 @@
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Resource.h"
+#include "MCPError.h"
+#include "lldb/Core/Debugger.h"
+#include "lldb/Core/Module.h"
+
+using namespace lldb_private::mcp;
+
+template <typename... Args>
+static llvm::Error createStringError(const char *format, Args &&...args) {
+ return llvm::createStringError(
+ llvm::formatv(format, std::forward<Args>(args)...).str());
+}
+
+static llvm::Error createUnsupportedURIError(llvm::StringRef uri) {
+ return llvm::make_error<UnsupportedURI>(uri.str());
+}
+
+protocol::Resource
+DebuggerResourceProvider::GetDebuggerResource(lldb::user_id_t debugger_id) {
+ protocol::Resource resource;
+ resource.uri = llvm::formatv("lldb://debugger/{0}", debugger_id);
+ resource.name = llvm::formatv("debugger {0}", debugger_id);
+ resource.description =
+ llvm::formatv("Information about debugger instance {0}", debugger_id);
+ resource.mimeType = "application/json";
+ return resource;
+}
+
+protocol::Resource
+DebuggerResourceProvider::GetTargetResource(lldb::user_id_t debugger_id,
+ lldb::user_id_t target_id) {
+ protocol::Resource resource;
+ resource.uri =
+ llvm::formatv("lldb://debugger/{0}/target/{1}", debugger_id, target_id);
+ resource.name = llvm::formatv("target {0}", target_id);
+ resource.description =
+ llvm::formatv("Information about target {0} in debugger instance {1}",
+ target_id, debugger_id);
+ resource.mimeType = "application/json";
+ return resource;
+}
+
+std::vector<protocol::Resource> DebuggerResourceProvider::GetResources() const {
+ std::vector<protocol::Resource> resources;
+
+ const size_t num_debuggers = Debugger::GetNumDebuggers();
+ for (size_t i = 0; i < num_debuggers; ++i) {
+ lldb::DebuggerSP debugger_sp = Debugger::GetDebuggerAtIndex(i);
+ if (!debugger_sp)
+ continue;
+ resources.emplace_back(GetDebuggerResource(i));
+
+ TargetList &target_list = debugger_sp->GetTargetList();
+ const size_t num_targets = target_list.GetNumTargets();
+ for (size_t j = 0; j < num_targets; ++j) {
+ lldb::TargetSP target_sp = target_list.GetTargetAtIndex(j);
+ if (!target_sp)
+ continue;
+ resources.emplace_back(GetTargetResource(i, j));
+ }
+ }
+
+ return resources;
+}
+
+llvm::Expected<protocol::ResourceResult>
+DebuggerResourceProvider::ReadResource(llvm::StringRef uri) const {
+ auto [protocol, path] = uri.split("://");
+
+ if (protocol != "lldb")
+ return createUnsupportedURIError(uri);
+
+ llvm::SmallVector<llvm::StringRef, 4> components;
+ path.split(components, '/');
+
+ if (components.size() < 2)
+ return createUnsupportedURIError(uri);
+
+ if (components[0] != "debugger")
+ return createUnsupportedURIError(uri);
+
+ lldb::user_id_t debugger_id;
+ if (components[1].getAsInteger(0, debugger_id))
+ return createStringError("invalid debugger id '{0}': {1}", components[1],
+ path);
+
+ if (components.size() > 3) {
+ if (components[2] != "target")
+ return createUnsupportedURIError(uri);
+
+ lldb::user_id_t target_id;
+ if (components[3].getAsInteger(0, target_id))
+ return createStringError("invalid target id '{0}': {1}", components[3],
+ path);
+
+ return ReadTargetResource(uri, debugger_id, target_id);
+ }
+
+ return ReadDebuggerResource(uri, debugger_id);
+}
+
+llvm::Expected<protocol::ResourceResult>
+DebuggerResourceProvider::ReadDebuggerResource(llvm::StringRef uri,
+ lldb::user_id_t debugger_id) {
+ lldb::DebuggerSP debugger_sp = Debugger::GetDebuggerAtIndex(debugger_id);
+ if (!debugger_sp)
+ return createStringError("invalid debugger id: {0}", debugger_id);
+
+ TargetList &target_list = debugger_sp->GetTargetList();
+ const size_t num_targets = target_list.GetNumTargets();
+
+ llvm::json::Value value = llvm::json::Object{{"debugger_id", debugger_id},
+ {"num_targets", num_targets}};
+
+ std::string json = llvm::formatv("{0}", value);
+
+ protocol::ResourceContents contents;
+ contents.uri = uri;
+ contents.mimeType = "application/json";
+ contents.text = json;
+
+ protocol::ResourceResult result;
+ result.contents.push_back(contents);
+ return result;
+}
+
+llvm::Expected<protocol::ResourceResult>
+DebuggerResourceProvider::ReadTargetResource(llvm::StringRef uri,
+ lldb::user_id_t debugger_id,
+ lldb::user_id_t target_id) {
+
+ lldb::DebuggerSP debugger_sp = Debugger::GetDebuggerAtIndex(debugger_id);
+ if (!debugger_sp)
+ return createStringError("invalid debugger id: {0}", debugger_id);
+
+ TargetList &target_list = debugger_sp->GetTargetList();
+ lldb::TargetSP target_sp = target_list.GetTargetAtIndex(target_id);
+ if (!target_sp)
+ return createStringError("invalid target id: {0}", target_id);
+
+ llvm::json::Object object{
+ {"debugger_id", debugger_id},
+ {"target_id", target_id},
+ {"arch", target_sp->GetArchitecture().GetTriple().str()}};
+
+ if (Module *exe_module = target_sp->GetExecutableModulePointer())
+ object.insert({"path", exe_module->GetFileSpec().GetPath()});
+
+ llvm::json::Value value = std::move(object);
+ std::string json = llvm::formatv("{0}", value);
+
+ protocol::ResourceContents contents;
+ contents.uri = uri;
+ contents.mimeType = "application/json";
+ contents.text = json;
+
+ protocol::ResourceResult result;
+ result.contents.push_back(contents);
+ return result;
+}
diff --git a/lldb/source/Plugins/Protocol/MCP/Resource.h b/lldb/source/Plugins/Protocol/MCP/Resource.h
new file mode 100644
index 0000000000000...
[truncated]
|
fde2a5d
to
525e05d
Compare
@@ -244,6 +253,7 @@ ProtocolServerMCP::HandleData(llvm::StringRef data) { | |||
protocol::Capabilities ProtocolServerMCP::GetCapabilities() { | |||
protocol::Capabilities capabilities; | |||
capabilities.tools.listChanged = true; | |||
capabilities.resources.listChanged = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we support sending notifications when a debugger/target are added/removed? Otherwise the client could get out of sync with the state of lldb.
Maybe as a FIXME/TODO if we can't easily do that now.
@@ -30,4 +31,17 @@ class MCPError : public llvm::ErrorInfo<MCPError> { | |||
int64_t m_error_code; | |||
}; | |||
|
|||
class UnsupportedURI : public llvm::ErrorInfo<UnsupportedURI> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the MCP spec, there are 2 error codes for resources:
- Resource not found: -32002
- Internal errors: -32603
See https://modelcontextprotocol.io/specification/2025-06-18/server/resources#error-handling
Should we use those here?
std::optional<std::string> description; | ||
|
||
/// The MIME type of this resource, if known. | ||
std::optional<std::string> mimeType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been using std::optional
less in lldb-dap's protocols because here isn't much of a meaningful difference between an empty string and std::nullopt
. Not a required change, but I find it makes it easier to think about since I don't have to double check if field != std::nullopt && !field->empty()
, I can just do !field.empty()
.
When encoding toJSON
we can check if the string is empty and not include it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me do this as a followup for all of MCP 👍
TargetList &target_list = debugger_sp->GetTargetList(); | ||
const size_t num_targets = target_list.GetNumTargets(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think its possible to add/remove targets from the debugger. Is there a more stable way to identify them than their position in the target list?
If you remove a target, say target 2 of 3, does the list compact?
Just wondering what happens if this list is out of sync with the client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe we have anything more stable. My hope is to rely on the listChanged notification in the future, so the client knows the resource went away.
|
||
llvm::Expected<protocol::ResourceResult> | ||
DebuggerResourceProvider::ReadResource(llvm::StringRef uri) const { | ||
auto [protocol, path] = uri.split("://"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I almost wonder if we could use
static std::optional<URI> Parse(llvm::StringRef uri); |
but I don't think we're planning on setting the host at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it treats the first component as the hostname, which is why I didn't use it. Not sure if there's a good way to distinguish the two.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to add the target to the input scheme?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't because, unlike for the debugger, you can do target select
.
lldb::user_id_t target_id; | ||
if (components[3].getAsInteger(0, target_id)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it would be helpful to have support for lldb://debugger/0/target/selected
as a shorthand for the selected target.
Or in theory lldb://debugger/0/target
could return info for the selected target.
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}" } ```
525e05d
to
c9f3a68
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
) 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}" } ``` (cherry picked from commit 3c4c2fa)
Expose debuggers and target as resources through MCP. This has two advantages:
This PR exposes a resource for debuggers and targets.
The following URI returns information about a given debugger instance:
For example:
The following URI returns information about a given target:
For example: