-
Notifications
You must be signed in to change notification settings - Fork 14.9k
[lldb] Adjust ProtocolServer connection defaults. #155714
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
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.
@llvm/pr-subscribers-lldb Author: John Harrison (ashgti) ChangesThis adjusts the ProtocolServer command to default to create a new connection listening on 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:
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)
|
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. |
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"); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
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; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unintentional formatting change?
There was a problem hiding this comment.
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.
Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>
Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>
✅ With the latest revision this PR passed the C/C++ code formatter. |
This adjusts the ProtocolServer command to default to create a new connection listening on
localhost:0
and adds a newServerMetadata
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.