Skip to content

Commit 9524bfb

Browse files
authored
[lldb] Add Model Context Protocol (MCP) support to LLDB (#143628)
This PR adds an MCP (Model Context Protocol ) server to LLDB. For motivation and background, please refer to the corresponding RFC: https://discourse.llvm.org/t/rfc-adding-mcp-support-to-lldb/86798 I implemented this as a new kind of plugin. The idea is that we could support multiple protocol servers (e.g. if we want to support DAP from within LLDB). This also introduces a corresponding top-level command (`protocol-server`) with two subcommands to `start` and `stop` the server. ``` (lldb) protocol-server start MCP tcp://localhost:1234 MCP server started with connection listeners: connection://[::1]:1234, connection://[127.0.0.1]:1234 ``` The MCP sever supports one tool (`lldb_command`) which executes a command, but can easily be extended with more commands.
1 parent 887222e commit 9524bfb

33 files changed

+1803
-23
lines changed

lldb/cmake/modules/LLDBConfig.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ add_optional_dependency(LLDB_ENABLE_FBSDVMCORE "Enable libfbsdvmcore support in
6767

6868
option(LLDB_USE_ENTITLEMENTS "When codesigning, use entitlements if available" ON)
6969
option(LLDB_BUILD_FRAMEWORK "Build LLDB.framework (Darwin only)" OFF)
70+
option(LLDB_ENABLE_PROTOCOL_SERVERS "Enable protocol servers (e.g. MCP) in LLDB" ON)
7071
option(LLDB_NO_INSTALL_DEFAULT_RPATH "Disable default RPATH settings in binaries" OFF)
7172
option(LLDB_USE_SYSTEM_DEBUGSERVER "Use the system's debugserver for testing (Darwin only)." OFF)
7273
option(LLDB_SKIP_STRIP "Whether to skip stripping of binaries when installing lldb." OFF)

lldb/include/lldb/Core/Debugger.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,10 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
602602
void FlushProcessOutput(Process &process, bool flush_stdout,
603603
bool flush_stderr);
604604

605+
void AddProtocolServer(lldb::ProtocolServerSP protocol_server_sp);
606+
void RemoveProtocolServer(lldb::ProtocolServerSP protocol_server_sp);
607+
lldb::ProtocolServerSP GetProtocolServer(llvm::StringRef protocol) const;
608+
605609
SourceManager::SourceFileCache &GetSourceFileCache() {
606610
return m_source_file_cache;
607611
}
@@ -772,6 +776,8 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
772776
mutable std::mutex m_progress_reports_mutex;
773777
/// @}
774778

779+
llvm::SmallVector<lldb::ProtocolServerSP> m_protocol_servers;
780+
775781
std::mutex m_destroy_callback_mutex;
776782
lldb::callback_token_t m_destroy_callback_next_token = 0;
777783
struct DestroyCallbackInfo {

lldb/include/lldb/Core/PluginManager.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,17 @@ class PluginManager {
321321
static void AutoCompleteProcessName(llvm::StringRef partial_name,
322322
CompletionRequest &request);
323323

324+
// Protocol
325+
static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description,
326+
ProtocolServerCreateInstance create_callback);
327+
328+
static bool UnregisterPlugin(ProtocolServerCreateInstance create_callback);
329+
330+
static llvm::StringRef GetProtocolServerPluginNameAtIndex(uint32_t idx);
331+
332+
static ProtocolServerCreateInstance
333+
GetProtocolCreateCallbackForPluginName(llvm::StringRef name);
334+
324335
// Register Type Provider
325336
static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description,
326337
RegisterTypeBuilderCreateInstance create_callback);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//===-- ProtocolServer.h --------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_CORE_PROTOCOLSERVER_H
10+
#define LLDB_CORE_PROTOCOLSERVER_H
11+
12+
#include "lldb/Core/PluginInterface.h"
13+
#include "lldb/Host/Socket.h"
14+
#include "lldb/lldb-private-interfaces.h"
15+
16+
namespace lldb_private {
17+
18+
class ProtocolServer : public PluginInterface {
19+
public:
20+
ProtocolServer() = default;
21+
virtual ~ProtocolServer() = default;
22+
23+
static lldb::ProtocolServerSP Create(llvm::StringRef name,
24+
Debugger &debugger);
25+
26+
struct Connection {
27+
Socket::SocketProtocol protocol;
28+
std::string name;
29+
};
30+
31+
virtual llvm::Error Start(Connection connection) = 0;
32+
virtual llvm::Error Stop() = 0;
33+
34+
virtual Socket *GetSocket() const = 0;
35+
};
36+
37+
} // namespace lldb_private
38+
39+
#endif

lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ static constexpr CommandObject::ArgumentTableEntry g_argument_table[] = {
315315
{ lldb::eArgTypeCPUName, "cpu-name", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "The name of a CPU." },
316316
{ lldb::eArgTypeCPUFeatures, "cpu-features", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "The CPU feature string." },
317317
{ lldb::eArgTypeManagedPlugin, "managed-plugin", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "Plugins managed by the PluginManager" },
318+
{ lldb::eArgTypeProtocol, "protocol", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "The name of the protocol." },
318319
// clang-format on
319320
};
320321

lldb/include/lldb/lldb-enumerations.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,7 @@ enum CommandArgumentType {
664664
eArgTypeCPUName,
665665
eArgTypeCPUFeatures,
666666
eArgTypeManagedPlugin,
667+
eArgTypeProtocol,
667668
eArgTypeLastArg // Always keep this entry as the last entry in this
668669
// enumeration!!
669670
};

lldb/include/lldb/lldb-forward.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,13 @@ class PersistentExpressionState;
164164
class Platform;
165165
class Process;
166166
class ProcessAttachInfo;
167-
class ProcessLaunchInfo;
168167
class ProcessInfo;
169168
class ProcessInstanceInfo;
170169
class ProcessInstanceInfoMatch;
171170
class ProcessLaunchInfo;
172171
class ProcessModID;
173172
class Property;
173+
class ProtocolServer;
174174
class Queue;
175175
class QueueImpl;
176176
class QueueItem;
@@ -391,6 +391,7 @@ typedef std::shared_ptr<lldb_private::Platform> PlatformSP;
391391
typedef std::shared_ptr<lldb_private::Process> ProcessSP;
392392
typedef std::shared_ptr<lldb_private::ProcessAttachInfo> ProcessAttachInfoSP;
393393
typedef std::shared_ptr<lldb_private::ProcessLaunchInfo> ProcessLaunchInfoSP;
394+
typedef std::shared_ptr<lldb_private::ProtocolServer> ProtocolServerSP;
394395
typedef std::weak_ptr<lldb_private::Process> ProcessWP;
395396
typedef std::shared_ptr<lldb_private::RegisterCheckpoint> RegisterCheckpointSP;
396397
typedef std::shared_ptr<lldb_private::RegisterContext> RegisterContextSP;

lldb/include/lldb/lldb-private-interfaces.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ typedef lldb::PlatformSP (*PlatformCreateInstance)(bool force,
8181
typedef lldb::ProcessSP (*ProcessCreateInstance)(
8282
lldb::TargetSP target_sp, lldb::ListenerSP listener_sp,
8383
const FileSpec *crash_file_path, bool can_connect);
84+
typedef lldb::ProtocolServerSP (*ProtocolServerCreateInstance)(
85+
Debugger &debugger);
8486
typedef lldb::RegisterTypeBuilderSP (*RegisterTypeBuilderCreateInstance)(
8587
Target &target);
8688
typedef lldb::ScriptInterpreterSP (*ScriptInterpreterCreateInstance)(

lldb/source/Commands/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ add_lldb_library(lldbCommands NO_PLUGIN_DEPENDENCIES
2323
CommandObjectPlatform.cpp
2424
CommandObjectPlugin.cpp
2525
CommandObjectProcess.cpp
26+
CommandObjectProtocolServer.cpp
2627
CommandObjectQuit.cpp
2728
CommandObjectRegexCommand.cpp
2829
CommandObjectRegister.cpp
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
//===-- CommandObjectProtocolServer.cpp
2+
//----------------------------------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#include "CommandObjectProtocolServer.h"
11+
#include "lldb/Core/PluginManager.h"
12+
#include "lldb/Core/ProtocolServer.h"
13+
#include "lldb/Host/Socket.h"
14+
#include "lldb/Interpreter/CommandInterpreter.h"
15+
#include "lldb/Interpreter/CommandReturnObject.h"
16+
#include "lldb/Utility/UriParser.h"
17+
#include "llvm/ADT/STLExtras.h"
18+
#include "llvm/Support/FormatAdapters.h"
19+
20+
using namespace llvm;
21+
using namespace lldb;
22+
using namespace lldb_private;
23+
24+
#define LLDB_OPTIONS_mcp
25+
#include "CommandOptions.inc"
26+
27+
static std::vector<llvm::StringRef> GetSupportedProtocols() {
28+
std::vector<llvm::StringRef> supported_protocols;
29+
size_t i = 0;
30+
31+
for (llvm::StringRef protocol_name =
32+
PluginManager::GetProtocolServerPluginNameAtIndex(i++);
33+
!protocol_name.empty();
34+
protocol_name = PluginManager::GetProtocolServerPluginNameAtIndex(i++)) {
35+
supported_protocols.push_back(protocol_name);
36+
}
37+
38+
return supported_protocols;
39+
}
40+
41+
class CommandObjectProtocolServerStart : public CommandObjectParsed {
42+
public:
43+
CommandObjectProtocolServerStart(CommandInterpreter &interpreter)
44+
: CommandObjectParsed(interpreter, "protocol-server start",
45+
"start protocol server",
46+
"protocol-server start <protocol> <connection>") {
47+
AddSimpleArgumentList(lldb::eArgTypeProtocol, eArgRepeatPlain);
48+
AddSimpleArgumentList(lldb::eArgTypeConnectURL, eArgRepeatPlain);
49+
}
50+
51+
~CommandObjectProtocolServerStart() override = default;
52+
53+
protected:
54+
void DoExecute(Args &args, CommandReturnObject &result) override {
55+
if (args.GetArgumentCount() < 1) {
56+
result.AppendError("no protocol specified");
57+
return;
58+
}
59+
60+
llvm::StringRef protocol = args.GetArgumentAtIndex(0);
61+
std::vector<llvm::StringRef> supported_protocols = GetSupportedProtocols();
62+
if (llvm::find(supported_protocols, protocol) ==
63+
supported_protocols.end()) {
64+
result.AppendErrorWithFormatv(
65+
"unsupported protocol: {0}. Supported protocols are: {1}", protocol,
66+
llvm::join(GetSupportedProtocols(), ", "));
67+
return;
68+
}
69+
70+
if (args.GetArgumentCount() < 2) {
71+
result.AppendError("no connection specified");
72+
return;
73+
}
74+
llvm::StringRef connection_uri = args.GetArgumentAtIndex(1);
75+
76+
ProtocolServerSP server_sp = GetDebugger().GetProtocolServer(protocol);
77+
if (!server_sp)
78+
server_sp = ProtocolServer::Create(protocol, GetDebugger());
79+
80+
const char *connection_error =
81+
"unsupported connection specifier, expected 'accept:///path' or "
82+
"'listen://[host]:port', got '{0}'.";
83+
auto uri = lldb_private::URI::Parse(connection_uri);
84+
if (!uri) {
85+
result.AppendErrorWithFormatv(connection_error, connection_uri);
86+
return;
87+
}
88+
89+
std::optional<Socket::ProtocolModePair> protocol_and_mode =
90+
Socket::GetProtocolAndMode(uri->scheme);
91+
if (!protocol_and_mode || protocol_and_mode->second != Socket::ModeAccept) {
92+
result.AppendErrorWithFormatv(connection_error, connection_uri);
93+
return;
94+
}
95+
96+
ProtocolServer::Connection connection;
97+
connection.protocol = protocol_and_mode->first;
98+
connection.name =
99+
formatv("[{0}]:{1}", uri->hostname.empty() ? "0.0.0.0" : uri->hostname,
100+
uri->port.value_or(0));
101+
102+
if (llvm::Error error = server_sp->Start(connection)) {
103+
result.AppendErrorWithFormatv("{0}", llvm::fmt_consume(std::move(error)));
104+
return;
105+
}
106+
107+
GetDebugger().AddProtocolServer(server_sp);
108+
109+
if (Socket *socket = server_sp->GetSocket()) {
110+
std::string address =
111+
llvm::join(socket->GetListeningConnectionURI(), ", ");
112+
result.AppendMessageWithFormatv(
113+
"{0} server started with connection listeners: {1}", protocol,
114+
address);
115+
}
116+
}
117+
};
118+
119+
class CommandObjectProtocolServerStop : public CommandObjectParsed {
120+
public:
121+
CommandObjectProtocolServerStop(CommandInterpreter &interpreter)
122+
: CommandObjectParsed(interpreter, "protocol-server stop",
123+
"stop protocol server",
124+
"protocol-server stop <protocol>") {
125+
AddSimpleArgumentList(lldb::eArgTypeProtocol, eArgRepeatPlain);
126+
}
127+
128+
~CommandObjectProtocolServerStop() override = default;
129+
130+
protected:
131+
void DoExecute(Args &args, CommandReturnObject &result) override {
132+
if (args.GetArgumentCount() < 1) {
133+
result.AppendError("no protocol specified");
134+
return;
135+
}
136+
137+
llvm::StringRef protocol = args.GetArgumentAtIndex(0);
138+
std::vector<llvm::StringRef> supported_protocols = GetSupportedProtocols();
139+
if (llvm::find(supported_protocols, protocol) ==
140+
supported_protocols.end()) {
141+
result.AppendErrorWithFormatv(
142+
"unsupported protocol: {0}. Supported protocols are: {1}", protocol,
143+
llvm::join(GetSupportedProtocols(), ", "));
144+
return;
145+
}
146+
147+
Debugger &debugger = GetDebugger();
148+
149+
ProtocolServerSP server_sp = debugger.GetProtocolServer(protocol);
150+
if (!server_sp) {
151+
result.AppendError(
152+
llvm::formatv("no {0} protocol server running", protocol).str());
153+
return;
154+
}
155+
156+
if (llvm::Error error = server_sp->Stop()) {
157+
result.AppendErrorWithFormatv("{0}", llvm::fmt_consume(std::move(error)));
158+
return;
159+
}
160+
161+
debugger.RemoveProtocolServer(server_sp);
162+
}
163+
};
164+
165+
CommandObjectProtocolServer::CommandObjectProtocolServer(
166+
CommandInterpreter &interpreter)
167+
: CommandObjectMultiword(interpreter, "protocol-server",
168+
"Start and stop a protocol server.",
169+
"protocol-server") {
170+
LoadSubCommand("start", CommandObjectSP(new CommandObjectProtocolServerStart(
171+
interpreter)));
172+
LoadSubCommand("stop", CommandObjectSP(
173+
new CommandObjectProtocolServerStop(interpreter)));
174+
}
175+
176+
CommandObjectProtocolServer::~CommandObjectProtocolServer() = default;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===-- CommandObjectProtocolServer.h
2+
//------------------------------------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#ifndef LLDB_SOURCE_COMMANDS_COMMANDOBJECTPROTOCOLSERVER_H
11+
#define LLDB_SOURCE_COMMANDS_COMMANDOBJECTPROTOCOLSERVER_H
12+
13+
#include "lldb/Interpreter/CommandObjectMultiword.h"
14+
15+
namespace lldb_private {
16+
17+
class CommandObjectProtocolServer : public CommandObjectMultiword {
18+
public:
19+
CommandObjectProtocolServer(CommandInterpreter &interpreter);
20+
~CommandObjectProtocolServer() override;
21+
};
22+
23+
} // namespace lldb_private
24+
25+
#endif // LLDB_SOURCE_COMMANDS_COMMANDOBJECTMCP_H

lldb/source/Core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ add_lldb_library(lldbCore NO_PLUGIN_DEPENDENCIES
4646
Opcode.cpp
4747
PluginManager.cpp
4848
Progress.cpp
49+
ProtocolServer.cpp
4950
Statusline.cpp
5051
RichManglingContext.cpp
5152
SearchFilter.cpp

lldb/source/Core/Debugger.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "lldb/Core/ModuleSpec.h"
1717
#include "lldb/Core/PluginManager.h"
1818
#include "lldb/Core/Progress.h"
19+
#include "lldb/Core/ProtocolServer.h"
1920
#include "lldb/Core/StreamAsynchronousIO.h"
2021
#include "lldb/Core/Telemetry.h"
2122
#include "lldb/DataFormatters/DataVisualization.h"
@@ -2375,3 +2376,26 @@ llvm::ThreadPoolInterface &Debugger::GetThreadPool() {
23752376
"Debugger::GetThreadPool called before Debugger::Initialize");
23762377
return *g_thread_pool;
23772378
}
2379+
2380+
void Debugger::AddProtocolServer(lldb::ProtocolServerSP protocol_server_sp) {
2381+
assert(protocol_server_sp &&
2382+
GetProtocolServer(protocol_server_sp->GetPluginName()) == nullptr);
2383+
m_protocol_servers.push_back(protocol_server_sp);
2384+
}
2385+
2386+
void Debugger::RemoveProtocolServer(lldb::ProtocolServerSP protocol_server_sp) {
2387+
auto it = llvm::find(m_protocol_servers, protocol_server_sp);
2388+
if (it != m_protocol_servers.end())
2389+
m_protocol_servers.erase(it);
2390+
}
2391+
2392+
lldb::ProtocolServerSP
2393+
Debugger::GetProtocolServer(llvm::StringRef protocol) const {
2394+
for (ProtocolServerSP protocol_server_sp : m_protocol_servers) {
2395+
if (!protocol_server_sp)
2396+
continue;
2397+
if (protocol_server_sp->GetPluginName() == protocol)
2398+
return protocol_server_sp;
2399+
}
2400+
return nullptr;
2401+
}

0 commit comments

Comments
 (0)