-
Notifications
You must be signed in to change notification settings - Fork 14.9k
[lldb-mcp] Adding a tool to list debuggers again. #158340
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: John Harrison (ashgti) ChangesThis brings back the tool for listing debuggers. This is helpful when an LLM doesn't support resources, like gemini-cli. I also fixed an issue with Full diff: https://github.com/llvm/llvm-project/pull/158340.diff 5 Files Affected:
diff --git a/lldb/include/lldb/Protocol/MCP/Server.h b/lldb/include/lldb/Protocol/MCP/Server.h
index b674d58159550..1f916ae525b5c 100644
--- a/lldb/include/lldb/Protocol/MCP/Server.h
+++ b/lldb/include/lldb/Protocol/MCP/Server.h
@@ -108,8 +108,7 @@ bool fromJSON(const llvm::json::Value &, ServerInfo &, llvm::json::Path);
/// once it is no longer referenced.
class ServerInfoHandle {
public:
- ServerInfoHandle();
- explicit ServerInfoHandle(llvm::StringRef filename);
+ explicit ServerInfoHandle(llvm::StringRef filename = "");
~ServerInfoHandle();
ServerInfoHandle(ServerInfoHandle &&other);
@@ -121,6 +120,9 @@ class ServerInfoHandle {
ServerInfoHandle &operator=(const ServerInfoHandle &) = delete;
/// @}
+ /// Remove the file.
+ void Remove();
+
private:
llvm::SmallString<128> m_filename;
};
diff --git a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp
index dc18c8e06803a..d3af3cf25c4a1 100644
--- a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp
+++ b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp
@@ -10,8 +10,6 @@
#include "Resource.h"
#include "Tool.h"
#include "lldb/Core/PluginManager.h"
-#include "lldb/Host/FileSystem.h"
-#include "lldb/Host/HostInfo.h"
#include "lldb/Protocol/MCP/Server.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
@@ -60,7 +58,9 @@ void ProtocolServerMCP::Extend(lldb_protocol::mcp::Server &server) const {
"MCP initialization complete");
});
server.AddTool(
- std::make_unique<CommandTool>("lldb_command", "Run an lldb command."));
+ std::make_unique<CommandTool>("command", "Run an lldb command."));
+ server.AddTool(std::make_unique<DebuggerListTool>(
+ "debugger_list", "List debugger instances with their debugger_id."));
server.AddResourceProvider(std::make_unique<DebuggerResourceProvider>());
}
@@ -145,8 +145,8 @@ llvm::Error ProtocolServerMCP::Stop() {
if (m_loop_thread.joinable())
m_loop_thread.join();
+ m_server_info_handle.Remove();
m_listen_handlers.clear();
- m_server_info_handle = ServerInfoHandle();
m_instances.clear();
return llvm::Error::success();
diff --git a/lldb/source/Plugins/Protocol/MCP/Tool.cpp b/lldb/source/Plugins/Protocol/MCP/Tool.cpp
index 2f451bf76e81d..7250ed3a5518d 100644
--- a/lldb/source/Plugins/Protocol/MCP/Tool.cpp
+++ b/lldb/source/Plugins/Protocol/MCP/Tool.cpp
@@ -7,26 +7,34 @@
//===----------------------------------------------------------------------===//
#include "Tool.h"
+#include "lldb/Core/Debugger.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Protocol/MCP/Protocol.h"
+#include "lldb/Utility/UriParser.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include <cstdint>
+#include <optional>
using namespace lldb_private;
using namespace lldb_protocol;
using namespace lldb_private::mcp;
+using namespace lldb;
using namespace llvm;
namespace {
+
struct CommandToolArguments {
- uint64_t debugger_id;
- std::string arguments;
+ /// Either an id like '1' or a uri like 'lldb://sessions/1'.
+ std::string debugger;
+ std::string command;
};
-bool fromJSON(const llvm::json::Value &V, CommandToolArguments &A,
- llvm::json::Path P) {
- llvm::json::ObjectMapper O(V, P);
- return O && O.map("debugger_id", A.debugger_id) &&
- O.mapOptional("arguments", A.arguments);
+bool fromJSON(const json::Value &V, CommandToolArguments &A, json::Path P) {
+ json::ObjectMapper O(V, P);
+ return O && O.mapOptional("debugger", A.debugger) &&
+ O.mapOptional("command", A.command);
}
/// Helper function to create a CallToolResult from a string output.
@@ -39,9 +47,15 @@ createTextResult(std::string output, bool is_error = false) {
return text_result;
}
+static constexpr StringLiteral kSchemeAndHost = "lldb-mcp://debugger/";
+
+std::string to_uri(DebuggerSP debugger) {
+ return (kSchemeAndHost + std::to_string(debugger->GetID())).str();
+}
+
} // namespace
-llvm::Expected<lldb_protocol::mcp::CallToolResult>
+Expected<lldb_protocol::mcp::CallToolResult>
CommandTool::Call(const lldb_protocol::mcp::ToolArguments &args) {
if (!std::holds_alternative<json::Value>(args))
return createStringError("CommandTool requires arguments");
@@ -52,19 +66,35 @@ CommandTool::Call(const lldb_protocol::mcp::ToolArguments &args) {
if (!fromJSON(std::get<json::Value>(args), arguments, root))
return root.getError();
- lldb::DebuggerSP debugger_sp =
- Debugger::FindDebuggerWithID(arguments.debugger_id);
+ lldb::DebuggerSP debugger_sp;
+
+ if (!arguments.debugger.empty()) {
+ llvm::StringRef debugger_specifier = arguments.debugger;
+ debugger_specifier.consume_front(kSchemeAndHost);
+ uint32_t debugger_id = 0;
+ if (debugger_specifier.consumeInteger(10, debugger_id))
+ return createStringError(
+ formatv("malformed debugger specifier {0}", arguments.debugger));
+
+ debugger_sp = Debugger::FindDebuggerWithID(debugger_id);
+ } else {
+ for (size_t i = 0; i < Debugger::GetNumDebuggers(); i++) {
+ debugger_sp = Debugger::GetDebuggerAtIndex(i);
+ if (debugger_sp)
+ break;
+ }
+ }
+
if (!debugger_sp)
- return createStringError(
- llvm::formatv("no debugger with id {0}", arguments.debugger_id));
+ return createStringError("no debugger found");
// FIXME: Disallow certain commands and their aliases.
CommandReturnObject result(/*colors=*/false);
- debugger_sp->GetCommandInterpreter().HandleCommand(
- arguments.arguments.c_str(), eLazyBoolYes, result);
+ debugger_sp->GetCommandInterpreter().HandleCommand(arguments.command.c_str(),
+ eLazyBoolYes, result);
std::string output;
- llvm::StringRef output_str = result.GetOutputString();
+ StringRef output_str = result.GetOutputString();
if (!output_str.empty())
output += output_str.str();
@@ -78,14 +108,42 @@ CommandTool::Call(const lldb_protocol::mcp::ToolArguments &args) {
return createTextResult(output, !result.Succeeded());
}
-std::optional<llvm::json::Value> CommandTool::GetSchema() const {
- llvm::json::Object id_type{{"type", "number"}};
- llvm::json::Object str_type{{"type", "string"}};
- llvm::json::Object properties{{"debugger_id", std::move(id_type)},
- {"arguments", std::move(str_type)}};
- llvm::json::Array required{"debugger_id"};
- llvm::json::Object schema{{"type", "object"},
- {"properties", std::move(properties)},
- {"required", std::move(required)}};
+std::optional<json::Value> CommandTool::GetSchema() const {
+ using namespace llvm::json;
+ Object properties{
+ {"debugger",
+ Object{{"type", "string"},
+ {"description",
+ "The debugger ID or URI to a specific debug session. If not "
+ "specified, the first debugger will be used."}}},
+ {"command",
+ Object{{"type", "string"}, {"description", "An lldb command to run."}}}};
+ Object schema{{"type", "object"}, {"properties", std::move(properties)}};
return schema;
}
+
+Expected<lldb_protocol::mcp::CallToolResult>
+DebuggerListTool::Call(const lldb_protocol::mcp::ToolArguments &args) {
+ llvm::json::Path::Root root;
+
+ // Return a nested Markdown list with debuggers and target.
+ // Example output:
+ //
+ // - lldb-mcp://debugger/0
+ // - lldb-mcp://debugger/1
+ //
+ // FIXME: Use Structured Content when we adopt protocol version 2025-06-18.
+ std::string output;
+ llvm::raw_string_ostream os(output);
+
+ 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;
+
+ os << "- " << to_uri(debugger_sp) << '\n';
+ }
+
+ return createTextResult(output);
+}
diff --git a/lldb/source/Plugins/Protocol/MCP/Tool.h b/lldb/source/Plugins/Protocol/MCP/Tool.h
index 1886525b9168f..8450ce3d6c2dd 100644
--- a/lldb/source/Plugins/Protocol/MCP/Tool.h
+++ b/lldb/source/Plugins/Protocol/MCP/Tool.h
@@ -28,6 +28,15 @@ class CommandTool : public lldb_protocol::mcp::Tool {
std::optional<llvm::json::Value> GetSchema() const override;
};
+class DebuggerListTool : public lldb_protocol::mcp::Tool {
+public:
+ using lldb_protocol::mcp::Tool::Tool;
+ ~DebuggerListTool() = default;
+
+ llvm::Expected<lldb_protocol::mcp::CallToolResult>
+ Call(const lldb_protocol::mcp::ToolArguments &args) override;
+};
+
} // namespace lldb_private::mcp
#endif
diff --git a/lldb/source/Protocol/MCP/Server.cpp b/lldb/source/Protocol/MCP/Server.cpp
index f3489c620832f..a08874e7321af 100644
--- a/lldb/source/Protocol/MCP/Server.cpp
+++ b/lldb/source/Protocol/MCP/Server.cpp
@@ -22,34 +22,32 @@ using namespace llvm;
using namespace lldb_private;
using namespace lldb_protocol::mcp;
-ServerInfoHandle::ServerInfoHandle() : ServerInfoHandle("") {}
-
ServerInfoHandle::ServerInfoHandle(StringRef filename) : m_filename(filename) {
if (!m_filename.empty())
sys::RemoveFileOnSignal(m_filename);
}
-ServerInfoHandle::~ServerInfoHandle() {
- if (m_filename.empty())
- return;
-
- sys::fs::remove(m_filename);
- sys::DontRemoveFileOnSignal(m_filename);
- m_filename.clear();
-}
+ServerInfoHandle::~ServerInfoHandle() { Remove(); }
-ServerInfoHandle::ServerInfoHandle(ServerInfoHandle &&other)
- : m_filename(other.m_filename) {
+ServerInfoHandle::ServerInfoHandle(ServerInfoHandle &&other) {
*this = std::move(other);
}
ServerInfoHandle &
ServerInfoHandle::operator=(ServerInfoHandle &&other) noexcept {
- m_filename = other.m_filename;
- other.m_filename.clear();
+ m_filename = std::move(other.m_filename);
return *this;
}
+void ServerInfoHandle::Remove() {
+ if (m_filename.empty())
+ return;
+
+ sys::fs::remove(m_filename);
+ sys::DontRemoveFileOnSignal(m_filename);
+ m_filename.clear();
+}
+
json::Value lldb_protocol::mcp::toJSON(const ServerInfo &SM) {
return json::Object{{"connection_uri", SM.connection_uri}};
}
|
This brings back the tool for listing debuggers. This is helpful when an LLM doesn't support resources, like gemini-cli.
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.
Not directly related to this PR, but something I'm planning to address that will affect this. I've gotten some reports that nearly all models struggle with the debugger IDs being 1-based. Some of them are smart enough to try 1 if 0 fails, but others just give up. I'm planning to make them zero based at the MCP layer.
I filed #158676 for the index issue so we don't loose track of it. |
This brings back the tool for listing debuggers.
This is helpful when an LLM doesn't support resources, like gemini-cli.
I also fixed an issue with
ServerInfoHandle
not always cleaning up the~/lldb/lldb-mcp-<pid>.json
file correctly.