================
@@ -100,22 +175,301 @@ template <typename Req, typename Resp, typename Evt> 
class Transport {
   virtual llvm::Expected<MainLoop::ReadHandleUP>
   RegisterMessageHandler(MainLoop &loop, MessageHandler &handler) = 0;
 
-  // FIXME: Refactor mcp::Server to not directly access log on the transport.
-  // protected:
+protected:
   template <typename... Ts> inline auto Logv(const char *Fmt, Ts &&...Vals) {
     Log(llvm::formatv(Fmt, std::forward<Ts>(Vals)...).str());
   }
   virtual void Log(llvm::StringRef message) = 0;
+
+  /// Function object to reply to a call.
+  /// Each instance must be called exactly once, otherwise:
+  ///  - the bug is logged, and (in debug mode) an assert will fire
+  ///  - if there was no reply, an error reply is sent
+  ///  - if there were multiple replies, only the first is sent
+  class ReplyOnce {
+    std::atomic<bool> replied = {false};
+    const Req req;
+    JSONTransport *transport;               // Null when moved-from.
+    JSONTransport::MessageHandler *handler; // Null when moved-from.
+
+  public:
+    ReplyOnce(const Req req, JSONTransport *transport,
+              JSONTransport::MessageHandler *handler)
+        : req(req), transport(transport), handler(handler) {
+      assert(handler);
+    }
+    ReplyOnce(ReplyOnce &&other)
+        : replied(other.replied.load()), req(other.req),
+          transport(other.transport), handler(other.handler) {
+      other.transport = nullptr;
+      other.handler = nullptr;
+    }
+    ReplyOnce &operator=(ReplyOnce &&) = delete;
+    ReplyOnce(const ReplyOnce &) = delete;
+    ReplyOnce &operator=(const ReplyOnce &) = delete;
+
+    ~ReplyOnce() {
+      if (transport && handler && !replied) {
+        assert(false && "must reply to all calls!");
+        (*this)(MakeResponse<Req, Resp>(
+            req, llvm::createStringError("failed to reply")));
+      }
+    }
+
+    void operator()(const Resp &resp) {
+      assert(transport && handler && "moved-from!");
+      if (replied.exchange(true)) {
+        assert(false && "must reply to each call only once!");
+        return;
+      }
+
+      if (llvm::Error error = transport->Send(resp))
+        handler->OnError(std::move(error));
+    }
+  };
+
+public:
+  class Binder;
+  using BinderUP = std::unique_ptr<Binder>;
+
+  /// Binder collects a table of functions that handle calls.
----------------
ashgti wrote:

> By defining message types we can get that automatically out of the messages, 
> and then have it call the right callback?

Yea, for example, from the Server.cpp
```
MCPTransport::BinderUP Server::Bind(MCPTransport &transport) {
  MCPTransport::BinderUP binder =
      std::make_unique<MCPTransport::Binder>(transport);
  binder->bind<InitializeResult, InitializeParams>(
      "initialize", &Server::InitializeHandler, this);
  binder->bind<ListToolsResult, void>("tools/list", &Server::ToolsListHandler,
                                      this);
  binder->bind<CallToolResult, CallToolParams>("tools/call",
                                               &Server::ToolsCallHandler, this);
  binder->bind<ListResourcesResult, void>("resources/list",
                                          &Server::ResourcesListHandler, this);
  binder->bind<ReadResourceResult, ReadResourceParams>(
      "resources/read", &Server::ResourcesReadHandler, this);
  binder->bind<void>("notifications/initialized",
                     [this]() { Log("MCP initialization complete"); });
  return binder;
}
```

The `bind` method setups the serialization of the Params and Results and wraps 
them into the `Request` and `Response` types.

This means the server doesn't need to implement its own method dispatching 
mechanism, that can be handled by the `Binder` and Server can be simplified a 
bit. You can also see an example in JSONTransportTest.cpp that does this simple 
binding of a incoming call, outgoing call, incoming event and outgoing event.

> does this need to be tied to JSONTransport? Can this be built on top of an 
> arbitrary transport?

Within each `bind` call we do assume that `Params` and `Result` types are all 
JSON serializable.

That could be pulled apart a bit more though if we wanted to remove the JSON 
requirement. That could become a parameter to the template I suppose.

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

Reply via email to