//===- 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 #include 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("lldb_command", "Run an lldb command.")); AddResourceProvider(std::make_unique()); } 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(); } llvm::StringRef ProtocolServerMCP::GetPluginDescriptionStatic() { return "MCP Server."; } void ProtocolServerMCP::AcceptCallback(std::unique_ptr 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_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> 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 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 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 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; }