Path: blob/main/contrib/llvm-project/lldb/source/Plugins/Protocol/MCP/ProtocolServerMCP.cpp
213845 views
//===- ProtocolServerMCP.cpp ----------------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "ProtocolServerMCP.h"9#include "MCPError.h"10#include "lldb/Core/PluginManager.h"11#include "lldb/Utility/LLDBLog.h"12#include "lldb/Utility/Log.h"13#include "llvm/ADT/StringExtras.h"14#include "llvm/Support/Threading.h"15#include <thread>16#include <variant>1718using namespace lldb_private;19using namespace lldb_private::mcp;20using namespace llvm;2122LLDB_PLUGIN_DEFINE(ProtocolServerMCP)2324static constexpr size_t kChunkSize = 1024;2526ProtocolServerMCP::ProtocolServerMCP() : ProtocolServer() {27AddRequestHandler("initialize",28std::bind(&ProtocolServerMCP::InitializeHandler, this,29std::placeholders::_1));3031AddRequestHandler("tools/list",32std::bind(&ProtocolServerMCP::ToolsListHandler, this,33std::placeholders::_1));34AddRequestHandler("tools/call",35std::bind(&ProtocolServerMCP::ToolsCallHandler, this,36std::placeholders::_1));3738AddRequestHandler("resources/list",39std::bind(&ProtocolServerMCP::ResourcesListHandler, this,40std::placeholders::_1));41AddRequestHandler("resources/read",42std::bind(&ProtocolServerMCP::ResourcesReadHandler, this,43std::placeholders::_1));44AddNotificationHandler(45"notifications/initialized", [](const protocol::Notification &) {46LLDB_LOG(GetLog(LLDBLog::Host), "MCP initialization complete");47});4849AddTool(50std::make_unique<CommandTool>("lldb_command", "Run an lldb command."));5152AddResourceProvider(std::make_unique<DebuggerResourceProvider>());53}5455ProtocolServerMCP::~ProtocolServerMCP() { llvm::consumeError(Stop()); }5657void ProtocolServerMCP::Initialize() {58PluginManager::RegisterPlugin(GetPluginNameStatic(),59GetPluginDescriptionStatic(), CreateInstance);60}6162void ProtocolServerMCP::Terminate() {63PluginManager::UnregisterPlugin(CreateInstance);64}6566lldb::ProtocolServerUP ProtocolServerMCP::CreateInstance() {67return std::make_unique<ProtocolServerMCP>();68}6970llvm::StringRef ProtocolServerMCP::GetPluginDescriptionStatic() {71return "MCP Server.";72}7374llvm::Expected<protocol::Response>75ProtocolServerMCP::Handle(protocol::Request request) {76auto it = m_request_handlers.find(request.method);77if (it != m_request_handlers.end()) {78llvm::Expected<protocol::Response> response = it->second(request);79if (!response)80return response;81response->id = request.id;82return *response;83}8485return make_error<MCPError>(86llvm::formatv("no handler for request: {0}", request.method).str());87}8889void ProtocolServerMCP::Handle(protocol::Notification notification) {90auto it = m_notification_handlers.find(notification.method);91if (it != m_notification_handlers.end()) {92it->second(notification);93return;94}9596LLDB_LOG(GetLog(LLDBLog::Host), "MPC notification: {0} ({1})",97notification.method, notification.params);98}99100void ProtocolServerMCP::AcceptCallback(std::unique_ptr<Socket> socket) {101LLDB_LOG(GetLog(LLDBLog::Host), "New MCP client ({0}) connected",102m_clients.size() + 1);103104lldb::IOObjectSP io_sp = std::move(socket);105auto client_up = std::make_unique<Client>();106client_up->io_sp = io_sp;107Client *client = client_up.get();108109Status status;110auto read_handle_up = m_loop.RegisterReadObject(111io_sp,112[this, client](MainLoopBase &loop) {113if (Error error = ReadCallback(*client)) {114LLDB_LOG_ERROR(GetLog(LLDBLog::Host), std::move(error), "{0}");115client->read_handle_up.reset();116}117},118status);119if (status.Fail())120return;121122client_up->read_handle_up = std::move(read_handle_up);123m_clients.emplace_back(std::move(client_up));124}125126llvm::Error ProtocolServerMCP::ReadCallback(Client &client) {127char chunk[kChunkSize];128size_t bytes_read = sizeof(chunk);129if (Status status = client.io_sp->Read(chunk, bytes_read); status.Fail())130return status.takeError();131client.buffer.append(chunk, bytes_read);132133for (std::string::size_type pos;134(pos = client.buffer.find('\n')) != std::string::npos;) {135llvm::Expected<std::optional<protocol::Message>> message =136HandleData(StringRef(client.buffer.data(), pos));137client.buffer = client.buffer.erase(0, pos + 1);138if (!message)139return message.takeError();140141if (*message) {142std::string Output;143llvm::raw_string_ostream OS(Output);144OS << llvm::formatv("{0}", toJSON(**message)) << '\n';145size_t num_bytes = Output.size();146return client.io_sp->Write(Output.data(), num_bytes).takeError();147}148}149150return llvm::Error::success();151}152153llvm::Error ProtocolServerMCP::Start(ProtocolServer::Connection connection) {154std::lock_guard<std::mutex> guard(m_server_mutex);155156if (m_running)157return llvm::createStringError("the MCP server is already running");158159Status status;160m_listener = Socket::Create(connection.protocol, status);161if (status.Fail())162return status.takeError();163164status = m_listener->Listen(connection.name, /*backlog=*/5);165if (status.Fail())166return status.takeError();167168auto handles =169m_listener->Accept(m_loop, std::bind(&ProtocolServerMCP::AcceptCallback,170this, std::placeholders::_1));171if (llvm::Error error = handles.takeError())172return error;173174m_running = true;175m_listen_handlers = std::move(*handles);176m_loop_thread = std::thread([=] {177llvm::set_thread_name("protocol-server.mcp");178m_loop.Run();179});180181return llvm::Error::success();182}183184llvm::Error ProtocolServerMCP::Stop() {185{186std::lock_guard<std::mutex> guard(m_server_mutex);187if (!m_running)188return createStringError("the MCP sever is not running");189m_running = false;190}191192// Stop the main loop.193m_loop.AddPendingCallback(194[](MainLoopBase &loop) { loop.RequestTermination(); });195196// Wait for the main loop to exit.197if (m_loop_thread.joinable())198m_loop_thread.join();199200{201std::lock_guard<std::mutex> guard(m_server_mutex);202m_listener.reset();203m_listen_handlers.clear();204m_clients.clear();205}206207return llvm::Error::success();208}209210llvm::Expected<std::optional<protocol::Message>>211ProtocolServerMCP::HandleData(llvm::StringRef data) {212auto message = llvm::json::parse<protocol::Message>(/*JSON=*/data);213if (!message)214return message.takeError();215216if (const protocol::Request *request =217std::get_if<protocol::Request>(&(*message))) {218llvm::Expected<protocol::Response> response = Handle(*request);219220// Handle failures by converting them into an Error message.221if (!response) {222protocol::Error protocol_error;223llvm::handleAllErrors(224response.takeError(),225[&](const MCPError &err) { protocol_error = err.toProtcolError(); },226[&](const llvm::ErrorInfoBase &err) {227protocol_error.error.code = MCPError::kInternalError;228protocol_error.error.message = err.message();229});230protocol_error.id = request->id;231return protocol_error;232}233234return *response;235}236237if (const protocol::Notification *notification =238std::get_if<protocol::Notification>(&(*message))) {239Handle(*notification);240return std::nullopt;241}242243if (std::get_if<protocol::Error>(&(*message)))244return llvm::createStringError("unexpected MCP message: error");245246if (std::get_if<protocol::Response>(&(*message)))247return llvm::createStringError("unexpected MCP message: response");248249llvm_unreachable("all message types handled");250}251252protocol::Capabilities ProtocolServerMCP::GetCapabilities() {253protocol::Capabilities capabilities;254capabilities.tools.listChanged = true;255// FIXME: Support sending notifications when a debugger/target are256// added/removed.257capabilities.resources.listChanged = false;258return capabilities;259}260261void ProtocolServerMCP::AddTool(std::unique_ptr<Tool> tool) {262std::lock_guard<std::mutex> guard(m_server_mutex);263264if (!tool)265return;266m_tools[tool->GetName()] = std::move(tool);267}268269void ProtocolServerMCP::AddResourceProvider(270std::unique_ptr<ResourceProvider> resource_provider) {271std::lock_guard<std::mutex> guard(m_server_mutex);272273if (!resource_provider)274return;275m_resource_providers.push_back(std::move(resource_provider));276}277278void ProtocolServerMCP::AddRequestHandler(llvm::StringRef method,279RequestHandler handler) {280std::lock_guard<std::mutex> guard(m_server_mutex);281m_request_handlers[method] = std::move(handler);282}283284void ProtocolServerMCP::AddNotificationHandler(llvm::StringRef method,285NotificationHandler handler) {286std::lock_guard<std::mutex> guard(m_server_mutex);287m_notification_handlers[method] = std::move(handler);288}289290llvm::Expected<protocol::Response>291ProtocolServerMCP::InitializeHandler(const protocol::Request &request) {292protocol::Response response;293response.result.emplace(llvm::json::Object{294{"protocolVersion", protocol::kVersion},295{"capabilities", GetCapabilities()},296{"serverInfo",297llvm::json::Object{{"name", kName}, {"version", kVersion}}}});298return response;299}300301llvm::Expected<protocol::Response>302ProtocolServerMCP::ToolsListHandler(const protocol::Request &request) {303protocol::Response response;304305llvm::json::Array tools;306for (const auto &tool : m_tools)307tools.emplace_back(toJSON(tool.second->GetDefinition()));308309response.result.emplace(llvm::json::Object{{"tools", std::move(tools)}});310311return response;312}313314llvm::Expected<protocol::Response>315ProtocolServerMCP::ToolsCallHandler(const protocol::Request &request) {316protocol::Response response;317318if (!request.params)319return llvm::createStringError("no tool parameters");320321const json::Object *param_obj = request.params->getAsObject();322if (!param_obj)323return llvm::createStringError("no tool parameters");324325const json::Value *name = param_obj->get("name");326if (!name)327return llvm::createStringError("no tool name");328329llvm::StringRef tool_name = name->getAsString().value_or("");330if (tool_name.empty())331return llvm::createStringError("no tool name");332333auto it = m_tools.find(tool_name);334if (it == m_tools.end())335return llvm::createStringError(llvm::formatv("no tool \"{0}\"", tool_name));336337protocol::ToolArguments tool_args;338if (const json::Value *args = param_obj->get("arguments"))339tool_args = *args;340341llvm::Expected<protocol::TextResult> text_result =342it->second->Call(tool_args);343if (!text_result)344return text_result.takeError();345346response.result.emplace(toJSON(*text_result));347348return response;349}350351llvm::Expected<protocol::Response>352ProtocolServerMCP::ResourcesListHandler(const protocol::Request &request) {353protocol::Response response;354355llvm::json::Array resources;356357std::lock_guard<std::mutex> guard(m_server_mutex);358for (std::unique_ptr<ResourceProvider> &resource_provider_up :359m_resource_providers) {360for (const protocol::Resource &resource :361resource_provider_up->GetResources())362resources.push_back(resource);363}364response.result.emplace(365llvm::json::Object{{"resources", std::move(resources)}});366367return response;368}369370llvm::Expected<protocol::Response>371ProtocolServerMCP::ResourcesReadHandler(const protocol::Request &request) {372protocol::Response response;373374if (!request.params)375return llvm::createStringError("no resource parameters");376377const json::Object *param_obj = request.params->getAsObject();378if (!param_obj)379return llvm::createStringError("no resource parameters");380381const json::Value *uri = param_obj->get("uri");382if (!uri)383return llvm::createStringError("no resource uri");384385llvm::StringRef uri_str = uri->getAsString().value_or("");386if (uri_str.empty())387return llvm::createStringError("no resource uri");388389std::lock_guard<std::mutex> guard(m_server_mutex);390for (std::unique_ptr<ResourceProvider> &resource_provider_up :391m_resource_providers) {392llvm::Expected<protocol::ResourceResult> result =393resource_provider_up->ReadResource(uri_str);394if (result.errorIsA<UnsupportedURI>()) {395llvm::consumeError(result.takeError());396continue;397}398if (!result)399return result.takeError();400401protocol::Response response;402response.result.emplace(std::move(*result));403return response;404}405406return make_error<MCPError>(407llvm::formatv("no resource handler for uri: {0}", uri_str).str(),408MCPError::kResourceNotFound);409}410411412