[Lldb-commits] [lldb] [lldb-mcp] Adding a tool to list debuggers again. (PR #158340)

John Harrison via lldb-commits lldb-commits at lists.llvm.org
Fri Sep 12 11:13:42 PDT 2025


https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/158340

>From 465f9379664cd9178fd226967da9a6ab680cbf2a Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Fri, 12 Sep 2025 11:10:31 -0700
Subject: [PATCH 1/2] [lldb-mcp] Adding a tool to list debuggers again.

This brings back the tool for listing debuggers.

This is helpful when an LLM doesn't support resources, like gemini-cli.
---
 lldb/include/lldb/Protocol/MCP/Server.h       |   6 +-
 .../Protocol/MCP/ProtocolServerMCP.cpp        |   8 +-
 lldb/source/Plugins/Protocol/MCP/Tool.cpp     | 106 ++++++++++++++----
 lldb/source/Plugins/Protocol/MCP/Tool.h       |   9 ++
 lldb/source/Protocol/MCP/Server.cpp           |  26 ++---
 5 files changed, 111 insertions(+), 44 deletions(-)

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}};
 }

>From 7f2d0fc035856234fe49e10f15b9bb0c13721652 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Fri, 12 Sep 2025 11:13:29 -0700
Subject: [PATCH 2/2] Adjusting some comments for consistency.

---
 lldb/source/Plugins/Protocol/MCP/Tool.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lldb/source/Plugins/Protocol/MCP/Tool.cpp b/lldb/source/Plugins/Protocol/MCP/Tool.cpp
index 7250ed3a5518d..cb134b965c2e2 100644
--- a/lldb/source/Plugins/Protocol/MCP/Tool.cpp
+++ b/lldb/source/Plugins/Protocol/MCP/Tool.cpp
@@ -25,8 +25,10 @@ using namespace llvm;
 
 namespace {
 
+static constexpr StringLiteral kSchemeAndHost = "lldb-mcp://debugger/";
+
 struct CommandToolArguments {
-  /// Either an id like '1' or a uri like 'lldb://sessions/1'.
+  /// Either an id like '1' or a uri like 'lldb-mcp://debugger/1'.
   std::string debugger;
   std::string command;
 };
@@ -47,8 +49,6 @@ 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();
 }
@@ -129,8 +129,8 @@ DebuggerListTool::Call(const lldb_protocol::mcp::ToolArguments &args) {
   // Return a nested Markdown list with debuggers and target.
   // Example output:
   //
-  // - lldb-mcp://debugger/0
   // - lldb-mcp://debugger/1
+  // - lldb-mcp://debugger/2
   //
   // FIXME: Use Structured Content when we adopt protocol version 2025-06-18.
   std::string output;



More information about the lldb-commits mailing list