Skip to content

Conversation

ashgti
Copy link
Contributor

@ashgti ashgti commented Aug 27, 2025

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.

@ashgti
Copy link
Contributor Author

ashgti commented Aug 27, 2025

This is built on #155711, so I'll leave it as a draft until that is in.

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.
@ashgti ashgti marked this pull request as ready for review August 27, 2025 23:22
@ashgti ashgti requested a review from JDevlieghere as a code owner August 27, 2025 23:22
@llvmbot llvmbot added the lldb label Aug 27, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 27, 2025

@llvm/pr-subscribers-lldb

Author: John Harrison (ashgti)

Changes

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-&lt;pid&gt;.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.


Full diff: https://github.com/llvm/llvm-project/pull/155714.diff

5 Files Affected:

  • (modified) lldb/include/lldb/Protocol/MCP/Server.h (+12)
  • (modified) lldb/source/Commands/CommandObjectProtocolServer.cpp (+7-8)
  • (modified) lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp (+44-3)
  • (modified) lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.h (+1)
  • (modified) lldb/source/Protocol/MCP/Server.cpp (+12)
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)

@ashgti
Copy link
Contributor Author

ashgti commented Aug 27, 2025

Right now, I wasn't entirely sure how to make the MCP server's fully unique on the host or what information we need to store about the servers, so I'm currently using the lldb pid to make the files unique. That could be replaced in the future by something else a UUID or hash.

Comment on lines 113 to 116
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");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add this to HostInfo. Getting the MCP directory might be a bit too specific, but ~/.lldb seems generally useful. Looks like we have a few more uses of that in PlatformProperties for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added new things to HostInfo, I might need to double check some more platforms as well.

Copy link
Member

@JDevlieghere JDevlieghere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines 1997 to 2013
helper.DispatchOnExit(
[&cmd_obj, &parsed_command_args, &result, detailed_command_telemetry,
command_id](lldb_private::telemetry::CommandInfo *info) {
// TODO: this is logging the time the command-handler finishes.
// But we may want a finer-grain durations too?
// (ie., the execute_time recorded below?)
info->command_id = command_id;
llvm::StringRef command_name =
cmd_obj ? cmd_obj->GetCommandName() : "<not found>";
info->command_name = command_name.str();
info->ret_status = result.GetStatus();
if (std::string error_str = result.GetErrorString(); !error_str.empty())
info->error_data = std::move(error_str);

if (detailed_command_telemetry)
info->args = parsed_command_args;
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unintentional formatting change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted, clangd keeps trying to format the entire fire on save, I should probably turn that off.

ashgti and others added 4 commits August 28, 2025 11:33
Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>
Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>
Copy link

github-actions bot commented Aug 28, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants