This PR adopts JSONTransport in the MCP server implementation. It required a slight change in design in the relationship between the two server classes. Previously, these two had an "is-a" connection, while now they have a "has-a" connection. The "generic" protocol server in Protocol/MCP now operates using a single connection (Transport). This matches the design in DAP where each DAP instance has its own connection. The protocol server in Plugins still supports multiple clients and creates a new server instance for each connection. I believe the new design makes sense in the long term (as proved by DAP) and allows us to make the server stateful if we choose to do so. There's no reason that multiple client support can't live in the generic protocol library, but for now I kept it in ProtocolServerMCP to avoid creating unnecessary abstractions.
135 lines
4.3 KiB
C++
135 lines
4.3 KiB
C++
//===- ProtocolServerMCP.cpp ----------------------------------------------===//
|
|
//
|
|
// 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 "ProtocolServerMCP.h"
|
|
#include "Resource.h"
|
|
#include "Tool.h"
|
|
#include "lldb/Core/PluginManager.h"
|
|
#include "lldb/Protocol/MCP/MCPError.h"
|
|
#include "lldb/Protocol/MCP/Tool.h"
|
|
#include "lldb/Utility/LLDBLog.h"
|
|
#include "lldb/Utility/Log.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/Support/Threading.h"
|
|
#include <thread>
|
|
#include <variant>
|
|
|
|
using namespace lldb_private;
|
|
using namespace lldb_private::mcp;
|
|
using namespace lldb_protocol::mcp;
|
|
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() {}
|
|
|
|
ProtocolServerMCP::~ProtocolServerMCP() { llvm::consumeError(Stop()); }
|
|
|
|
void ProtocolServerMCP::Initialize() {
|
|
PluginManager::RegisterPlugin(GetPluginNameStatic(),
|
|
GetPluginDescriptionStatic(), CreateInstance);
|
|
}
|
|
|
|
void ProtocolServerMCP::Terminate() {
|
|
PluginManager::UnregisterPlugin(CreateInstance);
|
|
}
|
|
|
|
lldb::ProtocolServerUP ProtocolServerMCP::CreateInstance() {
|
|
return std::make_unique<ProtocolServerMCP>();
|
|
}
|
|
|
|
llvm::StringRef ProtocolServerMCP::GetPluginDescriptionStatic() {
|
|
return "MCP Server.";
|
|
}
|
|
|
|
void ProtocolServerMCP::Extend(lldb_protocol::mcp::Server &server) const {
|
|
server.AddNotificationHandler("notifications/initialized",
|
|
[](const lldb_protocol::mcp::Notification &) {
|
|
LLDB_LOG(GetLog(LLDBLog::Host),
|
|
"MCP initialization complete");
|
|
});
|
|
server.AddTool(
|
|
std::make_unique<CommandTool>("lldb_command", "Run an lldb command."));
|
|
server.AddResourceProvider(std::make_unique<DebuggerResourceProvider>());
|
|
}
|
|
|
|
void ProtocolServerMCP::AcceptCallback(std::unique_ptr<Socket> socket) {
|
|
Log *log = GetLog(LLDBLog::Host);
|
|
std::string client_name = llvm::formatv("client_{0}", m_instances.size() + 1);
|
|
LLDB_LOG(log, "New MCP client connected: {0}", client_name);
|
|
|
|
lldb::IOObjectSP io_sp = std::move(socket);
|
|
auto transport_up = std::make_unique<lldb_protocol::mcp::MCPTransport>(
|
|
io_sp, io_sp, std::move(client_name), [&](llvm::StringRef message) {
|
|
LLDB_LOG(GetLog(LLDBLog::Host), "{0}", message);
|
|
});
|
|
auto instance_up = std::make_unique<lldb_protocol::mcp::Server>(
|
|
std::string(kName), std::string(kVersion), std::move(transport_up),
|
|
m_loop);
|
|
Extend(*instance_up);
|
|
llvm::Error error = instance_up->Run();
|
|
if (error) {
|
|
LLDB_LOG_ERROR(log, std::move(error), "Failed to run MCP server: {0}");
|
|
return;
|
|
}
|
|
m_instances.push_back(std::move(instance_up));
|
|
}
|
|
|
|
llvm::Error ProtocolServerMCP::Start(ProtocolServer::Connection connection) {
|
|
std::lock_guard<std::mutex> guard(m_mutex);
|
|
|
|
if (m_running)
|
|
return llvm::createStringError("the MCP server is already running");
|
|
|
|
Status status;
|
|
m_listener = Socket::Create(connection.protocol, status);
|
|
if (status.Fail())
|
|
return status.takeError();
|
|
|
|
status = m_listener->Listen(connection.name, /*backlog=*/5);
|
|
if (status.Fail())
|
|
return status.takeError();
|
|
|
|
auto handles =
|
|
m_listener->Accept(m_loop, std::bind(&ProtocolServerMCP::AcceptCallback,
|
|
this, std::placeholders::_1));
|
|
if (llvm::Error error = handles.takeError())
|
|
return error;
|
|
|
|
m_running = true;
|
|
m_listen_handlers = std::move(*handles);
|
|
m_loop_thread = std::thread([=] {
|
|
llvm::set_thread_name("protocol-server.mcp");
|
|
m_loop.Run();
|
|
});
|
|
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
llvm::Error ProtocolServerMCP::Stop() {
|
|
{
|
|
std::lock_guard<std::mutex> guard(m_mutex);
|
|
if (!m_running)
|
|
return createStringError("the MCP sever is not running");
|
|
m_running = false;
|
|
}
|
|
|
|
// Stop the main loop.
|
|
m_loop.AddPendingCallback(
|
|
[](lldb_private::MainLoopBase &loop) { loop.RequestTermination(); });
|
|
|
|
// Wait for the main loop to exit.
|
|
if (m_loop_thread.joinable())
|
|
m_loop_thread.join();
|
|
|
|
return llvm::Error::success();
|
|
}
|