llvmbot wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: John Harrison (ashgti)

<details>
<summary>Changes</summary>

The `lldb_protocol::mcp::Binder` class is used to craft bindings between 
requests and notifications to specific handlers.

This supports both incoming and outgoing handlers that bind these functions to 
a MessageHandler and generates encoding/decoding helpers for each call.

For example, see the `lldb_protocol::mcp::Server` class that has been greatly 
simplified.

---

Patch is 75.70 KiB, truncated to 20.00 KiB below, full version: 
https://github.com/llvm/llvm-project/pull/155315.diff


19 Files Affected:

- (added) lldb/include/lldb/Protocol/MCP/Binder.h (+351) 
- (modified) lldb/include/lldb/Protocol/MCP/Protocol.h (+169-4) 
- (modified) lldb/include/lldb/Protocol/MCP/Resource.h (+1-1) 
- (modified) lldb/include/lldb/Protocol/MCP/Server.h (+21-53) 
- (modified) lldb/include/lldb/Protocol/MCP/Tool.h (+7-2) 
- (added) lldb/include/lldb/Protocol/MCP/Transport.h (+50) 
- (modified) lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp (+9-11) 
- (modified) lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.h (+3-1) 
- (modified) lldb/source/Plugins/Protocol/MCP/Resource.cpp (+5-5) 
- (modified) lldb/source/Plugins/Protocol/MCP/Resource.h (+3-3) 
- (modified) lldb/source/Plugins/Protocol/MCP/Tool.cpp (+15-11) 
- (modified) lldb/source/Plugins/Protocol/MCP/Tool.h (+4-3) 
- (added) lldb/source/Protocol/MCP/Binder.cpp (+139) 
- (modified) lldb/source/Protocol/MCP/CMakeLists.txt (+3) 
- (modified) lldb/source/Protocol/MCP/Protocol.cpp (+157-2) 
- (modified) lldb/source/Protocol/MCP/Server.cpp (+55-200) 
- (added) lldb/source/Protocol/MCP/Transport.cpp (+113) 
- (modified) lldb/unittests/Protocol/ProtocolMCPTest.cpp (+6-4) 
- (modified) lldb/unittests/ProtocolServer/ProtocolMCPServerTest.cpp (+49-29) 


``````````diff
diff --git a/lldb/include/lldb/Protocol/MCP/Binder.h 
b/lldb/include/lldb/Protocol/MCP/Binder.h
new file mode 100644
index 0000000000000..f9cebd940bfcb
--- /dev/null
+++ b/lldb/include/lldb/Protocol/MCP/Binder.h
@@ -0,0 +1,351 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_PROTOCOL_MCP_BINDER_H
+#define LLDB_PROTOCOL_MCP_BINDER_H
+
+#include "lldb/Protocol/MCP/MCPError.h"
+#include "lldb/Protocol/MCP/Protocol.h"
+#include "lldb/Protocol/MCP/Transport.h"
+#include "lldb/Utility/Status.h"
+#include "llvm/ADT/FunctionExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+#include <functional>
+#include <future>
+#include <memory>
+#include <mutex>
+#include <optional>
+
+namespace lldb_protocol::mcp {
+
+template <typename T> using Callback = llvm::unique_function<T>;
+
+template <typename T>
+using Reply = llvm::unique_function<void(llvm::Expected<T>)>;
+template <typename Params, typename Result>
+using OutgoingRequest =
+    llvm::unique_function<void(const Params &, Reply<Result>)>;
+template <typename Params>
+using OutgoingNotification = llvm::unique_function<void(const Params &)>;
+
+template <typename Params, typename Result>
+llvm::Expected<Result> AsyncInvoke(lldb_private::MainLoop &loop,
+                                   OutgoingRequest<Params, Result> &fn,
+                                   const Params &params) {
+  std::promise<llvm::Expected<Result>> result_promise;
+  std::future<llvm::Expected<Result>> result_future =
+      result_promise.get_future();
+  std::thread thr([&loop, &fn, params,
+                   result_promise = std::move(result_promise)]() mutable {
+    fn(params, [&loop, &result_promise](llvm::Expected<Result> result) mutable 
{
+      result_promise.set_value(std::move(result));
+      loop.AddPendingCallback(
+          [](lldb_private::MainLoopBase &loop) { loop.RequestTermination(); });
+    });
+    if (llvm::Error error = loop.Run().takeError())
+      result_promise.set_value(std::move(error));
+  });
+  thr.join();
+  return result_future.get();
+}
+
+/// Binder collects a table of functions that handle calls.
+///
+/// The wrapper takes care of parsing/serializing responses.
+class Binder {
+public:
+  explicit Binder(MCPTransport *transport) : m_handlers(transport) {}
+
+  Binder(const Binder &) = delete;
+  Binder &operator=(const Binder &) = delete;
+
+  /// Bind a handler on transport disconnect.
+  template <typename ThisT, typename... ExtraArgs>
+  void disconnected(void (ThisT::*handler)(MCPTransport *), ThisT *_this,
+                    ExtraArgs... extra_args) {
+    m_handlers.m_disconnect_handler =
+        std::bind(handler, _this, std::placeholders::_1,
+                  std::forward<ExtraArgs>(extra_args)...);
+  }
+
+  /// Bind a handler on error when communicating with the transport.
+  template <typename ThisT, typename... ExtraArgs>
+  void error(void (ThisT::*handler)(MCPTransport *, llvm::Error), ThisT *_this,
+             ExtraArgs... extra_args) {
+    m_handlers.m_error_handler =
+        std::bind(handler, _this, std::placeholders::_1, std::placeholders::_2,
+                  std::forward<ExtraArgs>(extra_args)...);
+  }
+
+  /// Bind a handler for a request.
+  /// e.g. Bind.request("peek", this, &ThisModule::peek);
+  /// Handler should be e.g. Expected<PeekResult> peek(const PeekParams&);
+  /// PeekParams must be JSON parsable and PeekResult must be serializable.
+  template <typename Result, typename Params, typename ThisT,
+            typename... ExtraArgs>
+  void request(llvm::StringLiteral method,
+               llvm::Expected<Result> (ThisT::*fn)(const Params &,
+                                                   ExtraArgs...),
+               ThisT *_this, ExtraArgs... extra_args) {
+    assert(m_handlers.m_request_handlers.find(method) ==
+               m_handlers.m_request_handlers.end() &&
+           "request already bound");
+    std::function<llvm::Expected<Result>(const Params &)> handler =
+        std::bind(fn, _this, std::placeholders::_1,
+                  std::forward<ExtraArgs>(extra_args)...);
+    m_handlers.m_request_handlers[method] =
+        [method, handler](const Request &req,
+                          llvm::unique_function<void(const Response &)> reply) 
{
+          Params params;
+          llvm::json::Path::Root root(method);
+          if (!fromJSON(req.params, params, root)) {
+            reply(Response{0, Error{eErrorCodeInvalidParams,
+                                    "invalid params for " + method.str() +
+                                        ": " + llvm::toString(root.getError()),
+                                    std::nullopt}});
+            return;
+          }
+          llvm::Expected<Result> result = handler(params);
+          if (llvm::Error error = result.takeError()) {
+            Error protocol_error;
+            llvm::handleAllErrors(
+                std::move(error),
+                [&](const MCPError &err) {
+                  protocol_error = err.toProtocolError();
+                },
+                [&](const llvm::ErrorInfoBase &err) {
+                  protocol_error.code = MCPError::kInternalError;
+                  protocol_error.message = err.message();
+                });
+            reply(Response{0, protocol_error});
+            return;
+          }
+
+          reply(Response{0, *result});
+        };
+  }
+
+  /// Bind a handler for an async request.
+  /// e.g. Bind.asyncRequest("peek", this, &ThisModule::peek);
+  /// Handler should be e.g. `void peek(const PeekParams&,
+  /// Reply<Expected<PeekResult>>);` PeekParams must be JSON parsable and
+  /// PeekResult must be serializable.
+  template <typename Result, typename Params, typename... ExtraArgs>
+  void asyncRequest(
+      llvm::StringLiteral method,
+      std::function<void(const Params &, ExtraArgs..., Reply<Result>)> fn,
+      ExtraArgs... extra_args) {
+    assert(m_handlers.m_request_handlers.find(method) ==
+               m_handlers.m_request_handlers.end() &&
+           "request already bound");
+    std::function<void(const Params &, Reply<Result>)> handler = std::bind(
+        fn, std::placeholders::_1, std::forward<ExtraArgs>(extra_args)...,
+        std::placeholders::_2);
+    m_handlers.m_request_handlers[method] =
+        [method, handler](const Request &req,
+                          Callback<void(const Response &)> reply) {
+          Params params;
+          llvm::json::Path::Root root(method);
+          if (!fromJSON(req.params, params, root)) {
+            reply(Response{0, Error{eErrorCodeInvalidParams,
+                                    "invalid params for " + method.str() +
+                                        ": " + llvm::toString(root.getError()),
+                                    std::nullopt}});
+            return;
+          }
+
+          handler(params, [reply = std::move(reply)](
+                              llvm::Expected<Result> result) mutable {
+            if (llvm::Error error = result.takeError()) {
+              Error protocol_error;
+              llvm::handleAllErrors(
+                  std::move(error),
+                  [&](const MCPError &err) {
+                    protocol_error = err.toProtocolError();
+                  },
+                  [&](const llvm::ErrorInfoBase &err) {
+                    protocol_error.code = MCPError::kInternalError;
+                    protocol_error.message = err.message();
+                  });
+              reply(Response{0, protocol_error});
+              return;
+            }
+
+            reply(Response{0, toJSON(*result)});
+          });
+        };
+  }
+  template <typename Result, typename Params, typename ThisT,
+            typename... ExtraArgs>
+  void asyncRequest(llvm::StringLiteral method,
+                    void (ThisT::*fn)(const Params &, ExtraArgs...,
+                                      Reply<Result>),
+                    ThisT *_this, ExtraArgs... extra_args) {
+    assert(m_handlers.m_request_handlers.find(method) ==
+               m_handlers.m_request_handlers.end() &&
+           "request already bound");
+    std::function<void(const Params &, Reply<Result>)> handler = std::bind(
+        fn, _this, std::placeholders::_1,
+        std::forward<ExtraArgs>(extra_args)..., std::placeholders::_2);
+    m_handlers.m_request_handlers[method] =
+        [method, handler](const Request &req,
+                          Callback<void(const Response &)> reply) {
+          Params params;
+          llvm::json::Path::Root root;
+          if (!fromJSON(req.params, params, root)) {
+            reply(Response{0, Error{eErrorCodeInvalidParams,
+                                    "invalid params for " + method.str(),
+                                    std::nullopt}});
+            return;
+          }
+
+          handler(params, [reply = std::move(reply)](
+                              llvm::Expected<Result> result) mutable {
+            if (llvm::Error error = result.takeError()) {
+              Error protocol_error;
+              llvm::handleAllErrors(
+                  std::move(error),
+                  [&](const MCPError &err) {
+                    protocol_error = err.toProtocolError();
+                  },
+                  [&](const llvm::ErrorInfoBase &err) {
+                    protocol_error.code = MCPError::kInternalError;
+                    protocol_error.message = err.message();
+                  });
+              reply(Response{0, protocol_error});
+              return;
+            }
+
+            reply(Response{0, toJSON(*result)});
+          });
+        };
+  }
+
+  /// Bind a handler for a notification.
+  /// e.g. Bind.notification("peek", this, &ThisModule::peek);
+  /// Handler should be e.g. void peek(const PeekParams&);
+  /// PeekParams must be JSON parsable.
+  template <typename Params, typename ThisT, typename... ExtraArgs>
+  void notification(llvm::StringLiteral method,
+                    void (ThisT::*fn)(const Params &, ExtraArgs...),
+                    ThisT *_this, ExtraArgs... extra_args) {
+    std::function<void(const Params &)> handler =
+        std::bind(fn, _this, std::placeholders::_1,
+                  std::forward<ExtraArgs>(extra_args)...);
+    m_handlers.m_notification_handlers[method] =
+        [handler](const Notification &note) {
+          Params params;
+          llvm::json::Path::Root root;
+          if (!fromJSON(note.params, params, root))
+            return; // FIXME: log error?
+
+          handler(params);
+        };
+  }
+  template <typename Params>
+  void notification(llvm::StringLiteral method,
+                    std::function<void(const Params &)> handler) {
+    assert(m_handlers.m_notification_handlers.find(method) ==
+               m_handlers.m_notification_handlers.end() &&
+           "notification already bound");
+    m_handlers.m_notification_handlers[method] =
+        [handler = std::move(handler)](const Notification &note) {
+          Params params;
+          llvm::json::Path::Root root;
+          if (!fromJSON(note.params, params, root))
+            return; // FIXME: log error?
+
+          handler(params);
+        };
+  }
+
+  /// Bind a function object to be used for outgoing requests.
+  /// e.g. OutgoingRequest<Params, Result> Edit = Bind.outgoingRequest("edit");
+  /// Params must be JSON-serializable, Result must be parsable.
+  template <typename Params, typename Result>
+  OutgoingRequest<Params, Result> outgoingRequest(llvm::StringLiteral method) {
+    return [this, method](const Params &params, Reply<Result> reply) {
+      Request request;
+      request.method = method;
+      request.params = toJSON(params);
+      m_handlers.Send(request, [reply = std::move(reply)](
+                                   const Response &resp) mutable {
+        if (const lldb_protocol::mcp::Error *err =
+                std::get_if<lldb_protocol::mcp::Error>(&resp.result)) {
+          reply(llvm::make_error<MCPError>(err->message, err->code));
+          return;
+        }
+        Result result;
+        llvm::json::Path::Root root;
+        if (!fromJSON(std::get<llvm::json::Value>(resp.result), result, root)) 
{
+          reply(llvm::make_error<MCPError>("parsing response failed: " +
+                                           llvm::toString(root.getError())));
+          return;
+        }
+        reply(result);
+      });
+    };
+  }
+
+  /// Bind a function object to be used for outgoing notifications.
+  /// e.g. OutgoingNotification<LogParams> Log = Bind.outgoingMethod("log");
+  /// LogParams must be JSON-serializable.
+  template <typename Params>
+  OutgoingNotification<Params>
+  outgoingNotification(llvm::StringLiteral method) {
+    return [this, method](const Params &params) {
+      Notification note;
+      note.method = method;
+      note.params = toJSON(params);
+      m_handlers.Send(note);
+    };
+  }
+
+  operator MCPTransport::MessageHandler &() { return m_handlers; }
+
+private:
+  class RawHandler final : public MCPTransport::MessageHandler {
+  public:
+    explicit RawHandler(MCPTransport *transport);
+
+    void Received(const Notification &note) override;
+    void Received(const Request &req) override;
+    void Received(const Response &resp) override;
+    void OnError(llvm::Error err) override;
+    void OnClosed() override;
+
+    void Send(const Request &req,
+              Callback<void(const Response &)> response_handler);
+    void Send(const Notification &note);
+    void Send(const Response &resp);
+
+    friend class Binder;
+
+  private:
+    std::recursive_mutex m_mutex;
+    MCPTransport *m_transport;
+    int m_seq = 0;
+    std::map<Id, Callback<void(const Response &)>> m_pending_responses;
+    llvm::StringMap<
+        Callback<void(const Request &, Callback<void(const Response &)>)>>
+        m_request_handlers;
+    llvm::StringMap<Callback<void(const Notification &)>>
+        m_notification_handlers;
+    Callback<void(MCPTransport *)> m_disconnect_handler;
+    Callback<void(MCPTransport *, llvm::Error)> m_error_handler;
+  };
+
+  RawHandler m_handlers;
+};
+using BinderUP = std::unique_ptr<Binder>;
+
+} // namespace lldb_protocol::mcp
+
+#endif
diff --git a/lldb/include/lldb/Protocol/MCP/Protocol.h 
b/lldb/include/lldb/Protocol/MCP/Protocol.h
index 49f9490221755..d21a5ef85ece6 100644
--- a/lldb/include/lldb/Protocol/MCP/Protocol.h
+++ b/lldb/include/lldb/Protocol/MCP/Protocol.h
@@ -14,10 +14,12 @@
 #ifndef LLDB_PROTOCOL_MCP_PROTOCOL_H
 #define LLDB_PROTOCOL_MCP_PROTOCOL_H
 
+#include "lldb/lldb-types.h"
 #include "llvm/Support/JSON.h"
 #include <optional>
 #include <string>
 #include <variant>
+#include <vector>
 
 namespace lldb_protocol::mcp {
 
@@ -43,6 +45,12 @@ llvm::json::Value toJSON(const Request &);
 bool fromJSON(const llvm::json::Value &, Request &, llvm::json::Path);
 bool operator==(const Request &, const Request &);
 
+enum ErrorCode : signed {
+  eErrorCodeMethodNotFound = -32601,
+  eErrorCodeInvalidParams = -32602,
+  eErrorCodeInternalError = -32000,
+};
+
 struct Error {
   /// The error type that occurred.
   int64_t code = 0;
@@ -147,6 +155,14 @@ struct Resource {
 llvm::json::Value toJSON(const Resource &);
 bool fromJSON(const llvm::json::Value &, Resource &, llvm::json::Path);
 
+/// The server’s response to a resources/list request from the client.
+struct ResourcesListResult {
+  std::vector<Resource> resources;
+};
+llvm::json::Value toJSON(const ResourcesListResult &);
+bool fromJSON(const llvm::json::Value &, ResourcesListResult &,
+              llvm::json::Path);
+
 /// The contents of a specific resource or sub-resource.
 struct ResourceContents {
   /// The URI of this resource.
@@ -163,13 +179,23 @@ struct ResourceContents {
 llvm::json::Value toJSON(const ResourceContents &);
 bool fromJSON(const llvm::json::Value &, ResourceContents &, llvm::json::Path);
 
+/// Sent from the client to the server, to read a specific resource URI.
+struct ResourcesReadParams {
+  /// The URI of the resource to read. The URI can use any protocol; it is up 
to
+  /// the server how to interpret it.
+  std::string URI;
+};
+llvm::json::Value toJSON(const ResourcesReadParams &);
+bool fromJSON(const llvm::json::Value &, ResourcesReadParams &,
+              llvm::json::Path);
+
 /// The server's response to a resources/read request from the client.
-struct ResourceResult {
+struct ResourcesReadResult {
   std::vector<ResourceContents> contents;
 };
-
-llvm::json::Value toJSON(const ResourceResult &);
-bool fromJSON(const llvm::json::Value &, ResourceResult &, llvm::json::Path);
+llvm::json::Value toJSON(const ResourcesReadResult &);
+bool fromJSON(const llvm::json::Value &, ResourcesReadResult &,
+              llvm::json::Path);
 
 /// Text provided to or from an LLM.
 struct TextContent {
@@ -204,6 +230,145 @@ bool fromJSON(const llvm::json::Value &, ToolDefinition 
&, llvm::json::Path);
 
 using ToolArguments = std::variant<std::monostate, llvm::json::Value>;
 
+/// Describes the name and version of an MCP implementation, with an optional
+/// title for UI representation.
+///
+/// see
+/// 
https://modelcontextprotocol.io/specification/2025-06-18/schema#implementation
+struct Implementation {
+  /// Intended for programmatic or logical use, but used as a display name in
+  /// past specs or fallback (if title isn’t present).
+  std::string name;
+
+  /// Intended for UI and end-user contexts — optimized to be human-readable 
and
+  /// easily understood, even by those unfamiliar with domain-specific
+  /// terminology.
+  ///
+  /// If not provided, the name should be used for display (except for Tool,
+  /// where annotations.title should be given precedence over using name, if
+  /// present).
+  std::string title;
+
+  std::string version;
+};
+llvm::json::Value toJSON(const Implementation &);
+bool fromJSON(const llvm::json::Value &, Implementation &, llvm::json::Path);
+
+/// Capabilities a client may support. Known capabilities are defined here, in
+/// this schema, but this is not a closed set: any client can define its own,
+/// additional capabilities.
+struct ClientCapabilities {};
+llvm::json::Value toJSON(const ClientCapabilities &);
+bool fromJSON(const llvm::json::Value &, ClientCapabilities &,
+              llvm::json::Path);
+
+/// Capabilities that a server may support. Known capabilities are defined 
here,
+/// in this schema, but this is not a closed set: any server can define its 
own,
+/// additional capabilities.
+struct ServerCapabilities {
+  bool supportsToolsList = false;
+  bool supportsResourcesList = false;
+  bool supportsResourcesSubscribe = false;
+
+  /// Utilities.
+  bool supportsCompletions = false;
+  bool supportsLogging = false;
+};
+llvm::json::Value toJSON(const ServerCapabilities &);
+bool fromJSON(const llvm::json::Value &, ServerCapabilities &,
+              llvm::json::Path);
+
+/// Initialization
+
+/// This request is sent from the client to the server when it first connects,
+/// asking it to begin initialization.
+///
+/// @category initialize
+struct InitializeParams {
+  /// The latest version of the Model Context Protocol that the client 
supports.
+  /// The client MAY decide to support older versions as well.
+  std::string protocolVersion;
+
+  ClientCapabilities capabilities;
+
+  Implementation clientInfo;
+};
+llvm::json::Value toJSON(const InitializeParams &);
+bool fromJSON(const llvm::json::Value &, InitializeParams &, llvm::json::Path);
+
+/// After receiving an initialize request from the client, the server sends 
this
+/// response.
+///
+/// @category initialize
+struct InitializeResult {
+  /// The version of the Model Context Protocol that the server wants to use.
+  /// This may not match the version that the client requested. If the client
+  /// cannot support this version, it MUST disconnect.
+  std::string protocolVersion;
+
+  ServerCapabilities capabilities;
+  Implementation serverInfo;
+
+  /// Instructions describing how to use the server and its features.
+  ///
+  /// This can be used by cl...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/155315
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to