https://github.com/JDevlieghere updated 
https://github.com/llvm/llvm-project/pull/208506

>From 6a79f9746717b6f0a6b2f27980d1ad0e320daa6f Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <[email protected]>
Date: Thu, 9 Jul 2026 09:27:15 -0700
Subject: [PATCH 1/2] [lldb-mcp] Replace byte forwarding with a protocol-aware
 multiplexer

To serve several LLDB instances behind one endpoint it has to act as a
multiplexer. This PR adds a Multiplexer that presents a unified MCP
server to the client. It answers initialize and tools/list locally, and
forwards tools/call and the resource requests to the different instances
through an mcp::Client (added in #208371), relaying the answer back.

For now, this still drives a single backend. Discovering and routing
across several instances is coming next.

Assisted-by: Claude
---
 lldb/tools/lldb-mcp/CMakeLists.txt  |   1 +
 lldb/tools/lldb-mcp/Multiplexer.cpp | 170 ++++++++++++++++++++++++++++
 lldb/tools/lldb-mcp/Multiplexer.h   |  86 ++++++++++++++
 lldb/tools/lldb-mcp/lldb-mcp.cpp    |  73 ++++++------
 4 files changed, 294 insertions(+), 36 deletions(-)
 create mode 100644 lldb/tools/lldb-mcp/Multiplexer.cpp
 create mode 100644 lldb/tools/lldb-mcp/Multiplexer.h

diff --git a/lldb/tools/lldb-mcp/CMakeLists.txt 
b/lldb/tools/lldb-mcp/CMakeLists.txt
index ab68f853a0f49..3fbc95aeed24a 100644
--- a/lldb/tools/lldb-mcp/CMakeLists.txt
+++ b/lldb/tools/lldb-mcp/CMakeLists.txt
@@ -1,5 +1,6 @@
 add_lldb_tool(lldb-mcp
   lldb-mcp.cpp
+  Multiplexer.cpp
 
   LINK_COMPONENTS
     Option
diff --git a/lldb/tools/lldb-mcp/Multiplexer.cpp 
b/lldb/tools/lldb-mcp/Multiplexer.cpp
new file mode 100644
index 0000000000000..6c13136d3e3e5
--- /dev/null
+++ b/lldb/tools/lldb-mcp/Multiplexer.cpp
@@ -0,0 +1,170 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "Multiplexer.h"
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/JSON.h"
+
+using namespace llvm;
+using namespace lldb_protocol::mcp;
+using namespace lldb_mcp;
+
+namespace {
+
+/// Server identity reported to the client during initialization.
+/// @{
+constexpr llvm::StringLiteral kServerName = "lldb-mcp";
+constexpr llvm::StringLiteral kServerVersion = "0.1.0";
+/// @}
+
+/// Client-facing tool names.
+/// @{
+constexpr llvm::StringLiteral kToolCommand = "command";
+constexpr llvm::StringLiteral kToolSessionsList = "sessions_list";
+/// @}
+
+/// Backend tool names, as exposed by the LLDB MCP server.
+/// @{
+constexpr llvm::StringLiteral kBackendToolCommand = "command";
+constexpr llvm::StringLiteral kBackendToolDebuggerList = "debugger_list";
+/// @}
+
+} // namespace
+
+Multiplexer::Multiplexer(std::unique_ptr<MCPTransport> client_transport,
+                         LogCallback log_callback)
+    : m_client_transport(std::move(client_transport)),
+      m_client_binder(std::make_unique<MCPBinder>(*m_client_transport)),
+      m_log_callback(std::move(log_callback)) {
+  m_client_binder->Bind<InitializeResult, InitializeParams>(
+      "initialize", &Multiplexer::HandleInitialize, this);
+  m_client_binder->Bind<ListToolsResult, void>(
+      "tools/list", &Multiplexer::HandleToolsList, this);
+  m_client_binder->BindAsync<CallToolResult, CallToolParams>(
+      "tools/call", &Multiplexer::HandleToolsCall, this);
+  m_client_binder->BindAsync<ListResourcesResult, void>(
+      "resources/list", &Multiplexer::HandleResourcesList, this);
+  m_client_binder->BindAsync<ReadResourceResult, ReadResourceParams>(
+      "resources/read", &Multiplexer::HandleResourcesRead, this);
+  m_client_binder->Bind<void>("notifications/initialized", [this]() {
+    Log("client initialization complete");
+  });
+}
+
+void Multiplexer::AddBackend(std::unique_ptr<Client> backend) {
+  m_backends.push_back(std::move(backend));
+}
+
+llvm::Error Multiplexer::Run() {
+  m_client_binder->OnDisconnect(&Multiplexer::HandleDisconnect, this);
+  m_client_binder->OnError([this](llvm::Error err) {
+    Log(formatv("client transport error: {0}", 
toString(std::move(err))).str());
+  });
+  return m_client_transport->RegisterMessageHandler(*m_client_binder);
+}
+
+void Multiplexer::SetDisconnectHandler(llvm::unique_function<void()> handler) {
+  m_disconnect_handler = std::move(handler);
+}
+
+Client *Multiplexer::Route() {
+  if (m_backends.empty())
+    return nullptr;
+  return m_backends.front().get();
+}
+
+Expected<InitializeResult>
+Multiplexer::HandleInitialize(const InitializeParams &) {
+  InitializeResult result;
+  result.protocolVersion = kProtocolVersion;
+  result.capabilities.supportsToolsList = true;
+  result.capabilities.supportsResourcesList = true;
+  result.serverInfo.name = kServerName;
+  result.serverInfo.version = kServerVersion;
+  return result;
+}
+
+Expected<ListToolsResult> Multiplexer::HandleToolsList() {
+  ListToolsResult result;
+
+  ToolDefinition command;
+  command.name = kToolCommand;
+  command.description = "Run an LLDB command in a debug session.";
+  command.inputSchema = json::Object{
+      {"type", "object"},
+      {"properties",
+       json::Object{
+           {"command",
+            json::Object{{"type", "string"},
+                         {"description", "The LLDB command to run."}}},
+           {"debugger",
+            json::Object{{"type", "string"},
+                         {"description",
+                          "The debugger id or URI selecting the debug "
+                          "session. Defaults to the first."}}},
+       }},
+      {"required", json::Array{"command"}},
+  };
+  result.tools.push_back(std::move(command));
+
+  ToolDefinition sessions_list;
+  sessions_list.name = kToolSessionsList;
+  sessions_list.description = "List the active debug sessions.";
+  sessions_list.inputSchema = json::Object{{"type", "object"}};
+  result.tools.push_back(std::move(sessions_list));
+
+  return result;
+}
+
+void Multiplexer::HandleToolsCall(const CallToolParams &params,
+                                  Reply<CallToolResult> reply) {
+  Client *backend = Route();
+  if (!backend)
+    return reply(createStringError("no debug session available"));
+
+  std::optional<StringRef> backend_tool =
+      StringSwitch<std::optional<StringRef>>(params.name)
+          .Case(kToolCommand, kBackendToolCommand)
+          .Case(kToolSessionsList, kBackendToolDebuggerList)
+          .Default(std::nullopt);
+  if (!backend_tool)
+    return reply(createStringError(formatv("no tool \"{0}\"", params.name)));
+
+  CallToolParams backend_params;
+  backend_params.arguments = params.arguments;
+  backend_params.name = *backend_tool;
+
+  backend->ToolsCall(backend_params, std::move(reply));
+}
+
+void Multiplexer::HandleResourcesList(Reply<ListResourcesResult> reply) {
+  Client *backend = Route();
+  if (!backend)
+    return reply(ListResourcesResult{});
+  backend->ResourcesList(std::move(reply));
+}
+
+void Multiplexer::HandleResourcesRead(const ReadResourceParams &params,
+                                      Reply<ReadResourceResult> reply) {
+  Client *backend = Route();
+  if (!backend)
+    return reply(createStringError("no debug session available"));
+  backend->ResourcesRead(params, std::move(reply));
+}
+
+void Multiplexer::HandleDisconnect() {
+  if (m_disconnect_handler)
+    m_disconnect_handler();
+}
+
+void Multiplexer::Log(llvm::StringRef message) {
+  if (m_log_callback)
+    m_log_callback(message);
+}
diff --git a/lldb/tools/lldb-mcp/Multiplexer.h 
b/lldb/tools/lldb-mcp/Multiplexer.h
new file mode 100644
index 0000000000000..c6a09684cfdb5
--- /dev/null
+++ b/lldb/tools/lldb-mcp/Multiplexer.h
@@ -0,0 +1,86 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_TOOLS_LLDB_MCP_MULTIPLEXER_H
+#define LLDB_TOOLS_LLDB_MCP_MULTIPLEXER_H
+
+#include "lldb/Protocol/MCP/Client.h"
+#include "lldb/Protocol/MCP/Protocol.h"
+#include "lldb/Protocol/MCP/Transport.h"
+#include "llvm/ADT/FunctionExtras.h"
+#include "llvm/Support/Error.h"
+#include <memory>
+#include <vector>
+
+namespace lldb_mcp {
+
+/// The multiplexer is the single MCP server a client talks to. It presents a
+/// unified tool and resource surface and fans out to one or more backend LLDB
+/// MCP servers, each reached through an mcp::Client.
+///
+/// Requests that can be answered locally (initialize, tools/list) are handled
+/// directly. Requests that target a backend (tools/call, resources/list,
+/// resources/read) are forwarded asynchronously, and the backend's answer is
+/// relayed back to the client.
+class Multiplexer {
+public:
+  template <typename T> using Reply = lldb_private::transport::Reply<T>;
+
+  Multiplexer(
+      std::unique_ptr<lldb_protocol::mcp::MCPTransport> client_transport,
+      lldb_protocol::mcp::LogCallback log_callback = {});
+
+  /// Adds a backend. The client must already be running (see Client::Run).
+  /// Takes ownership.
+  void AddBackend(std::unique_ptr<lldb_protocol::mcp::Client> backend);
+
+  /// Registers the client-facing handlers. Backends should be added first.
+  llvm::Error Run();
+
+  /// Sets a handler invoked when the client disconnects (EOF).
+  void SetDisconnectHandler(llvm::unique_function<void()> handler);
+
+private:
+  /// Locally-answered requests.
+  /// @{
+  llvm::Expected<lldb_protocol::mcp::InitializeResult>
+  HandleInitialize(const lldb_protocol::mcp::InitializeParams &params);
+  llvm::Expected<lldb_protocol::mcp::ListToolsResult> HandleToolsList();
+  /// @}
+
+  /// Requests forwarded to a backend.
+  /// @{
+  void HandleToolsCall(const lldb_protocol::mcp::CallToolParams &params,
+                       Reply<lldb_protocol::mcp::CallToolResult> reply);
+  void
+  HandleResourcesList(Reply<lldb_protocol::mcp::ListResourcesResult> reply);
+  void HandleResourcesRead(const lldb_protocol::mcp::ReadResourceParams 
&params,
+                           Reply<lldb_protocol::mcp::ReadResourceResult> 
reply);
+  /// @}
+
+  /// Trampoline forwarding to the move-only disconnect handler.
+  void HandleDisconnect();
+
+  void Log(llvm::StringRef message);
+
+  /// Returns the backend a request routes to, or null if none is available.
+  /// All requests currently route to the single backend.
+  lldb_protocol::mcp::Client *Route();
+
+  std::unique_ptr<lldb_protocol::mcp::MCPTransport> m_client_transport;
+  lldb_protocol::mcp::MCPBinderUP m_client_binder;
+  lldb_protocol::mcp::LogCallback m_log_callback;
+
+  std::vector<std::unique_ptr<lldb_protocol::mcp::Client>> m_backends;
+
+  llvm::unique_function<void()> m_disconnect_handler;
+};
+
+} // namespace lldb_mcp
+
+#endif
diff --git a/lldb/tools/lldb-mcp/lldb-mcp.cpp b/lldb/tools/lldb-mcp/lldb-mcp.cpp
index 0d17500648049..7a21cca743de8 100644
--- a/lldb/tools/lldb-mcp/lldb-mcp.cpp
+++ b/lldb/tools/lldb-mcp/lldb-mcp.cpp
@@ -6,6 +6,7 @@
 //
 
//===----------------------------------------------------------------------===//
 
+#include "Multiplexer.h"
 #include "lldb/Host/Config.h"
 #include "lldb/Host/File.h"
 #include "lldb/Host/FileSystem.h"
@@ -15,7 +16,9 @@
 #include "lldb/Host/MainLoopBase.h"
 #include "lldb/Host/ProcessLaunchInfo.h"
 #include "lldb/Host/Socket.h"
+#include "lldb/Protocol/MCP/Client.h"
 #include "lldb/Protocol/MCP/Server.h"
+#include "lldb/Protocol/MCP/Transport.h"
 #include "lldb/Utility/FileSpec.h"
 #include "lldb/Utility/Status.h"
 #include "lldb/Utility/UriParser.h"
@@ -56,8 +59,6 @@ constexpr StringLiteral kDriverName = "lldb.exe";
 constexpr StringLiteral kDriverName = "lldb";
 #endif
 
-constexpr size_t kForwardIOBufferSize = 1024;
-
 inline void exitWithError(llvm::Error Err, StringRef Prefix = "") {
   handleAllErrors(std::move(Err), [&](ErrorInfoBase &Info) {
     WithColor::error(errs(), Prefix) << Info.message() << '\n';
@@ -127,37 +128,21 @@ Expected<ServerInfo> loadOrStart(
   return createStringError("timed out waiting for MCP server to start");
 }
 
-void forwardIO(MainLoopBase &loop, IOObjectSP &from, IOObjectSP &to) {
-  char buf[kForwardIOBufferSize];
-  size_t num_bytes = sizeof(buf);
-
-  if (llvm::Error err = from->Read(buf, num_bytes).takeError())
-    exitWithError(std::move(err));
-
-  // EOF reached.
-  if (num_bytes == 0)
-    return loop.RequestTermination();
-
-  if (llvm::Error err = to->Write(buf, num_bytes).takeError())
-    exitWithError(std::move(err));
-}
-
-llvm::Error connectAndForwardIO(lldb_private::MainLoop &loop, ServerInfo &info,
-                                IOObjectSP &input_sp, IOObjectSP &output_sp) {
+/// Connects to the MCP server described by \p info and returns the connected
+/// socket.
+Expected<IOObjectSP> connectToServer(const ServerInfo &info) {
   auto uri = lldb_private::URI::Parse(info.connection_uri);
   if (!uri)
     return createStringError("invalid connection_uri");
 
   std::optional<lldb_private::Socket::ProtocolModePair> protocol_and_mode =
       lldb_private::Socket::GetProtocolAndMode(uri->scheme);
-
   if (!protocol_and_mode)
     return createStringError("unknown protocol scheme");
 
   lldb_private::Status status;
   std::unique_ptr<lldb_private::Socket> sock =
       lldb_private::Socket::Create(protocol_and_mode->first, status);
-
   if (status.Fail())
     return status.takeError();
 
@@ -169,20 +154,15 @@ llvm::Error connectAndForwardIO(lldb_private::MainLoop 
&loop, ServerInfo &info,
   if (status.Fail())
     return status.takeError();
 
-  IOObjectSP sock_sp = std::move(sock);
-  auto input_handle = loop.RegisterReadObject(
-      input_sp, std::bind(forwardIO, std::placeholders::_1, input_sp, sock_sp),
-      status);
-  if (status.Fail())
-    return status.takeError();
-
-  auto socket_handle = loop.RegisterReadObject(
-      sock_sp, std::bind(forwardIO, std::placeholders::_1, sock_sp, output_sp),
-      status);
-  if (status.Fail())
-    return status.takeError();
+  return IOObjectSP(std::move(sock));
+}
 
-  return loop.Run().takeError();
+/// Returns a log callback that traces to stderr when LLDB_MCP_LOG is set in 
the
+/// environment, since stdout is reserved for the MCP protocol.
+lldb_protocol::mcp::LogCallback makeLogCallback() {
+  if (!std::getenv("LLDB_MCP_LOG"))
+    return {};
+  return [](StringRef message) { errs() << message << '\n'; };
 }
 
 } // namespace
@@ -236,8 +216,29 @@ int main(int argc, char *argv[]) {
         [](MainLoopBase &loop) { loop.RequestTermination(); });
   });
 
-  if (llvm::Error error =
-          connectAndForwardIO(loop, *server_info, input_sp, output_sp))
+  // Connect to the backend LLDB MCP server and drive it as an MCP client.
+  Expected<IOObjectSP> backend_io = connectToServer(*server_info);
+  if (!backend_io)
+    exitWithError(backend_io.takeError());
+
+  auto backend_transport = std::make_unique<lldb_protocol::mcp::Transport>(
+      loop, *backend_io, *backend_io, makeLogCallback());
+  auto backend = std::make_unique<lldb_protocol::mcp::Client>(
+      std::move(backend_transport), makeLogCallback());
+  if (llvm::Error error = backend->Run())
+    exitWithError(std::move(error));
+
+  // Present a unified MCP server to the client over stdio.
+  auto client_transport = std::make_unique<lldb_protocol::mcp::Transport>(
+      loop, input_sp, output_sp, makeLogCallback());
+  lldb_mcp::Multiplexer multiplexer(std::move(client_transport),
+                                    makeLogCallback());
+  multiplexer.AddBackend(std::move(backend));
+  multiplexer.SetDisconnectHandler([]() { loop.RequestTermination(); });
+  if (llvm::Error error = multiplexer.Run())
+    exitWithError(std::move(error));
+
+  if (llvm::Error error = loop.Run().takeError())
     exitWithError(std::move(error));
 
   return EXIT_SUCCESS;

>From 690484bdff48c65b6514d0f24f1b271079acf30d Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <[email protected]>
Date: Thu, 9 Jul 2026 20:09:09 -0700
Subject: [PATCH 2/2] Address review feedback

---
 lldb/include/lldb/Protocol/MCP/Protocol.h           |  4 ++++
 .../Plugins/Protocol/MCP/ProtocolServerMCP.cpp      |  4 ++--
 lldb/source/Protocol/MCP/Protocol.cpp               |  3 +++
 lldb/tools/lldb-mcp/Multiplexer.cpp                 | 13 ++++++++-----
 4 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/lldb/include/lldb/Protocol/MCP/Protocol.h 
b/lldb/include/lldb/Protocol/MCP/Protocol.h
index a0ba8659ffe24..1836afabc56f1 100644
--- a/lldb/include/lldb/Protocol/MCP/Protocol.h
+++ b/lldb/include/lldb/Protocol/MCP/Protocol.h
@@ -25,6 +25,10 @@ namespace lldb_protocol::mcp {
 
 static llvm::StringLiteral kProtocolVersion = "2024-11-05";
 
+/// Version reported by LLDB's MCP server. The function avoids having to 
include
+/// the version header.
+llvm::StringLiteral GetServerVersion();
+
 /// A Request or Response 'id'.
 ///
 /// NOTE: This differs from the JSON-RPC 2.0 spec. The MCP spec says this must
diff --git a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp 
b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp
index 96d6910ce5ce5..7264ab00fd7f0 100644
--- a/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp
+++ b/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp
@@ -26,7 +26,6 @@ using namespace llvm;
 LLDB_PLUGIN_DEFINE(ProtocolServerMCP)
 
 static constexpr llvm::StringLiteral kName = "lldb-mcp";
-static constexpr llvm::StringLiteral kVersion = "0.1.0";
 
 ProtocolServerMCP::ProtocolServerMCP() : ProtocolServer() {}
 
@@ -106,7 +105,8 @@ llvm::Error 
ProtocolServerMCP::Start(ProtocolServer::Connection connection) {
 
   m_client_count = 0;
   m_server = std::make_unique<lldb_protocol::mcp::Server>(
-      std::string(kName), std::string(kVersion), [](StringRef message) {
+      std::string(kName), std::string(lldb_protocol::mcp::GetServerVersion()),
+      [](StringRef message) {
         LLDB_LOG(GetLog(LLDBLog::Host), "MCP Server: {0}", message);
       });
   Extend(*m_server);
diff --git a/lldb/source/Protocol/MCP/Protocol.cpp 
b/lldb/source/Protocol/MCP/Protocol.cpp
index 13069ef28b175..e4354346f7927 100644
--- a/lldb/source/Protocol/MCP/Protocol.cpp
+++ b/lldb/source/Protocol/MCP/Protocol.cpp
@@ -7,6 +7,7 @@
 
//===----------------------------------------------------------------------===//
 
 #include "lldb/Protocol/MCP/Protocol.h"
+#include "lldb/Version/Version.inc"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/JSON.h"
 
@@ -14,6 +15,8 @@ using namespace llvm;
 
 namespace lldb_protocol::mcp {
 
+llvm::StringLiteral GetServerVersion() { return LLDB_VERSION_STRING; }
+
 static bool mapRaw(const json::Value &Params, StringLiteral Prop,
                    std::optional<json::Value> &V, json::Path P) {
   const auto *O = Params.getAsObject();
diff --git a/lldb/tools/lldb-mcp/Multiplexer.cpp 
b/lldb/tools/lldb-mcp/Multiplexer.cpp
index 6c13136d3e3e5..02bb40477c30a 100644
--- a/lldb/tools/lldb-mcp/Multiplexer.cpp
+++ b/lldb/tools/lldb-mcp/Multiplexer.cpp
@@ -18,11 +18,8 @@ using namespace lldb_mcp;
 
 namespace {
 
-/// Server identity reported to the client during initialization.
-/// @{
+/// Server name reported to the client during initialization.
 constexpr llvm::StringLiteral kServerName = "lldb-mcp";
-constexpr llvm::StringLiteral kServerVersion = "0.1.0";
-/// @}
 
 /// Client-facing tool names.
 /// @{
@@ -63,6 +60,12 @@ void Multiplexer::AddBackend(std::unique_ptr<Client> 
backend) {
 }
 
 llvm::Error Multiplexer::Run() {
+  // Run is called only after a backend has been added. Starting with none is a
+  // setup bug, not a usable server.
+  if (m_backends.empty())
+    return llvm::createStringError(
+        "no backends registered before Multiplexer::Run");
+
   m_client_binder->OnDisconnect(&Multiplexer::HandleDisconnect, this);
   m_client_binder->OnError([this](llvm::Error err) {
     Log(formatv("client transport error: {0}", 
toString(std::move(err))).str());
@@ -87,7 +90,7 @@ Multiplexer::HandleInitialize(const InitializeParams &) {
   result.capabilities.supportsToolsList = true;
   result.capabilities.supportsResourcesList = true;
   result.serverInfo.name = kServerName;
-  result.serverInfo.version = kServerVersion;
+  result.serverInfo.version = GetServerVersion();
   return result;
 }
 

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

Reply via email to