This is a continuation of #152188, which started splitting up the MCP implementation into a generic implementation in Protocol/MCP that will be shared between LLDB and lldb-mcp. For now I kept all the networking code in the MCP server plugin. Once the changes to JSONTransport land, we might be able to move more of it into the Protocol library.
185 lines
5.6 KiB
C++
185 lines
5.6 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 size_t kChunkSize = 1024;
|
|
static constexpr llvm::StringLiteral kName = "lldb-mcp";
|
|
static constexpr llvm::StringLiteral kVersion = "0.1.0";
|
|
|
|
ProtocolServerMCP::ProtocolServerMCP()
|
|
: ProtocolServer(),
|
|
lldb_protocol::mcp::Server(std::string(kName), std::string(kVersion)) {
|
|
AddNotificationHandler("notifications/initialized",
|
|
[](const lldb_protocol::mcp::Notification &) {
|
|
LLDB_LOG(GetLog(LLDBLog::Host),
|
|
"MCP initialization complete");
|
|
});
|
|
|
|
AddTool(
|
|
std::make_unique<CommandTool>("lldb_command", "Run an lldb command."));
|
|
|
|
AddResourceProvider(std::make_unique<DebuggerResourceProvider>());
|
|
}
|
|
|
|
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::AcceptCallback(std::unique_ptr<Socket> socket) {
|
|
LLDB_LOG(GetLog(LLDBLog::Host), "New MCP client ({0}) connected",
|
|
m_clients.size() + 1);
|
|
|
|
lldb::IOObjectSP io_sp = std::move(socket);
|
|
auto client_up = std::make_unique<Client>();
|
|
client_up->io_sp = io_sp;
|
|
Client *client = client_up.get();
|
|
|
|
Status status;
|
|
auto read_handle_up = m_loop.RegisterReadObject(
|
|
io_sp,
|
|
[this, client](MainLoopBase &loop) {
|
|
if (llvm::Error error = ReadCallback(*client)) {
|
|
LLDB_LOG_ERROR(GetLog(LLDBLog::Host), std::move(error), "{0}");
|
|
client->read_handle_up.reset();
|
|
}
|
|
},
|
|
status);
|
|
if (status.Fail())
|
|
return;
|
|
|
|
client_up->read_handle_up = std::move(read_handle_up);
|
|
m_clients.emplace_back(std::move(client_up));
|
|
}
|
|
|
|
llvm::Error ProtocolServerMCP::ReadCallback(Client &client) {
|
|
char chunk[kChunkSize];
|
|
size_t bytes_read = sizeof(chunk);
|
|
if (Status status = client.io_sp->Read(chunk, bytes_read); status.Fail())
|
|
return status.takeError();
|
|
client.buffer.append(chunk, bytes_read);
|
|
|
|
for (std::string::size_type pos;
|
|
(pos = client.buffer.find('\n')) != std::string::npos;) {
|
|
llvm::Expected<std::optional<lldb_protocol::mcp::Message>> message =
|
|
HandleData(StringRef(client.buffer.data(), pos));
|
|
client.buffer = client.buffer.erase(0, pos + 1);
|
|
if (!message)
|
|
return message.takeError();
|
|
|
|
if (*message) {
|
|
std::string Output;
|
|
llvm::raw_string_ostream OS(Output);
|
|
OS << llvm::formatv("{0}", toJSON(**message)) << '\n';
|
|
size_t num_bytes = Output.size();
|
|
return client.io_sp->Write(Output.data(), num_bytes).takeError();
|
|
}
|
|
}
|
|
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
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(
|
|
[](MainLoopBase &loop) { loop.RequestTermination(); });
|
|
|
|
// Wait for the main loop to exit.
|
|
if (m_loop_thread.joinable())
|
|
m_loop_thread.join();
|
|
|
|
{
|
|
std::lock_guard<std::mutex> guard(m_mutex);
|
|
m_listener.reset();
|
|
m_listen_handlers.clear();
|
|
m_clients.clear();
|
|
}
|
|
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
lldb_protocol::mcp::Capabilities ProtocolServerMCP::GetCapabilities() {
|
|
lldb_protocol::mcp::Capabilities capabilities;
|
|
capabilities.tools.listChanged = true;
|
|
// FIXME: Support sending notifications when a debugger/target are
|
|
// added/removed.
|
|
capabilities.resources.listChanged = false;
|
|
return capabilities;
|
|
}
|