77// ===----------------------------------------------------------------------===//
88
99#include " Tool.h"
10+ #include " lldb/Core/Debugger.h"
1011#include " lldb/Interpreter/CommandInterpreter.h"
1112#include " lldb/Interpreter/CommandReturnObject.h"
1213#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>
1319
1420using namespace lldb_private ;
1521using namespace lldb_protocol ;
1622using namespace lldb_private ::mcp;
23+ using namespace lldb ;
1724using namespace llvm ;
1825
1926namespace {
27+
28+ static constexpr StringLiteral kSchemeAndHost = " lldb-mcp://debugger/" ;
29+
2030struct CommandToolArguments {
21- uint64_t debugger_id;
22- std::string arguments;
31+ // / Either an id like '1' or a uri like 'lldb-mcp://debugger/1'.
32+ std::string debugger;
33+ std::string command;
2334};
2435
25- bool fromJSON (const llvm::json::Value &V, CommandToolArguments &A,
26- llvm::json::Path P) {
27- llvm::json::ObjectMapper O (V, P);
28- return O && O.map (" debugger_id" , A.debugger_id ) &&
29- O.mapOptional (" arguments" , A.arguments );
36+ bool 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 );
3040}
3141
3242// / Helper function to create a CallToolResult from a string output.
@@ -39,9 +49,13 @@ createTextResult(std::string output, bool is_error = false) {
3949 return text_result;
4050}
4151
52+ std::string to_uri (DebuggerSP debugger) {
53+ return (kSchemeAndHost + std::to_string (debugger->GetID ())).str ();
54+ }
55+
4256} // namespace
4357
44- llvm:: Expected<lldb_protocol::mcp::CallToolResult>
58+ Expected<lldb_protocol::mcp::CallToolResult>
4559CommandTool::Call (const lldb_protocol::mcp::ToolArguments &args) {
4660 if (!std::holds_alternative<json::Value>(args))
4761 return createStringError (" CommandTool requires arguments" );
@@ -52,19 +66,35 @@ CommandTool::Call(const lldb_protocol::mcp::ToolArguments &args) {
5266 if (!fromJSON (std::get<json::Value>(args), arguments, root))
5367 return root.getError ();
5468
55- lldb::DebuggerSP debugger_sp =
56- Debugger::FindDebuggerWithID (arguments.debugger_id );
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+
5788 if (!debugger_sp)
58- return createStringError (
59- llvm::formatv (" no debugger with id {0}" , arguments.debugger_id ));
89+ return createStringError (" no debugger found" );
6090
6191 // FIXME: Disallow certain commands and their aliases.
6292 CommandReturnObject result (/* colors=*/ false );
63- debugger_sp->GetCommandInterpreter ().HandleCommand (
64- arguments. arguments . c_str (), eLazyBoolYes, result);
93+ debugger_sp->GetCommandInterpreter ().HandleCommand (arguments. command . c_str (),
94+ eLazyBoolYes, result);
6595
6696 std::string output;
67- llvm:: StringRef output_str = result.GetOutputString ();
97+ StringRef output_str = result.GetOutputString ();
6898 if (!output_str.empty ())
6999 output += output_str.str ();
70100
@@ -78,14 +108,42 @@ CommandTool::Call(const lldb_protocol::mcp::ToolArguments &args) {
78108 return createTextResult (output, !result.Succeeded ());
79109}
80110
81- std::optional<llvm::json::Value> CommandTool::GetSchema () const {
82- llvm::json::Object id_type{{" type" , " number" }};
83- llvm::json::Object str_type{{" type" , " string" }};
84- llvm::json::Object properties{{" debugger_id" , std::move (id_type)},
85- {" arguments" , std::move (str_type)}};
86- llvm::json::Array required{" debugger_id" };
87- llvm::json::Object schema{{" type" , " object" },
88- {" properties" , std::move (properties)},
89- {" required" , std::move (required)}};
111+ std::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)}};
90122 return schema;
91123}
124+
125+ Expected<lldb_protocol::mcp::CallToolResult>
126+ DebuggerListTool::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/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) {
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+ }
0 commit comments