mirror of
https://github.com/intel/llvm.git
synced 2026-01-13 19:08:21 +08:00
This adds a new Binding helper class to allow mapping of incoming and outgoing requests / events to specific handlers. This should make it easier to create new protocol implementations and allow us to create a relay in the lldb-mcp binary.
250 lines
8.0 KiB
C++
250 lines
8.0 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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 "lldb/Protocol/MCP/Server.h"
|
|
#include "lldb/Host/File.h"
|
|
#include "lldb/Host/FileSystem.h"
|
|
#include "lldb/Host/HostInfo.h"
|
|
#include "lldb/Protocol/MCP/MCPError.h"
|
|
#include "lldb/Protocol/MCP/Protocol.h"
|
|
#include "lldb/Protocol/MCP/Transport.h"
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
#include "llvm/Support/JSON.h"
|
|
#include "llvm/Support/Signals.h"
|
|
|
|
using namespace llvm;
|
|
using namespace lldb_private;
|
|
using namespace lldb_protocol::mcp;
|
|
|
|
ServerInfoHandle::ServerInfoHandle(StringRef filename) : m_filename(filename) {
|
|
if (!m_filename.empty())
|
|
sys::RemoveFileOnSignal(m_filename);
|
|
}
|
|
|
|
ServerInfoHandle::~ServerInfoHandle() { Remove(); }
|
|
|
|
ServerInfoHandle::ServerInfoHandle(ServerInfoHandle &&other) {
|
|
*this = std::move(other);
|
|
}
|
|
|
|
ServerInfoHandle &
|
|
ServerInfoHandle::operator=(ServerInfoHandle &&other) noexcept {
|
|
m_filename = std::move(other.m_filename);
|
|
return *this;
|
|
}
|
|
|
|
void ServerInfoHandle::Remove() {
|
|
if (m_filename.empty())
|
|
return;
|
|
|
|
sys::fs::remove(m_filename);
|
|
sys::DontRemoveFileOnSignal(m_filename);
|
|
m_filename.clear();
|
|
}
|
|
|
|
json::Value lldb_protocol::mcp::toJSON(const ServerInfo &SM) {
|
|
return json::Object{{"connection_uri", SM.connection_uri}};
|
|
}
|
|
|
|
bool lldb_protocol::mcp::fromJSON(const json::Value &V, ServerInfo &SM,
|
|
json::Path P) {
|
|
json::ObjectMapper O(V, P);
|
|
return O && O.map("connection_uri", SM.connection_uri);
|
|
}
|
|
|
|
Expected<ServerInfoHandle> ServerInfo::Write(const ServerInfo &info) {
|
|
std::string buf = formatv("{0}", toJSON(info)).str();
|
|
size_t num_bytes = buf.size();
|
|
|
|
FileSpec user_lldb_dir = HostInfo::GetUserLLDBDir();
|
|
|
|
Status error(sys::fs::create_directory(user_lldb_dir.GetPath()));
|
|
if (error.Fail())
|
|
return error.takeError();
|
|
|
|
FileSpec mcp_registry_entry_path = user_lldb_dir.CopyByAppendingPathComponent(
|
|
formatv("lldb-mcp-{0}.json", getpid()).str());
|
|
|
|
const File::OpenOptions flags = File::eOpenOptionWriteOnly |
|
|
File::eOpenOptionCanCreate |
|
|
File::eOpenOptionTruncate;
|
|
Expected<lldb::FileUP> file =
|
|
FileSystem::Instance().Open(mcp_registry_entry_path, flags);
|
|
if (!file)
|
|
return file.takeError();
|
|
if (llvm::Error error = (*file)->Write(buf.data(), num_bytes).takeError())
|
|
return error;
|
|
return ServerInfoHandle{mcp_registry_entry_path.GetPath()};
|
|
}
|
|
|
|
Expected<std::vector<ServerInfo>> ServerInfo::Load() {
|
|
namespace path = llvm::sys::path;
|
|
FileSpec user_lldb_dir = HostInfo::GetUserLLDBDir();
|
|
FileSystem &fs = FileSystem::Instance();
|
|
std::error_code EC;
|
|
vfs::directory_iterator it = fs.DirBegin(user_lldb_dir, EC);
|
|
vfs::directory_iterator end;
|
|
std::vector<ServerInfo> infos;
|
|
for (; it != end && !EC; it.increment(EC)) {
|
|
auto &entry = *it;
|
|
auto path = entry.path();
|
|
auto name = path::filename(path);
|
|
if (!name.starts_with("lldb-mcp-") || !name.ends_with(".json"))
|
|
continue;
|
|
|
|
auto buffer = fs.CreateDataBuffer(path);
|
|
auto info = json::parse<ServerInfo>(toStringRef(buffer->GetData()));
|
|
if (!info)
|
|
return info.takeError();
|
|
|
|
infos.emplace_back(std::move(*info));
|
|
}
|
|
|
|
return infos;
|
|
}
|
|
|
|
Server::Server(std::string name, std::string version, LogCallback log_callback)
|
|
: m_name(std::move(name)), m_version(std::move(version)),
|
|
m_log_callback(std::move(log_callback)) {}
|
|
|
|
void Server::AddTool(std::unique_ptr<Tool> tool) {
|
|
if (!tool)
|
|
return;
|
|
m_tools[tool->GetName()] = std::move(tool);
|
|
}
|
|
|
|
void Server::AddResourceProvider(
|
|
std::unique_ptr<ResourceProvider> resource_provider) {
|
|
if (!resource_provider)
|
|
return;
|
|
m_resource_providers.push_back(std::move(resource_provider));
|
|
}
|
|
|
|
MCPBinderUP Server::Bind(MCPTransport &transport) {
|
|
MCPBinderUP binder_up = std::make_unique<MCPBinder>(transport);
|
|
binder_up->Bind<InitializeResult, InitializeParams>(
|
|
"initialize", &Server::InitializeHandler, this);
|
|
binder_up->Bind<ListToolsResult, void>("tools/list",
|
|
&Server::ToolsListHandler, this);
|
|
binder_up->Bind<CallToolResult, CallToolParams>(
|
|
"tools/call", &Server::ToolsCallHandler, this);
|
|
binder_up->Bind<ListResourcesResult, void>(
|
|
"resources/list", &Server::ResourcesListHandler, this);
|
|
binder_up->Bind<ReadResourceResult, ReadResourceParams>(
|
|
"resources/read", &Server::ResourcesReadHandler, this);
|
|
binder_up->Bind<void>("notifications/initialized",
|
|
[this]() { Log("MCP initialization complete"); });
|
|
return binder_up;
|
|
}
|
|
|
|
llvm::Error Server::Accept(MainLoop &loop, MCPTransportUP transport) {
|
|
MCPBinderUP binder = Bind(*transport);
|
|
MCPTransport *transport_ptr = transport.get();
|
|
binder->OnDisconnect([this, transport_ptr]() {
|
|
assert(m_instances.find(transport_ptr) != m_instances.end() &&
|
|
"Client not found in m_instances");
|
|
m_instances.erase(transport_ptr);
|
|
});
|
|
binder->OnError([this](llvm::Error err) {
|
|
Logv("Transport error: {0}", llvm::toString(std::move(err)));
|
|
});
|
|
|
|
auto handle = transport->RegisterMessageHandler(loop, *binder);
|
|
if (!handle)
|
|
return handle.takeError();
|
|
|
|
m_instances[transport_ptr] =
|
|
Client{std::move(*handle), std::move(transport), std::move(binder)};
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
Expected<InitializeResult>
|
|
Server::InitializeHandler(const InitializeParams &request) {
|
|
InitializeResult result;
|
|
result.protocolVersion = mcp::kProtocolVersion;
|
|
result.capabilities = GetCapabilities();
|
|
result.serverInfo.name = m_name;
|
|
result.serverInfo.version = m_version;
|
|
return result;
|
|
}
|
|
|
|
llvm::Expected<ListToolsResult> Server::ToolsListHandler() {
|
|
ListToolsResult result;
|
|
for (const auto &tool : m_tools)
|
|
result.tools.emplace_back(tool.second->GetDefinition());
|
|
|
|
return result;
|
|
}
|
|
|
|
llvm::Expected<CallToolResult>
|
|
Server::ToolsCallHandler(const CallToolParams ¶ms) {
|
|
llvm::StringRef tool_name = params.name;
|
|
if (tool_name.empty())
|
|
return llvm::createStringError("no tool name");
|
|
|
|
auto it = m_tools.find(tool_name);
|
|
if (it == m_tools.end())
|
|
return llvm::createStringError(llvm::formatv("no tool \"{0}\"", tool_name));
|
|
|
|
ToolArguments tool_args;
|
|
if (params.arguments)
|
|
tool_args = *params.arguments;
|
|
|
|
llvm::Expected<CallToolResult> text_result = it->second->Call(tool_args);
|
|
if (!text_result)
|
|
return text_result.takeError();
|
|
|
|
return text_result;
|
|
}
|
|
|
|
llvm::Expected<ListResourcesResult> Server::ResourcesListHandler() {
|
|
ListResourcesResult result;
|
|
for (std::unique_ptr<ResourceProvider> &resource_provider_up :
|
|
m_resource_providers)
|
|
for (const Resource &resource : resource_provider_up->GetResources())
|
|
result.resources.push_back(resource);
|
|
|
|
return result;
|
|
}
|
|
|
|
Expected<ReadResourceResult>
|
|
Server::ResourcesReadHandler(const ReadResourceParams ¶ms) {
|
|
StringRef uri_str = params.uri;
|
|
if (uri_str.empty())
|
|
return createStringError("no resource uri");
|
|
|
|
for (std::unique_ptr<ResourceProvider> &resource_provider_up :
|
|
m_resource_providers) {
|
|
Expected<ReadResourceResult> result =
|
|
resource_provider_up->ReadResource(uri_str);
|
|
if (result.errorIsA<UnsupportedURI>()) {
|
|
consumeError(result.takeError());
|
|
continue;
|
|
}
|
|
if (!result)
|
|
return result.takeError();
|
|
|
|
return *result;
|
|
}
|
|
|
|
return make_error<MCPError>(
|
|
formatv("no resource handler for uri: {0}", uri_str).str(),
|
|
MCPError::kResourceNotFound);
|
|
}
|
|
|
|
ServerCapabilities Server::GetCapabilities() {
|
|
lldb_protocol::mcp::ServerCapabilities capabilities;
|
|
capabilities.supportsToolsList = true;
|
|
capabilities.supportsResourcesList = true;
|
|
// FIXME: Support sending notifications when a debugger/target are
|
|
// added/removed.
|
|
capabilities.supportsResourcesSubscribe = false;
|
|
return capabilities;
|
|
}
|