Jonas Devlieghere dbaa82b384
[lldb] Move the generic MCP code into Protocol/MCP (NFC) (#152188)
This moves all the generic MCP code into the new ProtocolMCP library so
it can be shared between the MCP implementation in LLDB (built on the
private API) and lldb-mcp (built on the public API).

This PR doesn't include the actual MCP server. The reason is that the
transport mechanism will be different between the LLDB implementation
(using sockets) and the lldb-mcp implementation (using stdio). The goal
s to abstract away that difference by making the server use
JSONTransport (again) but I'm saving that for a separate PR.
2025-08-05 14:10:31 -07:00

221 lines
7.4 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 "Resource.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/Module.h"
#include "lldb/Protocol/MCP/MCPError.h"
#include "lldb/Target/Platform.h"
using namespace lldb_private;
using namespace lldb_private::mcp;
using namespace lldb_protocol::mcp;
namespace {
struct DebuggerResource {
uint64_t debugger_id = 0;
std::string name;
uint64_t num_targets = 0;
};
llvm::json::Value toJSON(const DebuggerResource &DR) {
llvm::json::Object Result{{"debugger_id", DR.debugger_id},
{"num_targets", DR.num_targets}};
if (!DR.name.empty())
Result.insert({"name", DR.name});
return Result;
}
struct TargetResource {
size_t debugger_id = 0;
size_t target_idx = 0;
bool selected = false;
bool dummy = false;
std::string arch;
std::string path;
std::string platform;
};
llvm::json::Value toJSON(const TargetResource &TR) {
llvm::json::Object Result{{"debugger_id", TR.debugger_id},
{"target_idx", TR.target_idx},
{"selected", TR.selected},
{"dummy", TR.dummy}};
if (!TR.arch.empty())
Result.insert({"arch", TR.arch});
if (!TR.path.empty())
Result.insert({"path", TR.path});
if (!TR.platform.empty())
Result.insert({"platform", TR.platform});
return Result;
}
} // namespace
static constexpr llvm::StringLiteral kMimeTypeJSON = "application/json";
template <typename... Args>
static llvm::Error createStringError(const char *format, Args &&...args) {
return llvm::createStringError(
llvm::formatv(format, std::forward<Args>(args)...).str());
}
static llvm::Error createUnsupportedURIError(llvm::StringRef uri) {
return llvm::make_error<UnsupportedURI>(uri.str());
}
lldb_protocol::mcp::Resource
DebuggerResourceProvider::GetDebuggerResource(Debugger &debugger) {
const lldb::user_id_t debugger_id = debugger.GetID();
lldb_protocol::mcp::Resource resource;
resource.uri = llvm::formatv("lldb://debugger/{0}", debugger_id);
resource.name = debugger.GetInstanceName();
resource.description =
llvm::formatv("Information about debugger instance {0}: {1}", debugger_id,
debugger.GetInstanceName());
resource.mimeType = kMimeTypeJSON;
return resource;
}
lldb_protocol::mcp::Resource
DebuggerResourceProvider::GetTargetResource(size_t target_idx, Target &target) {
const size_t debugger_id = target.GetDebugger().GetID();
std::string target_name = llvm::formatv("target {0}", target_idx);
if (Module *exe_module = target.GetExecutableModulePointer())
target_name = exe_module->GetFileSpec().GetFilename().GetString();
lldb_protocol::mcp::Resource resource;
resource.uri =
llvm::formatv("lldb://debugger/{0}/target/{1}", debugger_id, target_idx);
resource.name = target_name;
resource.description =
llvm::formatv("Information about target {0} in debugger instance {1}",
target_idx, debugger_id);
resource.mimeType = kMimeTypeJSON;
return resource;
}
std::vector<lldb_protocol::mcp::Resource>
DebuggerResourceProvider::GetResources() const {
std::vector<lldb_protocol::mcp::Resource> resources;
const size_t num_debuggers = Debugger::GetNumDebuggers();
for (size_t i = 0; i < num_debuggers; ++i) {
lldb::DebuggerSP debugger_sp = Debugger::GetDebuggerAtIndex(i);
if (!debugger_sp)
continue;
resources.emplace_back(GetDebuggerResource(*debugger_sp));
TargetList &target_list = debugger_sp->GetTargetList();
const size_t num_targets = target_list.GetNumTargets();
for (size_t j = 0; j < num_targets; ++j) {
lldb::TargetSP target_sp = target_list.GetTargetAtIndex(j);
if (!target_sp)
continue;
resources.emplace_back(GetTargetResource(j, *target_sp));
}
}
return resources;
}
llvm::Expected<lldb_protocol::mcp::ResourceResult>
DebuggerResourceProvider::ReadResource(llvm::StringRef uri) const {
auto [protocol, path] = uri.split("://");
if (protocol != "lldb")
return createUnsupportedURIError(uri);
llvm::SmallVector<llvm::StringRef, 4> components;
path.split(components, '/');
if (components.size() < 2)
return createUnsupportedURIError(uri);
if (components[0] != "debugger")
return createUnsupportedURIError(uri);
size_t debugger_idx;
if (components[1].getAsInteger(0, debugger_idx))
return createStringError("invalid debugger id '{0}': {1}", components[1],
path);
if (components.size() > 3) {
if (components[2] != "target")
return createUnsupportedURIError(uri);
size_t target_idx;
if (components[3].getAsInteger(0, target_idx))
return createStringError("invalid target id '{0}': {1}", components[3],
path);
return ReadTargetResource(uri, debugger_idx, target_idx);
}
return ReadDebuggerResource(uri, debugger_idx);
}
llvm::Expected<lldb_protocol::mcp::ResourceResult>
DebuggerResourceProvider::ReadDebuggerResource(llvm::StringRef uri,
lldb::user_id_t debugger_id) {
lldb::DebuggerSP debugger_sp = Debugger::FindDebuggerWithID(debugger_id);
if (!debugger_sp)
return createStringError("invalid debugger id: {0}", debugger_id);
DebuggerResource debugger_resource;
debugger_resource.debugger_id = debugger_id;
debugger_resource.name = debugger_sp->GetInstanceName();
debugger_resource.num_targets = debugger_sp->GetTargetList().GetNumTargets();
lldb_protocol::mcp::ResourceContents contents;
contents.uri = uri;
contents.mimeType = kMimeTypeJSON;
contents.text = llvm::formatv("{0}", toJSON(debugger_resource));
lldb_protocol::mcp::ResourceResult result;
result.contents.push_back(contents);
return result;
}
llvm::Expected<lldb_protocol::mcp::ResourceResult>
DebuggerResourceProvider::ReadTargetResource(llvm::StringRef uri,
lldb::user_id_t debugger_id,
size_t target_idx) {
lldb::DebuggerSP debugger_sp = Debugger::FindDebuggerWithID(debugger_id);
if (!debugger_sp)
return createStringError("invalid debugger id: {0}", debugger_id);
TargetList &target_list = debugger_sp->GetTargetList();
lldb::TargetSP target_sp = target_list.GetTargetAtIndex(target_idx);
if (!target_sp)
return createStringError("invalid target idx: {0}", target_idx);
TargetResource target_resource;
target_resource.debugger_id = debugger_id;
target_resource.target_idx = target_idx;
target_resource.arch = target_sp->GetArchitecture().GetTriple().str();
target_resource.dummy = target_sp->IsDummyTarget();
target_resource.selected = target_sp == debugger_sp->GetSelectedTarget();
if (Module *exe_module = target_sp->GetExecutableModulePointer())
target_resource.path = exe_module->GetFileSpec().GetPath();
if (lldb::PlatformSP platform_sp = target_sp->GetPlatform())
target_resource.platform = platform_sp->GetName();
lldb_protocol::mcp::ResourceContents contents;
contents.uri = uri;
contents.mimeType = kMimeTypeJSON;
contents.text = llvm::formatv("{0}", toJSON(target_resource));
lldb_protocol::mcp::ResourceResult result;
result.contents.push_back(contents);
return result;
}