150 lines · cpp
1//===- Tool.cpp -----------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "Tool.h"10#include "lldb/Core/Debugger.h"11#include "lldb/Interpreter/CommandInterpreter.h"12#include "lldb/Interpreter/CommandReturnObject.h"13#include "lldb/Protocol/MCP/Protocol.h"14#include "lldb/Utility/UriParser.h"15#include "llvm/ADT/StringRef.h"16#include "llvm/Support/Error.h"17#include <cstdint>18#include <optional>19 20using namespace lldb_private;21using namespace lldb_protocol;22using namespace lldb_private::mcp;23using namespace lldb;24using namespace llvm;25 26namespace {27 28static constexpr StringLiteral kSchemeAndHost = "lldb-mcp://debugger/";29 30struct CommandToolArguments {31 /// Either an id like '1' or a uri like 'lldb-mcp://debugger/1'.32 std::string debugger;33 std::string command;34};35 36bool fromJSON(const json::Value &V, CommandToolArguments &A, json::Path P) {37 json::ObjectMapper O(V, P);38 return O && O.mapOptional("debugger", A.debugger) &&39 O.mapOptional("command", A.command);40}41 42/// Helper function to create a CallToolResult from a string output.43static lldb_protocol::mcp::CallToolResult44createTextResult(std::string output, bool is_error = false) {45 lldb_protocol::mcp::CallToolResult text_result;46 text_result.content.emplace_back(47 lldb_protocol::mcp::TextContent{{std::move(output)}});48 text_result.isError = is_error;49 return text_result;50}51 52std::string to_uri(DebuggerSP debugger) {53 return (kSchemeAndHost + std::to_string(debugger->GetID())).str();54}55 56} // namespace57 58Expected<lldb_protocol::mcp::CallToolResult>59CommandTool::Call(const lldb_protocol::mcp::ToolArguments &args) {60 if (!std::holds_alternative<json::Value>(args))61 return createStringError("CommandTool requires arguments");62 63 json::Path::Root root;64 65 CommandToolArguments arguments;66 if (!fromJSON(std::get<json::Value>(args), arguments, root))67 return root.getError();68 69 lldb::DebuggerSP debugger_sp;70 71 if (!arguments.debugger.empty()) {72 llvm::StringRef debugger_specifier = arguments.debugger;73 debugger_specifier.consume_front(kSchemeAndHost);74 uint32_t debugger_id = 0;75 if (debugger_specifier.consumeInteger(10, debugger_id))76 return createStringError(77 formatv("malformed debugger specifier {0}", arguments.debugger));78 79 debugger_sp = Debugger::FindDebuggerWithID(debugger_id);80 } else {81 for (size_t i = 0; i < Debugger::GetNumDebuggers(); i++) {82 debugger_sp = Debugger::GetDebuggerAtIndex(i);83 if (debugger_sp)84 break;85 }86 }87 88 if (!debugger_sp)89 return createStringError("no debugger found");90 91 // FIXME: Disallow certain commands and their aliases.92 CommandReturnObject result(/*colors=*/false);93 debugger_sp->GetCommandInterpreter().HandleCommand(arguments.command.c_str(),94 eLazyBoolYes, result);95 96 std::string output;97 StringRef output_str = result.GetOutputString();98 if (!output_str.empty())99 output += output_str.str();100 101 std::string err_str = result.GetErrorString();102 if (!err_str.empty()) {103 if (!output.empty())104 output += '\n';105 output += err_str;106 }107 108 return createTextResult(output, !result.Succeeded());109}110 111std::optional<json::Value> CommandTool::GetSchema() const {112 using namespace llvm::json;113 Object properties{114 {"debugger",115 Object{{"type", "string"},116 {"description",117 "The debugger ID or URI to a specific debug session. If not "118 "specified, the first debugger will be used."}}},119 {"command",120 Object{{"type", "string"}, {"description", "An lldb command to run."}}}};121 Object schema{{"type", "object"}, {"properties", std::move(properties)}};122 return schema;123}124 125Expected<lldb_protocol::mcp::CallToolResult>126DebuggerListTool::Call(const lldb_protocol::mcp::ToolArguments &args) {127 llvm::json::Path::Root root;128 129 // Return a nested Markdown list with debuggers and target.130 // Example output:131 //132 // - lldb-mcp://debugger/1133 // - lldb-mcp://debugger/2134 //135 // FIXME: Use Structured Content when we adopt protocol version 2025-06-18.136 std::string output;137 llvm::raw_string_ostream os(output);138 139 const size_t num_debuggers = Debugger::GetNumDebuggers();140 for (size_t i = 0; i < num_debuggers; ++i) {141 lldb::DebuggerSP debugger_sp = Debugger::GetDebuggerAtIndex(i);142 if (!debugger_sp)143 continue;144 145 os << "- " << to_uri(debugger_sp) << '\n';146 }147 148 return createTextResult(output);149}150