LLDB mainline
Plugins/Protocol/MCP/Tool.cpp
Go to the documentation of this file.
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-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "Tool.h"
10#include "lldb/Core/Debugger.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.
44createTextResult(std::string output, bool is_error = false) {
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} // namespace
57
58Expected<lldb_protocol::mcp::CallToolResult>
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>
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/1
133 // - lldb-mcp://debugger/2
134 //
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) {
142 if (!debugger_sp)
143 continue;
144
145 os << "- " << to_uri(debugger_sp) << '\n';
146 }
147
148 return createTextResult(output);
149}
static llvm::Error createStringError(const char *format, Args &&...args)
Definition Resource.cpp:59
std::string GetErrorString(bool with_diagnostics=true) const
Return the errors as a string.
llvm::StringRef GetOutputString() const
static lldb::DebuggerSP GetDebuggerAtIndex(size_t index)
static lldb::DebuggerSP FindDebuggerWithID(lldb::user_id_t id)
static size_t GetNumDebuggers()
llvm::Expected< lldb_protocol::mcp::CallToolResult > Call(const lldb_protocol::mcp::ToolArguments &args) override
std::optional< llvm::json::Value > GetSchema() const override
llvm::Expected< lldb_protocol::mcp::CallToolResult > Call(const lldb_protocol::mcp::ToolArguments &args) override
A class that represents a running process on the host machine.
bool fromJSON(const llvm::json::Value &value, TraceSupportedResponse &info, llvm::json::Path path)
std::variant< std::monostate, llvm::json::Value > ToolArguments
Definition Protocol.h:190
std::shared_ptr< lldb_private::Debugger > DebuggerSP
The server’s response to a tool call.
Definition Protocol.h:299
std::vector< ContentBlock > content
A list of content objects that represent the unstructured result of the tool call.
Definition Protocol.h:302
bool isError
Whether the tool call ended in an error.
Definition Protocol.h:316
Text provided to or from an LLM.
Definition Protocol.h:169