https://github.com/ashgti created https://github.com/llvm/llvm-project/pull/155714
This adjusts the ProtocolServer command to default to create a new connection listening on `localhost:0` and adds a new `ServerMetadata` details to `~/.lldb/mcp/lldb-<pid>.json` to record information about the current MCP server. This can be consumed by the lldb-mcp binary to establish a connection from an LLM client. >From b10f3ffca74e08234960fc0a3d4dfb5431d901ee Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@google.com> Date: Wed, 27 Aug 2025 15:22:40 -0700 Subject: [PATCH 1/2] [lldb] NFC Moving mcp::Transport into its own file. Moving `lldb_protocol::mcp::MCPTransport` out of Server.h and into its own file and simplifying the name to `Transport`. --- lldb/include/lldb/Protocol/MCP/MCPError.h | 5 +-- lldb/include/lldb/Protocol/MCP/Protocol.h | 5 +++ lldb/include/lldb/Protocol/MCP/Server.h | 31 ++------------- lldb/include/lldb/Protocol/MCP/Transport.h | 39 +++++++++++++++++++ .../Protocol/MCP/ProtocolServerMCP.cpp | 2 +- lldb/source/Protocol/MCP/CMakeLists.txt | 1 + lldb/source/Protocol/MCP/Server.cpp | 6 +-- lldb/source/Protocol/MCP/Transport.cpp | 32 +++++++++++++++ .../Protocol/ProtocolMCPServerTest.cpp | 7 ++-- 9 files changed, 90 insertions(+), 38 deletions(-) create mode 100644 lldb/include/lldb/Protocol/MCP/Transport.h create mode 100644 lldb/source/Protocol/MCP/Transport.cpp diff --git a/lldb/include/lldb/Protocol/MCP/MCPError.h b/lldb/include/lldb/Protocol/MCP/MCPError.h index 55dd40f124a15..52c5a78fa23c0 100644 --- a/lldb/include/lldb/Protocol/MCP/MCPError.h +++ b/lldb/include/lldb/Protocol/MCP/MCPError.h @@ -19,7 +19,7 @@ class MCPError : public llvm::ErrorInfo<MCPError> { public: static char ID; - MCPError(std::string message, int64_t error_code = kInternalError); + MCPError(std::string message, int64_t error_code = eErrorCodeInternalError); void log(llvm::raw_ostream &OS) const override; std::error_code convertToErrorCode() const override; @@ -28,9 +28,6 @@ class MCPError : public llvm::ErrorInfo<MCPError> { lldb_protocol::mcp::Error toProtocolError() const; - static constexpr int64_t kResourceNotFound = -32002; - static constexpr int64_t kInternalError = -32603; - private: std::string m_message; int64_t m_error_code; diff --git a/lldb/include/lldb/Protocol/MCP/Protocol.h b/lldb/include/lldb/Protocol/MCP/Protocol.h index 6e1ffcbe1f3e3..295f1f6e1b037 100644 --- a/lldb/include/lldb/Protocol/MCP/Protocol.h +++ b/lldb/include/lldb/Protocol/MCP/Protocol.h @@ -55,6 +55,11 @@ enum ErrorCode : signed { eErrorCodeInvalidParams = -32602, /// Internal JSON-RPC error. eErrorCodeInternalError = -32603, + + /// Additional MCP error codes. + + /// Resource related uri not found. + eErrorCodeResourceNotFound = -32002, }; struct Error { diff --git a/lldb/include/lldb/Protocol/MCP/Server.h b/lldb/include/lldb/Protocol/MCP/Server.h index aa5714e45755e..009c574fde92f 100644 --- a/lldb/include/lldb/Protocol/MCP/Server.h +++ b/lldb/include/lldb/Protocol/MCP/Server.h @@ -9,43 +9,20 @@ #ifndef LLDB_PROTOCOL_MCP_SERVER_H #define LLDB_PROTOCOL_MCP_SERVER_H -#include "lldb/Host/JSONTransport.h" #include "lldb/Host/MainLoop.h" #include "lldb/Protocol/MCP/Protocol.h" #include "lldb/Protocol/MCP/Resource.h" #include "lldb/Protocol/MCP/Tool.h" +#include "lldb/Protocol/MCP/Transport.h" #include "llvm/ADT/StringMap.h" #include "llvm/Support/Error.h" -#include <mutex> namespace lldb_protocol::mcp { -class MCPTransport - : public lldb_private::JSONRPCTransport<Request, Response, Notification> { -public: - using LogCallback = std::function<void(llvm::StringRef message)>; - - MCPTransport(lldb::IOObjectSP in, lldb::IOObjectSP out, - std::string client_name, LogCallback log_callback = {}) - : JSONRPCTransport(in, out), m_client_name(std::move(client_name)), - m_log_callback(log_callback) {} - virtual ~MCPTransport() = default; - - void Log(llvm::StringRef message) override { - if (m_log_callback) - m_log_callback(llvm::formatv("{0}: {1}", m_client_name, message).str()); - } - -private: - std::string m_client_name; - LogCallback m_log_callback; -}; - -class Server : public MCPTransport::MessageHandler { +class Server : public Transport::MessageHandler { public: Server(std::string name, std::string version, - std::unique_ptr<MCPTransport> transport_up, - lldb_private::MainLoop &loop); + std::unique_ptr<Transport> transport_up, lldb_private::MainLoop &loop); ~Server() = default; using NotificationHandler = std::function<void(const Notification &)>; @@ -92,7 +69,7 @@ class Server : public MCPTransport::MessageHandler { const std::string m_name; const std::string m_version; - std::unique_ptr<MCPTransport> m_transport_up; + std::unique_ptr<Transport> m_transport_up; lldb_private::MainLoop &m_loop; llvm::StringMap<std::unique_ptr<Tool>> m_tools; diff --git a/lldb/include/lldb/Protocol/MCP/Transport.h b/lldb/include/lldb/Protocol/MCP/Transport.h new file mode 100644 index 0000000000000..3cb725131f3b0 --- /dev/null +++ b/lldb/include/lldb/Protocol/MCP/Transport.h @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_PROTOCOL_MCP_TRANSPORT_H +#define LLDB_PROTOCOL_MCP_TRANSPORT_H + +#include "lldb/Host/JSONTransport.h" +#include "lldb/Protocol/MCP/Protocol.h" +#include "lldb/lldb-forward.h" +#include "llvm/ADT/StringRef.h" +#include <functional> +#include <string> + +namespace lldb_protocol::mcp { + +class Transport + : public lldb_private::JSONRPCTransport<Request, Response, Notification> { +public: + using LogCallback = std::function<void(llvm::StringRef message)>; + + Transport(lldb::IOObjectSP in, lldb::IOObjectSP out, std::string client_name, + LogCallback log_callback = {}); + virtual ~Transport() = default; + + void Log(llvm::StringRef message) override; + +private: + std::string m_client_name; + LogCallback m_log_callback; +}; + +} // namespace lldb_protocol::mcp + +#endif diff --git a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp index 57132534cf680..a9c4164313a6d 100644 --- a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp +++ b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp @@ -67,7 +67,7 @@ void ProtocolServerMCP::AcceptCallback(std::unique_ptr<Socket> socket) { LLDB_LOG(log, "New MCP client connected: {0}", client_name); lldb::IOObjectSP io_sp = std::move(socket); - auto transport_up = std::make_unique<lldb_protocol::mcp::MCPTransport>( + auto transport_up = std::make_unique<lldb_protocol::mcp::Transport>( io_sp, io_sp, std::move(client_name), [&](llvm::StringRef message) { LLDB_LOG(GetLog(LLDBLog::Host), "{0}", message); }); diff --git a/lldb/source/Protocol/MCP/CMakeLists.txt b/lldb/source/Protocol/MCP/CMakeLists.txt index a73e7e6a7cab1..23da62085537e 100644 --- a/lldb/source/Protocol/MCP/CMakeLists.txt +++ b/lldb/source/Protocol/MCP/CMakeLists.txt @@ -3,6 +3,7 @@ add_lldb_library(lldbProtocolMCP NO_PLUGIN_DEPENDENCIES Protocol.cpp Server.cpp Tool.cpp + Transport.cpp LINK_COMPONENTS Support diff --git a/lldb/source/Protocol/MCP/Server.cpp b/lldb/source/Protocol/MCP/Server.cpp index 63c2d01d17922..fb317083b015e 100644 --- a/lldb/source/Protocol/MCP/Server.cpp +++ b/lldb/source/Protocol/MCP/Server.cpp @@ -15,7 +15,7 @@ using namespace lldb_protocol::mcp; using namespace llvm; Server::Server(std::string name, std::string version, - std::unique_ptr<MCPTransport> transport_up, + std::unique_ptr<Transport> transport_up, lldb_private::MainLoop &loop) : m_name(std::move(name)), m_version(std::move(version)), m_transport_up(std::move(transport_up)), m_loop(loop) { @@ -180,7 +180,7 @@ llvm::Expected<Response> Server::ResourcesReadHandler(const Request &request) { return make_error<MCPError>( llvm::formatv("no resource handler for uri: {0}", uri_str).str(), - MCPError::kResourceNotFound); + eErrorCodeResourceNotFound); } ServerCapabilities Server::GetCapabilities() { @@ -219,7 +219,7 @@ void Server::Received(const Request &request) { response.takeError(), [&](const MCPError &err) { protocol_error = err.toProtocolError(); }, [&](const llvm::ErrorInfoBase &err) { - protocol_error.code = MCPError::kInternalError; + protocol_error.code = eErrorCodeInternalError; protocol_error.message = err.message(); }); Response error_response; diff --git a/lldb/source/Protocol/MCP/Transport.cpp b/lldb/source/Protocol/MCP/Transport.cpp new file mode 100644 index 0000000000000..58c66af7320ed --- /dev/null +++ b/lldb/source/Protocol/MCP/Transport.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// 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 "lldb/Protocol/MCP/Transport.h" +#include "lldb/Host/JSONTransport.h" +#include "lldb/lldb-forward.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/FormatVariadic.h" +#include <string> +#include <utility> + +using namespace llvm; +using namespace lldb; + +namespace lldb_protocol::mcp { + +Transport::Transport(IOObjectSP in, IOObjectSP out, std::string client_name, + LogCallback log_callback) + : JSONRPCTransport(in, out), m_client_name(std::move(client_name)), + m_log_callback(log_callback) {} + +void Transport::Log(StringRef message) { + if (m_log_callback) + m_log_callback(formatv("{0}: {1}", m_client_name, message).str()); +} + +} // namespace lldb_protocol::mcp diff --git a/lldb/unittests/Protocol/ProtocolMCPServerTest.cpp b/lldb/unittests/Protocol/ProtocolMCPServerTest.cpp index 9fa446133d46f..846582ab9a74e 100644 --- a/lldb/unittests/Protocol/ProtocolMCPServerTest.cpp +++ b/lldb/unittests/Protocol/ProtocolMCPServerTest.cpp @@ -21,6 +21,7 @@ #include "lldb/Protocol/MCP/Resource.h" #include "lldb/Protocol/MCP/Server.h" #include "lldb/Protocol/MCP/Tool.h" +#include "lldb/Protocol/MCP/Transport.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" @@ -36,12 +37,12 @@ using namespace lldb_private; using namespace lldb_protocol::mcp; namespace { -class TestMCPTransport final : public MCPTransport { +class TestMCPTransport final : public lldb_protocol::mcp::Transport { public: TestMCPTransport(lldb::IOObjectSP in, lldb::IOObjectSP out) - : lldb_protocol::mcp::MCPTransport(in, out, "unittest") {} + : lldb_protocol::mcp::Transport(in, out, "unittest") {} - using MCPTransport::Write; + using Transport::Write; void Log(llvm::StringRef message) override { log_messages.emplace_back(message); >From 5dc722adf2a4e8b6cc20c88ea514e6eb195de0f3 Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@google.com> Date: Wed, 27 Aug 2025 15:58:04 -0700 Subject: [PATCH 2/2] [lldb] Adjust ProtocolServer connection defaults. This adjusts the ProtocolServer command to default to create a new connection listening on `localhost:0` and adds a new `ServerMetadata` details to `~/.lldb/mcp/lldb-<pid>.json` to record information about the current MCP server. This can be consumed by the lldb-mcp binary to establish a connection from an LLM client. --- lldb/include/lldb/Protocol/MCP/Server.h | 12 +++++ .../Commands/CommandObjectProtocolServer.cpp | 15 +++--- .../Protocol/MCP/ProtocolServerMCP.cpp | 47 +++++++++++++++++-- .../Plugins/Protocol/MCP/ProtocolServerMCP.h | 1 + lldb/source/Protocol/MCP/Server.cpp | 12 +++++ 5 files changed, 76 insertions(+), 11 deletions(-) diff --git a/lldb/include/lldb/Protocol/MCP/Server.h b/lldb/include/lldb/Protocol/MCP/Server.h index 009c574fde92f..54f004b17546e 100644 --- a/lldb/include/lldb/Protocol/MCP/Server.h +++ b/lldb/include/lldb/Protocol/MCP/Server.h @@ -14,11 +14,23 @@ #include "lldb/Protocol/MCP/Resource.h" #include "lldb/Protocol/MCP/Tool.h" #include "lldb/Protocol/MCP/Transport.h" +#include "lldb/lldb-types.h" #include "llvm/ADT/StringMap.h" #include "llvm/Support/Error.h" +#include "llvm/Support/JSON.h" namespace lldb_protocol::mcp { +/// Metadata about this instance of lldb's MCP server for lldb-mcp to use to +/// coordinate connecting an lldb-mcp client. +struct ServerMetadata { + std::string connection_uri; + lldb::pid_t pid; +}; +llvm::json::Value toJSON(const ServerMetadata &SM); +bool fromJSON(const llvm::json::Value &V, ServerMetadata &SM, + llvm::json::Path P); + class Server : public Transport::MessageHandler { public: Server(std::string name, std::string version, diff --git a/lldb/source/Commands/CommandObjectProtocolServer.cpp b/lldb/source/Commands/CommandObjectProtocolServer.cpp index f11e27f01c8a8..81c97614b8fe3 100644 --- a/lldb/source/Commands/CommandObjectProtocolServer.cpp +++ b/lldb/source/Commands/CommandObjectProtocolServer.cpp @@ -15,6 +15,7 @@ #include "lldb/Utility/UriParser.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/FormatAdapters.h" +#include <string> using namespace llvm; using namespace lldb; @@ -28,7 +29,7 @@ class CommandObjectProtocolServerStart : public CommandObjectParsed { CommandObjectProtocolServerStart(CommandInterpreter &interpreter) : CommandObjectParsed(interpreter, "protocol-server start", "start protocol server", - "protocol-server start <protocol> <connection>") { + "protocol-server start <protocol> [<connection>]") { AddSimpleArgumentList(lldb::eArgTypeProtocol, eArgRepeatPlain); AddSimpleArgumentList(lldb::eArgTypeConnectURL, eArgRepeatPlain); } @@ -51,15 +52,13 @@ class CommandObjectProtocolServerStart : public CommandObjectParsed { return; } - if (args.GetArgumentCount() < 2) { - result.AppendError("no connection specified"); - return; - } - llvm::StringRef connection_uri = args.GetArgumentAtIndex(1); + std::string connection_uri = "listen://[localhost]:0"; + if (args.GetArgumentCount() > 2) + connection_uri = args.GetArgumentAtIndex(1); const char *connection_error = - "unsupported connection specifier, expected 'accept:///path' or " - "'listen://[host]:port', got '{0}'."; + "unsupported connection specifier, expected 'accept:///path' " + "or 'listen://[host]:port', got '{0}'."; auto uri = lldb_private::URI::Parse(connection_uri); if (!uri) { result.AppendErrorWithFormatv(connection_error, connection_uri); diff --git a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp index a9c4164313a6d..9068ff92a9aab 100644 --- a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp +++ b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp @@ -10,14 +10,14 @@ #include "Resource.h" #include "Tool.h" #include "lldb/Core/PluginManager.h" -#include "lldb/Protocol/MCP/MCPError.h" -#include "lldb/Protocol/MCP/Tool.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Protocol/MCP/Server.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Error.h" #include "llvm/Support/Threading.h" #include <thread> -#include <variant> using namespace lldb_private; using namespace lldb_private::mcp; @@ -104,6 +104,43 @@ llvm::Error ProtocolServerMCP::Start(ProtocolServer::Connection connection) { if (llvm::Error error = handles.takeError()) return error; + auto listening_uris = m_listener->GetListeningConnectionURI(); + if (listening_uris.empty()) + return createStringError("Failed to list listening connections"); + std::string address = + llvm::join(m_listener->GetListeningConnectionURI(), ", "); + + llvm::SmallString<128> user_home_dir; + FileSystem::Instance().GetHomeDirectory(user_home_dir); + FileSpec mcp_registry_dir = FileSpec(user_home_dir.c_str()); + mcp_registry_dir.AppendPathComponent(".lldb"); + mcp_registry_dir.AppendPathComponent("mcp"); + + Status error(llvm::sys::fs::create_directory(mcp_registry_dir.GetPath())); + if (error.Fail()) + return error.takeError(); + + m_mcp_registry_entry_path = mcp_registry_dir.CopyByAppendingPathComponent( + formatv("lldb-{0}.json", getpid()).str()); + + const File::OpenOptions flags = File::eOpenOptionWriteOnly | + File::eOpenOptionCanCreate | + File::eOpenOptionTruncate; + llvm::Expected<lldb::FileUP> file = + FileSystem::Instance().Open(m_mcp_registry_entry_path, flags, + lldb::eFilePermissionsFileDefault, false); + if (!file) + return file.takeError(); + + ServerMetadata metadata; + metadata.connection_uri = listening_uris[0]; + metadata.pid = getpid(); + + std::string buf = formatv("{0}", toJSON(metadata)).str(); + size_t num_bytes = buf.size(); + if (llvm::Error error = (*file)->Write(buf.data(), num_bytes).takeError()) + return error; + m_running = true; m_listen_handlers = std::move(*handles); m_loop_thread = std::thread([=] { @@ -122,6 +159,10 @@ llvm::Error ProtocolServerMCP::Stop() { m_running = false; } + if (!m_mcp_registry_entry_path.GetPath().empty()) + FileSystem::Instance().RemoveFile(m_mcp_registry_entry_path); + m_mcp_registry_entry_path.Clear(); + // Stop the main loop. m_loop.AddPendingCallback( [](lldb_private::MainLoopBase &loop) { loop.RequestTermination(); }); diff --git a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.h b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.h index fc650ffe0dfa7..004fa3c2d05a8 100644 --- a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.h +++ b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.h @@ -48,6 +48,7 @@ class ProtocolServerMCP : public ProtocolServer { bool m_running = false; + FileSpec m_mcp_registry_entry_path; lldb_private::MainLoop m_loop; std::thread m_loop_thread; std::mutex m_mutex; diff --git a/lldb/source/Protocol/MCP/Server.cpp b/lldb/source/Protocol/MCP/Server.cpp index fb317083b015e..47cfb1a6114b4 100644 --- a/lldb/source/Protocol/MCP/Server.cpp +++ b/lldb/source/Protocol/MCP/Server.cpp @@ -14,6 +14,18 @@ using namespace lldb_protocol::mcp; using namespace llvm; +llvm::json::Value toJSON(const ServerMetadata &SM) { + return llvm::json::Object{{"connection_uri", SM.connection_uri}, + {"pid", SM.pid}}; +} + +bool fromJSON(const llvm::json::Value &V, ServerMetadata &SM, + llvm::json::Path P) { + llvm::json::ObjectMapper O(V, P); + return O && O.map("connection_uri", SM.connection_uri) && + O.map("pid", SM.pid); +} + Server::Server(std::string name, std::string version, std::unique_ptr<Transport> transport_up, lldb_private::MainLoop &loop) _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits