https://github.com/JDevlieghere created 
https://github.com/llvm/llvm-project/pull/209923

Starting a protocol server (such as MCP, and in the future potentially DAP) is 
only possible from the command line today, via `protocol-server start`. Add an 
SB API so an embedder can start one in its own process and learn where to 
connect.

SBProtocolServer wraps ProtocolServer::GetOrCreate/Start/Stop and exposes the 
listening connection URI. This lets a tool host the engine's protocol server 
in-process and talk to it as a normal client, reusing the server's tools rather 
than reimplementing them.

Assisted-by: Claude

>From 0ecf63c47a00bed7d6cb1d7b35f47fa5399783bc Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <[email protected]>
Date: Wed, 8 Jul 2026 17:31:48 -0700
Subject: [PATCH] [lldb] Add SBProtocolServer to start protocol servers
 programmatically

Starting a protocol server (MCP, DAP, ...) is only possible from the
command line today, via `protocol-server start`. Add an SB API so an
embedder can start one in its own process and learn where to connect.

SBProtocolServer wraps ProtocolServer::GetOrCreate/Start/Stop and exposes
the listening connection URI. This lets a tool host the engine's protocol
server in-process and talk to it as a normal client, reusing the server's
tools rather than reimplementing them.

Assisted-by: Claude
---
 lldb/bindings/interfaces.swig               |   1 +
 lldb/include/lldb/API/LLDB.h                |   1 +
 lldb/include/lldb/API/SBDefines.h           |   3 +-
 lldb/include/lldb/API/SBProtocolServer.h    |  54 +++++++
 lldb/source/API/CMakeLists.txt              |   1 +
 lldb/source/API/SBProtocolServer.cpp        | 160 ++++++++++++++++++++
 lldb/unittests/API/CMakeLists.txt           |   1 +
 lldb/unittests/API/SBProtocolServerTest.cpp | 128 ++++++++++++++++
 8 files changed, 348 insertions(+), 1 deletion(-)
 create mode 100644 lldb/include/lldb/API/SBProtocolServer.h
 create mode 100644 lldb/source/API/SBProtocolServer.cpp
 create mode 100644 lldb/unittests/API/SBProtocolServerTest.cpp

diff --git a/lldb/bindings/interfaces.swig b/lldb/bindings/interfaces.swig
index 5cdddd5136ac2..6f09c48fba3ab 100644
--- a/lldb/bindings/interfaces.swig
+++ b/lldb/bindings/interfaces.swig
@@ -140,6 +140,7 @@
 %include "lldb/API/SBProcessInfo.h"
 %include "lldb/API/SBProcessInfoList.h"
 %include "lldb/API/SBProgress.h"
+%include "lldb/API/SBProtocolServer.h"
 %include "lldb/API/SBQueue.h"
 %include "lldb/API/SBQueueItem.h"
 %include "lldb/API/SBReproducer.h"
diff --git a/lldb/include/lldb/API/LLDB.h b/lldb/include/lldb/API/LLDB.h
index 6ac35bb4a364b..af47d12dbc404 100644
--- a/lldb/include/lldb/API/LLDB.h
+++ b/lldb/include/lldb/API/LLDB.h
@@ -57,6 +57,7 @@
 #include "lldb/API/SBProcessInfo.h"
 #include "lldb/API/SBProcessInfoList.h"
 #include "lldb/API/SBProgress.h"
+#include "lldb/API/SBProtocolServer.h"
 #include "lldb/API/SBQueue.h"
 #include "lldb/API/SBQueueItem.h"
 #include "lldb/API/SBReproducer.h"
diff --git a/lldb/include/lldb/API/SBDefines.h 
b/lldb/include/lldb/API/SBDefines.h
index 5fcc685050c0b..7ec8e56067aa6 100644
--- a/lldb/include/lldb/API/SBDefines.h
+++ b/lldb/include/lldb/API/SBDefines.h
@@ -98,6 +98,7 @@ class LLDB_API SBProcess;
 class LLDB_API SBProcessInfo;
 class LLDB_API SBProcessInfoList;
 class LLDB_API SBProgress;
+class LLDB_API SBProtocolServer;
 class LLDB_API SBQueue;
 class LLDB_API SBQueueItem;
 class LLDB_API SBReplayOptions;
@@ -152,6 +153,6 @@ typedef lldb::CommandReturnObjectCallbackResult 
(*SBCommandPrintCallback)(
 typedef lldb::SBError (*SBPlatformLocateModuleCallback)(
     void *baton, const lldb::SBModuleSpec &module_spec,
     lldb::SBFileSpec &module_file_spec, lldb::SBFileSpec &symbol_file_spec);
-}
+} // namespace lldb
 
 #endif // LLDB_API_SBDEFINES_H
diff --git a/lldb/include/lldb/API/SBProtocolServer.h 
b/lldb/include/lldb/API/SBProtocolServer.h
new file mode 100644
index 0000000000000..625a71856aa9d
--- /dev/null
+++ b/lldb/include/lldb/API/SBProtocolServer.h
@@ -0,0 +1,54 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_API_SBPROTOCOLSERVER_H
+#define LLDB_API_SBPROTOCOLSERVER_H
+
+#include "lldb/API/SBDefines.h"
+
+namespace lldb_private {
+class SBProtocolServerImpl;
+}
+
+namespace lldb {
+
+/// A server that speaks a debugging protocol.
+///
+/// Protocol servers are shared across debuggers. Creating a server for a
+/// protocol that already has one hands back the existing server.
+class LLDB_API SBProtocolServer {
+public:
+  SBProtocolServer();
+  SBProtocolServer(const SBProtocolServer &rhs);
+  SBProtocolServer &operator=(const SBProtocolServer &rhs);
+  ~SBProtocolServer();
+
+  /// Returns the protocol server for \p protocol, creating it if this process
+  /// does not already have one. On failure \p error is set.
+  static SBProtocolServer Create(const char *protocol, lldb::SBError &error);
+
+  explicit operator bool() const;
+  bool IsValid() const;
+
+  /// Starts listening for clients on \p connection_uri, which must be an
+  /// accepting URI such as "listen://[host]:port" or "accept:///path".
+  lldb::SBError Start(const char *connection_uri);
+
+  lldb::SBError Stop();
+
+  /// Returns the URI clients can connect to, valid after a successful Start,
+  /// or nullptr if the server is not listening.
+  const char *GetConnectionURI();
+
+private:
+  std::unique_ptr<lldb_private::SBProtocolServerImpl> m_opaque_up;
+};
+
+} // namespace lldb
+
+#endif
diff --git a/lldb/source/API/CMakeLists.txt b/lldb/source/API/CMakeLists.txt
index 15c52aab41368..83ecb428d8ea4 100644
--- a/lldb/source/API/CMakeLists.txt
+++ b/lldb/source/API/CMakeLists.txt
@@ -81,6 +81,7 @@ add_lldb_library(liblldb SHARED ${option_framework}
   SBProcessInfo.cpp
   SBProcessInfoList.cpp
   SBProgress.cpp
+  SBProtocolServer.cpp
   SBQueue.cpp
   SBQueueItem.cpp
   SBReproducer.cpp
diff --git a/lldb/source/API/SBProtocolServer.cpp 
b/lldb/source/API/SBProtocolServer.cpp
new file mode 100644
index 0000000000000..7b3796599c082
--- /dev/null
+++ b/lldb/source/API/SBProtocolServer.cpp
@@ -0,0 +1,160 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/API/SBProtocolServer.h"
+#include "lldb/API/SBError.h"
+#include "lldb/Core/ProtocolServer.h"
+#include "lldb/Host/Socket.h"
+#include "lldb/Utility/ConstString.h"
+#include "lldb/Utility/Instrumentation.h"
+#include "lldb/Utility/UriParser.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/FormatVariadic.h"
+
+#include <memory>
+
+using namespace lldb;
+using namespace lldb_private;
+
+/// PIMPL backing SBProtocolServer.
+class lldb_private::SBProtocolServerImpl {
+public:
+  ProtocolServer *server = nullptr;
+};
+
+SBProtocolServer::SBProtocolServer()
+    : m_opaque_up(std::make_unique<SBProtocolServerImpl>()) {
+  LLDB_INSTRUMENT_VA(this);
+}
+
+SBProtocolServer::SBProtocolServer(const SBProtocolServer &rhs)
+    : m_opaque_up(std::make_unique<SBProtocolServerImpl>(*rhs.m_opaque_up)) {
+  LLDB_INSTRUMENT_VA(this, rhs);
+}
+
+SBProtocolServer &SBProtocolServer::operator=(const SBProtocolServer &rhs) {
+  LLDB_INSTRUMENT_VA(this, rhs);
+
+  if (this != &rhs)
+    *m_opaque_up = *rhs.m_opaque_up;
+  return *this;
+}
+
+SBProtocolServer::~SBProtocolServer() = default;
+
+SBProtocolServer SBProtocolServer::Create(const char *protocol,
+                                          SBError &error) {
+  LLDB_INSTRUMENT_VA(protocol, error);
+
+  SBProtocolServer server;
+  error.Clear();
+
+  if (!protocol || !protocol[0]) {
+    error.SetErrorString("no protocol specified");
+    return server;
+  }
+
+  ProtocolServer *protocol_server = ProtocolServer::GetOrCreate(protocol);
+  if (!protocol_server) {
+    error.SetErrorStringWithFormat(
+        "unsupported protocol: %s. Supported protocols are: %s", protocol,
+        llvm::join(ProtocolServer::GetSupportedProtocols(), ", ").c_str());
+    return server;
+  }
+
+  server.m_opaque_up->server = protocol_server;
+  return server;
+}
+
+SBProtocolServer::operator bool() const {
+  LLDB_INSTRUMENT_VA(this);
+
+  return m_opaque_up->server != nullptr;
+}
+
+bool SBProtocolServer::IsValid() const {
+  LLDB_INSTRUMENT_VA(this);
+
+  return m_opaque_up->server != nullptr;
+}
+
+SBError SBProtocolServer::Start(const char *connection_uri) {
+  LLDB_INSTRUMENT_VA(this, connection_uri);
+
+  SBError error;
+  if (!m_opaque_up->server) {
+    error.SetErrorString("invalid protocol server");
+    return error;
+  }
+
+  const char *connection_error =
+      "unsupported connection specifier, expected 'accept:///path' or "
+      "'listen://[host]:port'";
+  std::optional<URI> uri = URI::Parse(connection_uri ? connection_uri : "");
+  if (!uri) {
+    error.SetErrorString(connection_error);
+    return error;
+  }
+
+  std::optional<Socket::ProtocolModePair> protocol_and_mode =
+      Socket::GetProtocolAndMode(uri->scheme);
+  if (!protocol_and_mode || protocol_and_mode->second != Socket::ModeAccept) {
+    error.SetErrorString(connection_error);
+    return error;
+  }
+
+  ProtocolServer::Connection connection;
+  connection.protocol = protocol_and_mode->first;
+  if (connection.protocol == Socket::SocketProtocol::ProtocolUnixDomain)
+    connection.name = uri->path;
+  else
+    connection.name =
+        llvm::formatv("[{0}]:{1}",
+                      uri->hostname.empty() ? "0.0.0.0" : uri->hostname,
+                      uri->port.value_or(0))
+            .str();
+
+  if (llvm::Error err = m_opaque_up->server->Start(connection))
+    error.SetErrorString(llvm::toString(std::move(err)).c_str());
+
+  return error;
+}
+
+SBError SBProtocolServer::Stop() {
+  LLDB_INSTRUMENT_VA(this);
+
+  SBError error;
+  if (!m_opaque_up->server) {
+    error.SetErrorString("invalid protocol server");
+    return error;
+  }
+
+  if (llvm::Error err = m_opaque_up->server->Stop())
+    error.SetErrorString(llvm::toString(std::move(err)).c_str());
+
+  return error;
+}
+
+const char *SBProtocolServer::GetConnectionURI() {
+  LLDB_INSTRUMENT_VA(this);
+
+  if (!m_opaque_up->server)
+    return nullptr;
+
+  Socket *socket = m_opaque_up->server->GetSocket();
+  if (!socket)
+    return nullptr;
+
+  std::vector<std::string> uris = socket->GetListeningConnectionURI();
+  if (uris.empty())
+    return nullptr;
+
+  // A listening socket may report several equivalent URIs (e.g. IPv6 and
+  // IPv4 loopback). Return the first, interned so the pointer stays valid.
+  return ConstString(uris.front()).GetCString();
+}
diff --git a/lldb/unittests/API/CMakeLists.txt 
b/lldb/unittests/API/CMakeLists.txt
index b86054fb353f7..a7db51aea4994 100644
--- a/lldb/unittests/API/CMakeLists.txt
+++ b/lldb/unittests/API/CMakeLists.txt
@@ -3,6 +3,7 @@ add_lldb_unittest(APITests
   SBLineEntryTest.cpp
   SBMutexTest.cpp
   SBBreakpointClearConditionTest.cpp
+  SBProtocolServerTest.cpp
 
   SBAPITEST
 
diff --git a/lldb/unittests/API/SBProtocolServerTest.cpp 
b/lldb/unittests/API/SBProtocolServerTest.cpp
new file mode 100644
index 0000000000000..1191c6761e549
--- /dev/null
+++ b/lldb/unittests/API/SBProtocolServerTest.cpp
@@ -0,0 +1,128 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// Use the umbrella header for -Wdocumentation.
+#include "lldb/API/LLDB.h"
+
+#include "TestingSupport/SubsystemRAII.h"
+#include "lldb/API/SBDebugger.h"
+#include "lldb/API/SBError.h"
+#include "lldb/API/SBProtocolServer.h"
+#include "gtest/gtest.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+class SBProtocolServerTest : public testing::Test {
+protected:
+  void SetUp() override {
+    debugger = SBDebugger::Create(/*source_init_files=*/false);
+  }
+
+  void TearDown() override { SBDebugger::Destroy(debugger); }
+
+  SubsystemRAII<lldb::SBDebugger> subsystems;
+  SBDebugger debugger;
+};
+
+TEST_F(SBProtocolServerTest, DefaultConstructedIsInvalid) {
+  SBProtocolServer server;
+  EXPECT_FALSE(server.IsValid());
+  EXPECT_FALSE(static_cast<bool>(server));
+}
+
+TEST_F(SBProtocolServerTest, CreateMCP) {
+  SBError error;
+  SBProtocolServer server = SBProtocolServer::Create("MCP", error);
+  ASSERT_TRUE(error.Success()) << error.GetCString();
+  EXPECT_TRUE(server.IsValid());
+  EXPECT_TRUE(static_cast<bool>(server));
+  // No socket is bound until Start, so there is no connection URI yet.
+  EXPECT_EQ(server.GetConnectionURI(), nullptr);
+}
+
+TEST_F(SBProtocolServerTest, CreateUnsupportedProtocol) {
+  SBError error;
+  SBProtocolServer server = SBProtocolServer::Create("bogus", error);
+  EXPECT_TRUE(error.Fail());
+  EXPECT_FALSE(server.IsValid());
+}
+
+TEST_F(SBProtocolServerTest, CreateNullProtocol) {
+  SBError error;
+  SBProtocolServer server = SBProtocolServer::Create(nullptr, error);
+  EXPECT_TRUE(error.Fail());
+  EXPECT_FALSE(server.IsValid());
+}
+
+TEST_F(SBProtocolServerTest, CreateEmptyProtocol) {
+  SBError error;
+  SBProtocolServer server = SBProtocolServer::Create("", error);
+  EXPECT_TRUE(error.Fail());
+  EXPECT_FALSE(server.IsValid());
+}
+
+TEST_F(SBProtocolServerTest, StartAndStop) {
+  SBError error;
+  SBProtocolServer server = SBProtocolServer::Create("MCP", error);
+  ASSERT_TRUE(server.IsValid());
+
+  // Bind an ephemeral port on the loopback interface so the test never touches
+  // an external network.
+  SBError start_error = server.Start("listen://[localhost]:0");
+  ASSERT_TRUE(start_error.Success()) << start_error.GetCString();
+
+  // Once listening, the server reports the concrete URI a client can connect
+  // to (the ephemeral port resolved above).
+  EXPECT_NE(server.GetConnectionURI(), nullptr);
+
+  EXPECT_TRUE(server.Stop().Success());
+}
+
+TEST_F(SBProtocolServerTest, StartInvalidServer) {
+  // A default-constructed server has no backing protocol server, so Start 
fails
+  // before the connection URI is ever examined.
+  SBProtocolServer server;
+  EXPECT_TRUE(server.Start("").Fail());
+}
+
+TEST_F(SBProtocolServerTest, StopNotRunning) {
+  SBError error;
+  SBProtocolServer server = SBProtocolServer::Create("MCP", error);
+  ASSERT_TRUE(server.IsValid());
+  // Stopping a server that was never started is an error.
+  EXPECT_TRUE(server.Stop().Fail());
+}
+
+TEST_F(SBProtocolServerTest, StartRejectsNonAcceptingURI) {
+  SBError error;
+  SBProtocolServer server = SBProtocolServer::Create("MCP", error);
+  ASSERT_TRUE(server.IsValid());
+  // A connecting URI (not an accepting one) is rejected before any socket 
work.
+  EXPECT_TRUE(server.Start("connect://localhost:1234").Fail());
+  EXPECT_TRUE(server.Start("not a uri").Fail());
+}
+
+TEST_F(SBProtocolServerTest, CopyReferencesSameServer) {
+  SBError error;
+  SBProtocolServer server = SBProtocolServer::Create("MCP", error);
+  ASSERT_TRUE(server.IsValid());
+
+  // The protocol server is owned by the engine, not by SBProtocolServer, so a
+  // copy just references the same server and the source stays valid.
+  SBProtocolServer copy(server);
+  EXPECT_TRUE(copy.IsValid());
+  EXPECT_TRUE(server.IsValid());
+
+  // Copy assignment behaves the same way.
+  SBProtocolServer assigned;
+  EXPECT_FALSE(assigned.IsValid());
+  assigned = server;
+  EXPECT_TRUE(assigned.IsValid());
+  EXPECT_TRUE(server.IsValid());
+}

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to