[Lldb-commits] [lldb] e8abdfc - [lldb] Make MCP server instance global (#145616)
via lldb-commits
lldb-commits at lists.llvm.org
Wed Jun 25 13:46:37 PDT 2025
Author: Jonas Devlieghere
Date: 2025-06-25T13:46:33-07:00
New Revision: e8abdfc88ffed632750fe0fd7deafb577e902bd6
URL: https://github.com/llvm/llvm-project/commit/e8abdfc88ffed632750fe0fd7deafb577e902bd6
DIFF: https://github.com/llvm/llvm-project/commit/e8abdfc88ffed632750fe0fd7deafb577e902bd6.diff
LOG: [lldb] Make MCP server instance global (#145616)
Rather than having one MCP server per debugger, make the MCP server
global and pass a debugger id along with tool invocations that require
one. This PR also adds a second tool to list the available debuggers
with their targets so the model can decide which debugger instance to
use.
Added:
Modified:
lldb/include/lldb/Core/Debugger.h
lldb/include/lldb/Core/ProtocolServer.h
lldb/include/lldb/lldb-forward.h
lldb/include/lldb/lldb-private-interfaces.h
lldb/source/Commands/CommandObjectProtocolServer.cpp
lldb/source/Core/Debugger.cpp
lldb/source/Core/ProtocolServer.cpp
lldb/source/Plugins/Protocol/MCP/Protocol.h
lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp
lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.h
lldb/source/Plugins/Protocol/MCP/Tool.cpp
lldb/source/Plugins/Protocol/MCP/Tool.h
lldb/unittests/Protocol/ProtocolMCPServerTest.cpp
Removed:
################################################################################
diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h
index 9f82466a83417..2087ef2a11562 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -602,10 +602,6 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
void FlushProcessOutput(Process &process, bool flush_stdout,
bool flush_stderr);
- void AddProtocolServer(lldb::ProtocolServerSP protocol_server_sp);
- void RemoveProtocolServer(lldb::ProtocolServerSP protocol_server_sp);
- lldb::ProtocolServerSP GetProtocolServer(llvm::StringRef protocol) const;
-
SourceManager::SourceFileCache &GetSourceFileCache() {
return m_source_file_cache;
}
@@ -776,8 +772,6 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
mutable std::mutex m_progress_reports_mutex;
/// @}
- llvm::SmallVector<lldb::ProtocolServerSP> m_protocol_servers;
-
std::mutex m_destroy_callback_mutex;
lldb::callback_token_t m_destroy_callback_next_token = 0;
struct DestroyCallbackInfo {
diff --git a/lldb/include/lldb/Core/ProtocolServer.h b/lldb/include/lldb/Core/ProtocolServer.h
index fafe460904323..937256c10aec1 100644
--- a/lldb/include/lldb/Core/ProtocolServer.h
+++ b/lldb/include/lldb/Core/ProtocolServer.h
@@ -20,8 +20,9 @@ class ProtocolServer : public PluginInterface {
ProtocolServer() = default;
virtual ~ProtocolServer() = default;
- static lldb::ProtocolServerSP Create(llvm::StringRef name,
- Debugger &debugger);
+ static ProtocolServer *GetOrCreate(llvm::StringRef name);
+
+ static std::vector<llvm::StringRef> GetSupportedProtocols();
struct Connection {
Socket::SocketProtocol protocol;
diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index 558818e8e2309..2bc85a2d2afa6 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -391,7 +391,7 @@ typedef std::shared_ptr<lldb_private::Platform> PlatformSP;
typedef std::shared_ptr<lldb_private::Process> ProcessSP;
typedef std::shared_ptr<lldb_private::ProcessAttachInfo> ProcessAttachInfoSP;
typedef std::shared_ptr<lldb_private::ProcessLaunchInfo> ProcessLaunchInfoSP;
-typedef std::shared_ptr<lldb_private::ProtocolServer> ProtocolServerSP;
+typedef std::unique_ptr<lldb_private::ProtocolServer> ProtocolServerUP;
typedef std::weak_ptr<lldb_private::Process> ProcessWP;
typedef std::shared_ptr<lldb_private::RegisterCheckpoint> RegisterCheckpointSP;
typedef std::shared_ptr<lldb_private::RegisterContext> RegisterContextSP;
diff --git a/lldb/include/lldb/lldb-private-interfaces.h b/lldb/include/lldb/lldb-private-interfaces.h
index 34eaaa8e581e9..249b25c251ac2 100644
--- a/lldb/include/lldb/lldb-private-interfaces.h
+++ b/lldb/include/lldb/lldb-private-interfaces.h
@@ -81,8 +81,7 @@ typedef lldb::PlatformSP (*PlatformCreateInstance)(bool force,
typedef lldb::ProcessSP (*ProcessCreateInstance)(
lldb::TargetSP target_sp, lldb::ListenerSP listener_sp,
const FileSpec *crash_file_path, bool can_connect);
-typedef lldb::ProtocolServerSP (*ProtocolServerCreateInstance)(
- Debugger &debugger);
+typedef lldb::ProtocolServerUP (*ProtocolServerCreateInstance)();
typedef lldb::RegisterTypeBuilderSP (*RegisterTypeBuilderCreateInstance)(
Target &target);
typedef lldb::ScriptInterpreterSP (*ScriptInterpreterCreateInstance)(
diff --git a/lldb/source/Commands/CommandObjectProtocolServer.cpp b/lldb/source/Commands/CommandObjectProtocolServer.cpp
index 115754769f3e3..55bd42ed1a533 100644
--- a/lldb/source/Commands/CommandObjectProtocolServer.cpp
+++ b/lldb/source/Commands/CommandObjectProtocolServer.cpp
@@ -23,20 +23,6 @@ using namespace lldb_private;
#define LLDB_OPTIONS_mcp
#include "CommandOptions.inc"
-static std::vector<llvm::StringRef> GetSupportedProtocols() {
- std::vector<llvm::StringRef> supported_protocols;
- size_t i = 0;
-
- for (llvm::StringRef protocol_name =
- PluginManager::GetProtocolServerPluginNameAtIndex(i++);
- !protocol_name.empty();
- protocol_name = PluginManager::GetProtocolServerPluginNameAtIndex(i++)) {
- supported_protocols.push_back(protocol_name);
- }
-
- return supported_protocols;
-}
-
class CommandObjectProtocolServerStart : public CommandObjectParsed {
public:
CommandObjectProtocolServerStart(CommandInterpreter &interpreter)
@@ -57,12 +43,11 @@ class CommandObjectProtocolServerStart : public CommandObjectParsed {
}
llvm::StringRef protocol = args.GetArgumentAtIndex(0);
- std::vector<llvm::StringRef> supported_protocols = GetSupportedProtocols();
- if (llvm::find(supported_protocols, protocol) ==
- supported_protocols.end()) {
+ ProtocolServer *server = ProtocolServer::GetOrCreate(protocol);
+ if (!server) {
result.AppendErrorWithFormatv(
"unsupported protocol: {0}. Supported protocols are: {1}", protocol,
- llvm::join(GetSupportedProtocols(), ", "));
+ llvm::join(ProtocolServer::GetSupportedProtocols(), ", "));
return;
}
@@ -72,10 +57,6 @@ class CommandObjectProtocolServerStart : public CommandObjectParsed {
}
llvm::StringRef connection_uri = args.GetArgumentAtIndex(1);
- ProtocolServerSP server_sp = GetDebugger().GetProtocolServer(protocol);
- if (!server_sp)
- server_sp = ProtocolServer::Create(protocol, GetDebugger());
-
const char *connection_error =
"unsupported connection specifier, expected 'accept:///path' or "
"'listen://[host]:port', got '{0}'.";
@@ -98,14 +79,12 @@ class CommandObjectProtocolServerStart : public CommandObjectParsed {
formatv("[{0}]:{1}", uri->hostname.empty() ? "0.0.0.0" : uri->hostname,
uri->port.value_or(0));
- if (llvm::Error error = server_sp->Start(connection)) {
+ if (llvm::Error error = server->Start(connection)) {
result.AppendErrorWithFormatv("{0}", llvm::fmt_consume(std::move(error)));
return;
}
- GetDebugger().AddProtocolServer(server_sp);
-
- if (Socket *socket = server_sp->GetSocket()) {
+ if (Socket *socket = server->GetSocket()) {
std::string address =
llvm::join(socket->GetListeningConnectionURI(), ", ");
result.AppendMessageWithFormatv(
@@ -134,30 +113,18 @@ class CommandObjectProtocolServerStop : public CommandObjectParsed {
}
llvm::StringRef protocol = args.GetArgumentAtIndex(0);
- std::vector<llvm::StringRef> supported_protocols = GetSupportedProtocols();
- if (llvm::find(supported_protocols, protocol) ==
- supported_protocols.end()) {
+ ProtocolServer *server = ProtocolServer::GetOrCreate(protocol);
+ if (!server) {
result.AppendErrorWithFormatv(
"unsupported protocol: {0}. Supported protocols are: {1}", protocol,
- llvm::join(GetSupportedProtocols(), ", "));
+ llvm::join(ProtocolServer::GetSupportedProtocols(), ", "));
return;
}
- Debugger &debugger = GetDebugger();
-
- ProtocolServerSP server_sp = debugger.GetProtocolServer(protocol);
- if (!server_sp) {
- result.AppendError(
- llvm::formatv("no {0} protocol server running", protocol).str());
- return;
- }
-
- if (llvm::Error error = server_sp->Stop()) {
+ if (llvm::Error error = server->Stop()) {
result.AppendErrorWithFormatv("{0}", llvm::fmt_consume(std::move(error)));
return;
}
-
- debugger.RemoveProtocolServer(server_sp);
}
};
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index 33d1053fd8a65..445baf1f63785 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -2376,26 +2376,3 @@ llvm::ThreadPoolInterface &Debugger::GetThreadPool() {
"Debugger::GetThreadPool called before Debugger::Initialize");
return *g_thread_pool;
}
-
-void Debugger::AddProtocolServer(lldb::ProtocolServerSP protocol_server_sp) {
- assert(protocol_server_sp &&
- GetProtocolServer(protocol_server_sp->GetPluginName()) == nullptr);
- m_protocol_servers.push_back(protocol_server_sp);
-}
-
-void Debugger::RemoveProtocolServer(lldb::ProtocolServerSP protocol_server_sp) {
- auto it = llvm::find(m_protocol_servers, protocol_server_sp);
- if (it != m_protocol_servers.end())
- m_protocol_servers.erase(it);
-}
-
-lldb::ProtocolServerSP
-Debugger::GetProtocolServer(llvm::StringRef protocol) const {
- for (ProtocolServerSP protocol_server_sp : m_protocol_servers) {
- if (!protocol_server_sp)
- continue;
- if (protocol_server_sp->GetPluginName() == protocol)
- return protocol_server_sp;
- }
- return nullptr;
-}
diff --git a/lldb/source/Core/ProtocolServer.cpp b/lldb/source/Core/ProtocolServer.cpp
index d57a047afa7b2..41636cdacdecc 100644
--- a/lldb/source/Core/ProtocolServer.cpp
+++ b/lldb/source/Core/ProtocolServer.cpp
@@ -12,10 +12,36 @@
using namespace lldb_private;
using namespace lldb;
-ProtocolServerSP ProtocolServer::Create(llvm::StringRef name,
- Debugger &debugger) {
+ProtocolServer *ProtocolServer::GetOrCreate(llvm::StringRef name) {
+ static std::mutex g_mutex;
+ static llvm::StringMap<ProtocolServerUP> g_protocol_server_instances;
+
+ std::lock_guard<std::mutex> guard(g_mutex);
+
+ auto it = g_protocol_server_instances.find(name);
+ if (it != g_protocol_server_instances.end())
+ return it->second.get();
+
if (ProtocolServerCreateInstance create_callback =
- PluginManager::GetProtocolCreateCallbackForPluginName(name))
- return create_callback(debugger);
+ PluginManager::GetProtocolCreateCallbackForPluginName(name)) {
+ auto pair =
+ g_protocol_server_instances.try_emplace(name, create_callback());
+ return pair.first->second.get();
+ }
+
return nullptr;
}
+
+std::vector<llvm::StringRef> ProtocolServer::GetSupportedProtocols() {
+ std::vector<llvm::StringRef> supported_protocols;
+ size_t i = 0;
+
+ for (llvm::StringRef protocol_name =
+ PluginManager::GetProtocolServerPluginNameAtIndex(i++);
+ !protocol_name.empty();
+ protocol_name = PluginManager::GetProtocolServerPluginNameAtIndex(i++)) {
+ supported_protocols.push_back(protocol_name);
+ }
+
+ return supported_protocols;
+}
diff --git a/lldb/source/Plugins/Protocol/MCP/Protocol.h b/lldb/source/Plugins/Protocol/MCP/Protocol.h
index e315899406573..cb790dc4e5596 100644
--- a/lldb/source/Plugins/Protocol/MCP/Protocol.h
+++ b/lldb/source/Plugins/Protocol/MCP/Protocol.h
@@ -123,6 +123,8 @@ using Message = std::variant<Request, Response, Notification, Error>;
bool fromJSON(const llvm::json::Value &, Message &, llvm::json::Path);
llvm::json::Value toJSON(const Message &);
+using ToolArguments = std::variant<std::monostate, llvm::json::Value>;
+
} // namespace lldb_private::mcp::protocol
#endif
diff --git a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp
index c3cd9a88c20bf..3180341b50b91 100644
--- a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp
+++ b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp
@@ -24,8 +24,7 @@ LLDB_PLUGIN_DEFINE(ProtocolServerMCP)
static constexpr size_t kChunkSize = 1024;
-ProtocolServerMCP::ProtocolServerMCP(Debugger &debugger)
- : ProtocolServer(), m_debugger(debugger) {
+ProtocolServerMCP::ProtocolServerMCP() : ProtocolServer() {
AddRequestHandler("initialize",
std::bind(&ProtocolServerMCP::InitializeHandler, this,
std::placeholders::_1));
@@ -39,8 +38,10 @@ ProtocolServerMCP::ProtocolServerMCP(Debugger &debugger)
"notifications/initialized", [](const protocol::Notification &) {
LLDB_LOG(GetLog(LLDBLog::Host), "MCP initialization complete");
});
- AddTool(std::make_unique<LLDBCommandTool>(
- "lldb_command", "Run an lldb command.", m_debugger));
+ 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."));
}
ProtocolServerMCP::~ProtocolServerMCP() { llvm::consumeError(Stop()); }
@@ -54,8 +55,8 @@ void ProtocolServerMCP::Terminate() {
PluginManager::UnregisterPlugin(CreateInstance);
}
-lldb::ProtocolServerSP ProtocolServerMCP::CreateInstance(Debugger &debugger) {
- return std::make_shared<ProtocolServerMCP>(debugger);
+lldb::ProtocolServerUP ProtocolServerMCP::CreateInstance() {
+ return std::make_unique<ProtocolServerMCP>();
}
llvm::StringRef ProtocolServerMCP::GetPluginDescriptionStatic() {
@@ -145,7 +146,7 @@ llvm::Error ProtocolServerMCP::Start(ProtocolServer::Connection connection) {
std::lock_guard<std::mutex> guard(m_server_mutex);
if (m_running)
- return llvm::createStringError("server already running");
+ return llvm::createStringError("the MCP server is already running");
Status status;
m_listener = Socket::Create(connection.protocol, status);
@@ -162,10 +163,10 @@ llvm::Error ProtocolServerMCP::Start(ProtocolServer::Connection connection) {
if (llvm::Error error = handles.takeError())
return error;
+ m_running = true;
m_listen_handlers = std::move(*handles);
m_loop_thread = std::thread([=] {
- llvm::set_thread_name(
- llvm::formatv("debugger-{0}.mcp.runloop", m_debugger.GetID()));
+ llvm::set_thread_name("protocol-server.mcp");
m_loop.Run();
});
@@ -175,6 +176,8 @@ llvm::Error ProtocolServerMCP::Start(ProtocolServer::Connection connection) {
llvm::Error ProtocolServerMCP::Stop() {
{
std::lock_guard<std::mutex> guard(m_server_mutex);
+ if (!m_running)
+ return createStringError("the MCP sever is not running");
m_running = false;
}
@@ -311,11 +314,12 @@ ProtocolServerMCP::ToolsCallHandler(const protocol::Request &request) {
if (it == m_tools.end())
return llvm::createStringError(llvm::formatv("no tool \"{0}\"", tool_name));
- const json::Value *args = param_obj->get("arguments");
- if (!args)
- return llvm::createStringError("no tool arguments");
+ protocol::ToolArguments tool_args;
+ if (const json::Value *args = param_obj->get("arguments"))
+ tool_args = *args;
- llvm::Expected<protocol::TextResult> text_result = it->second->Call(*args);
+ llvm::Expected<protocol::TextResult> text_result =
+ it->second->Call(tool_args);
if (!text_result)
return text_result.takeError();
diff --git a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.h b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.h
index 52bb92a04a802..d55882cc8ab09 100644
--- a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.h
+++ b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.h
@@ -21,7 +21,7 @@ namespace lldb_private::mcp {
class ProtocolServerMCP : public ProtocolServer {
public:
- ProtocolServerMCP(Debugger &debugger);
+ ProtocolServerMCP();
virtual ~ProtocolServerMCP() override;
virtual llvm::Error Start(ProtocolServer::Connection connection) override;
@@ -33,7 +33,7 @@ class ProtocolServerMCP : public ProtocolServer {
static llvm::StringRef GetPluginNameStatic() { return "MCP"; }
static llvm::StringRef GetPluginDescriptionStatic();
- static lldb::ProtocolServerSP CreateInstance(Debugger &debugger);
+ static lldb::ProtocolServerUP CreateInstance();
llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
@@ -71,8 +71,6 @@ class ProtocolServerMCP : public ProtocolServer {
llvm::StringLiteral kName = "lldb-mcp";
llvm::StringLiteral kVersion = "0.1.0";
- Debugger &m_debugger;
-
bool m_running = false;
MainLoop m_loop;
diff --git a/lldb/source/Plugins/Protocol/MCP/Tool.cpp b/lldb/source/Plugins/Protocol/MCP/Tool.cpp
index de8fcc8f3cb4c..5c4626cf66b32 100644
--- a/lldb/source/Plugins/Protocol/MCP/Tool.cpp
+++ b/lldb/source/Plugins/Protocol/MCP/Tool.cpp
@@ -7,22 +7,38 @@
//===----------------------------------------------------------------------===//
#include "Tool.h"
+#include "lldb/Core/Module.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandReturnObject.h"
using namespace lldb_private::mcp;
using namespace llvm;
-struct LLDBCommandToolArguments {
+namespace {
+struct CommandToolArguments {
+ uint64_t debugger_id;
std::string arguments;
};
-bool fromJSON(const llvm::json::Value &V, LLDBCommandToolArguments &A,
+bool fromJSON(const llvm::json::Value &V, CommandToolArguments &A,
llvm::json::Path P) {
llvm::json::ObjectMapper O(V, P);
- return O && O.map("arguments", A.arguments);
+ return O && O.map("debugger_id", A.debugger_id) &&
+ O.mapOptional("arguments", A.arguments);
}
+/// Helper function to create a TextResult from a string output.
+static lldb_private::mcp::protocol::TextResult
+createTextResult(std::string output, bool is_error = false) {
+ lldb_private::mcp::protocol::TextResult text_result;
+ text_result.content.emplace_back(
+ lldb_private::mcp::protocol::TextContent{{std::move(output)}});
+ text_result.isError = is_error;
+ return text_result;
+}
+
+} // namespace
+
Tool::Tool(std::string name, std::string description)
: m_name(std::move(name)), m_description(std::move(description)) {}
@@ -37,22 +53,27 @@ protocol::ToolDefinition Tool::GetDefinition() const {
return definition;
}
-LLDBCommandTool::LLDBCommandTool(std::string name, std::string description,
- Debugger &debugger)
- : Tool(std::move(name), std::move(description)), m_debugger(debugger) {}
-
llvm::Expected<protocol::TextResult>
-LLDBCommandTool::Call(const llvm::json::Value &args) {
- llvm::json::Path::Root root;
+CommandTool::Call(const protocol::ToolArguments &args) {
+ if (!std::holds_alternative<json::Value>(args))
+ return createStringError("CommandTool requires arguments");
+
+ json::Path::Root root;
- LLDBCommandToolArguments arguments;
- if (!fromJSON(args, arguments, root))
+ CommandToolArguments arguments;
+ if (!fromJSON(std::get<json::Value>(args), arguments, root))
return root.getError();
+ lldb::DebuggerSP debugger_sp =
+ Debugger::GetDebuggerAtIndex(arguments.debugger_id);
+ if (!debugger_sp)
+ return createStringError(
+ llvm::formatv("no debugger with id {0}", arguments.debugger_id));
+
// FIXME: Disallow certain commands and their aliases.
CommandReturnObject result(/*colors=*/false);
- m_debugger.GetCommandInterpreter().HandleCommand(arguments.arguments.c_str(),
- eLazyBoolYes, result);
+ debugger_sp->GetCommandInterpreter().HandleCommand(
+ arguments.arguments.c_str(), eLazyBoolYes, result);
std::string output;
llvm::StringRef output_str = result.GetOutputString();
@@ -66,16 +87,64 @@ LLDBCommandTool::Call(const llvm::json::Value &args) {
output += err_str;
}
- mcp::protocol::TextResult text_result;
- text_result.content.emplace_back(mcp::protocol::TextContent{{output}});
- text_result.isError = !result.Succeeded();
- return text_result;
+ return createTextResult(output, !result.Succeeded());
}
-std::optional<llvm::json::Value> LLDBCommandTool::GetSchema() const {
+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{{"arguments", std::move(str_type)}};
+ 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)}};
+ {"properties", std::move(properties)},
+ {"required", std::move(required)}};
return schema;
}
+
+llvm::Expected<protocol::TextResult>
+DebuggerListTool::Call(const protocol::ToolArguments &args) {
+ if (!std::holds_alternative<std::monostate>(args))
+ return createStringError("DebuggerListTool takes no arguments");
+
+ llvm::json::Path::Root root;
+
+ // Return a nested Markdown list with debuggers and target.
+ // Example output:
+ //
+ // - debugger 0
+ // - target 0 /path/to/foo
+ // - target 1
+ // - debugger 1
+ // - target 0 /path/to/bar
+ //
+ // 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 << "- debugger " << i << '\n';
+
+ 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;
+ os << " - target " << j;
+ if (target_sp == target_list.GetSelectedTarget())
+ os << " (selected)";
+ // Append the module path if we have one.
+ if (Module *exe_module = target_sp->GetExecutableModulePointer())
+ os << " " << exe_module->GetFileSpec().GetPath();
+ os << '\n';
+ }
+ }
+
+ return createTextResult(output);
+}
diff --git a/lldb/source/Plugins/Protocol/MCP/Tool.h b/lldb/source/Plugins/Protocol/MCP/Tool.h
index 57a5125813b76..74ab04b472522 100644
--- a/lldb/source/Plugins/Protocol/MCP/Tool.h
+++ b/lldb/source/Plugins/Protocol/MCP/Tool.h
@@ -22,10 +22,10 @@ class Tool {
virtual ~Tool() = default;
virtual llvm::Expected<protocol::TextResult>
- Call(const llvm::json::Value &args) = 0;
+ Call(const protocol::ToolArguments &args) = 0;
virtual std::optional<llvm::json::Value> GetSchema() const {
- return std::nullopt;
+ return llvm::json::Object{{"type", "object"}};
}
protocol::ToolDefinition GetDefinition() const;
@@ -37,20 +37,26 @@ class Tool {
std::string m_description;
};
-class LLDBCommandTool : public mcp::Tool {
+class CommandTool : public mcp::Tool {
public:
- LLDBCommandTool(std::string name, std::string description,
- Debugger &debugger);
- ~LLDBCommandTool() = default;
+ using mcp::Tool::Tool;
+ ~CommandTool() = default;
virtual llvm::Expected<protocol::TextResult>
- Call(const llvm::json::Value &args) override;
+ Call(const protocol::ToolArguments &args) override;
virtual std::optional<llvm::json::Value> GetSchema() const override;
+};
-private:
- Debugger &m_debugger;
+class DebuggerListTool : public mcp::Tool {
+public:
+ using mcp::Tool::Tool;
+ ~DebuggerListTool() = default;
+
+ virtual llvm::Expected<protocol::TextResult>
+ Call(const protocol::ToolArguments &args) override;
};
+
} // namespace lldb_private::mcp
#endif
diff --git a/lldb/unittests/Protocol/ProtocolMCPServerTest.cpp b/lldb/unittests/Protocol/ProtocolMCPServerTest.cpp
index 72b8c7b1fd825..8e61379b5c731 100644
--- a/lldb/unittests/Protocol/ProtocolMCPServerTest.cpp
+++ b/lldb/unittests/Protocol/ProtocolMCPServerTest.cpp
@@ -46,9 +46,10 @@ class TestTool : public mcp::Tool {
using mcp::Tool::Tool;
virtual llvm::Expected<mcp::protocol::TextResult>
- Call(const llvm::json::Value &args) override {
+ Call(const ToolArguments &args) override {
std::string argument;
- if (const json::Object *args_obj = args.getAsObject()) {
+ if (const json::Object *args_obj =
+ std::get<json::Value>(args).getAsObject()) {
if (const json::Value *s = args_obj->get("arguments")) {
argument = s->getAsString().value_or("");
}
@@ -66,7 +67,7 @@ class ErrorTool : public mcp::Tool {
using mcp::Tool::Tool;
virtual llvm::Expected<mcp::protocol::TextResult>
- Call(const llvm::json::Value &args) override {
+ Call(const ToolArguments &args) override {
return llvm::createStringError("error");
}
};
@@ -77,7 +78,7 @@ class FailTool : public mcp::Tool {
using mcp::Tool::Tool;
virtual llvm::Expected<mcp::protocol::TextResult>
- Call(const llvm::json::Value &args) override {
+ Call(const ToolArguments &args) override {
mcp::protocol::TextResult text_result;
text_result.content.emplace_back(mcp::protocol::TextContent{{"failed"}});
text_result.isError = true;
@@ -115,7 +116,7 @@ class ProtocolServerMCPTest : public ::testing::Test {
ProtocolServer::Connection connection;
connection.protocol = Socket::SocketProtocol::ProtocolTcp;
connection.name = llvm::formatv("{0}:0", k_localhost).str();
- m_server_up = std::make_unique<TestProtocolServerMCP>(*m_debugger_sp);
+ m_server_up = std::make_unique<TestProtocolServerMCP>();
m_server_up->AddTool(std::make_unique<TestTool>("test", "test tool"));
ASSERT_THAT_ERROR(m_server_up->Start(connection), llvm::Succeeded());
@@ -145,7 +146,7 @@ class ProtocolServerMCPTest : public ::testing::Test {
TEST_F(ProtocolServerMCPTest, Intialization) {
llvm::StringLiteral request =
- R"json({"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude-ai","version":"0.1.0"}},"jsonrpc":"2.0","id":0})json";
+ R"json({"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"lldb-unit","version":"0.1.0"}},"jsonrpc":"2.0","id":0})json";
llvm::StringLiteral response =
R"json({"jsonrpc":"2.0","id":0,"result":{"capabilities":{"tools":{"listChanged":true}},"protocolVersion":"2024-11-05","serverInfo":{"name":"lldb-mcp","version":"0.1.0"}}})json";
@@ -167,7 +168,7 @@ TEST_F(ProtocolServerMCPTest, ToolsList) {
llvm::StringLiteral request =
R"json({"method":"tools/list","params":{},"jsonrpc":"2.0","id":1})json";
llvm::StringLiteral response =
- R"json({"id":1,"jsonrpc":"2.0","result":{"tools":[{"description":"test tool","name":"test"},{"description":"Run an lldb command.","inputSchema":{"properties":{"arguments":{"type":"string"}},"type":"object"},"name":"lldb_command"}]}})json";
+ R"json( {"id":1,"jsonrpc":"2.0","result":{"tools":[{"description":"test tool","inputSchema":{"type":"object"},"name":"test"},{"description":"List debugger instances with their debugger_id.","inputSchema":{"type":"object"},"name":"lldb_debugger_list"},{"description":"Run an lldb command.","inputSchema":{"properties":{"arguments":{"type":"string"},"debugger_id":{"type":"number"}},"required":["debugger_id"],"type":"object"},"name":"lldb_command"}]}})json";
ASSERT_THAT_ERROR(Write(request), llvm::Succeeded());
@@ -205,7 +206,7 @@ TEST_F(ProtocolServerMCPTest, ResourcesList) {
TEST_F(ProtocolServerMCPTest, ToolsCall) {
llvm::StringLiteral request =
- R"json({"method":"tools/call","params":{"name":"test","arguments":{"arguments":"foo"}},"jsonrpc":"2.0","id":11})json";
+ R"json({"method":"tools/call","params":{"name":"test","arguments":{"arguments":"foo","debugger_id":0}},"jsonrpc":"2.0","id":11})json";
llvm::StringLiteral response =
R"json({"id":11,"jsonrpc":"2.0","result":{"content":[{"text":"foo","type":"text"}],"isError":false}})json";
@@ -227,7 +228,7 @@ TEST_F(ProtocolServerMCPTest, ToolsCallError) {
m_server_up->AddTool(std::make_unique<ErrorTool>("error", "error tool"));
llvm::StringLiteral request =
- R"json({"method":"tools/call","params":{"name":"error","arguments":{"arguments":"foo"}},"jsonrpc":"2.0","id":11})json";
+ R"json({"method":"tools/call","params":{"name":"error","arguments":{"arguments":"foo","debugger_id":0}},"jsonrpc":"2.0","id":11})json";
llvm::StringLiteral response =
R"json({"error":{"code":-1,"message":"error"},"id":11,"jsonrpc":"2.0"})json";
@@ -249,7 +250,7 @@ TEST_F(ProtocolServerMCPTest, ToolsCallFail) {
m_server_up->AddTool(std::make_unique<FailTool>("fail", "fail tool"));
llvm::StringLiteral request =
- R"json({"method":"tools/call","params":{"name":"fail","arguments":{"arguments":"foo"}},"jsonrpc":"2.0","id":11})json";
+ R"json({"method":"tools/call","params":{"name":"fail","arguments":{"arguments":"foo","debugger_id":0}},"jsonrpc":"2.0","id":11})json";
llvm::StringLiteral response =
R"json({"id":11,"jsonrpc":"2.0","result":{"content":[{"text":"failed","type":"text"}],"isError":true}})json";
More information about the lldb-commits
mailing list