[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