LLDB mainline
Server.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
10#include "lldb/Host/File.h"
12#include "lldb/Host/HostInfo.h"
15#include "llvm/ADT/SmallString.h"
16#include "llvm/Support/FileSystem.h"
17#include "llvm/Support/JSON.h"
18#include "llvm/Support/Signals.h"
19
20using namespace llvm;
21using namespace lldb_private;
22using namespace lldb_protocol::mcp;
23
24ServerInfoHandle::ServerInfoHandle(StringRef filename) : m_filename(filename) {
25 if (!m_filename.empty())
26 sys::RemoveFileOnSignal(m_filename);
27}
28
30
32 *this = std::move(other);
33}
34
37 m_filename = std::move(other.m_filename);
38 return *this;
39}
40
42 if (m_filename.empty())
43 return;
44
45 sys::fs::remove(m_filename);
46 sys::DontRemoveFileOnSignal(m_filename);
47 m_filename.clear();
48}
49
50json::Value lldb_protocol::mcp::toJSON(const ServerInfo &SM) {
51 return json::Object{{"connection_uri", SM.connection_uri}};
52}
53
54bool lldb_protocol::mcp::fromJSON(const json::Value &V, ServerInfo &SM,
55 json::Path P) {
56 json::ObjectMapper O(V, P);
57 return O && O.map("connection_uri", SM.connection_uri);
58}
59
60Expected<ServerInfoHandle> ServerInfo::Write(const ServerInfo &info) {
61 std::string buf = formatv("{0}", toJSON(info)).str();
62 size_t num_bytes = buf.size();
63
64 FileSpec user_lldb_dir = HostInfo::GetUserLLDBDir();
65
66 Status error(sys::fs::create_directory(user_lldb_dir.GetPath()));
67 if (error.Fail())
68 return error.takeError();
69
70 FileSpec mcp_registry_entry_path = user_lldb_dir.CopyByAppendingPathComponent(
71 formatv("lldb-mcp-{0}.json", getpid()).str());
72
76 Expected<lldb::FileUP> file =
77 FileSystem::Instance().Open(mcp_registry_entry_path, flags);
78 if (!file)
79 return file.takeError();
80 if (llvm::Error error = (*file)->Write(buf.data(), num_bytes).takeError())
81 return error;
82 return ServerInfoHandle{mcp_registry_entry_path.GetPath()};
83}
84
85Expected<std::vector<ServerInfo>> ServerInfo::Load() {
86 namespace path = llvm::sys::path;
87 FileSpec user_lldb_dir = HostInfo::GetUserLLDBDir();
89 std::error_code EC;
90 vfs::directory_iterator it = fs.DirBegin(user_lldb_dir, EC);
91 vfs::directory_iterator end;
92 std::vector<ServerInfo> infos;
93 for (; it != end && !EC; it.increment(EC)) {
94 auto &entry = *it;
95 auto path = entry.path();
96 auto name = path::filename(path);
97 if (!name.starts_with("lldb-mcp-") || !name.ends_with(".json"))
98 continue;
99
100 auto buffer = fs.CreateDataBuffer(path);
101 auto info = json::parse<ServerInfo>(toStringRef(buffer->GetData()));
102 if (!info)
103 return info.takeError();
104
105 infos.emplace_back(std::move(*info));
106 }
107
108 return infos;
109}
110
111Server::Server(std::string name, std::string version, MCPTransport &client,
112 LogCallback log_callback, ClosedCallback closed_callback)
113 : m_name(std::move(name)), m_version(std::move(version)), m_client(client),
114 m_log_callback(std::move(log_callback)),
115 m_closed_callback(std::move(closed_callback)) {
117}
118
120 AddRequestHandler("initialize", std::bind(&Server::InitializeHandler, this,
121 std::placeholders::_1));
122 AddRequestHandler("tools/list", std::bind(&Server::ToolsListHandler, this,
123 std::placeholders::_1));
124 AddRequestHandler("tools/call", std::bind(&Server::ToolsCallHandler, this,
125 std::placeholders::_1));
126 AddRequestHandler("resources/list", std::bind(&Server::ResourcesListHandler,
127 this, std::placeholders::_1));
128 AddRequestHandler("resources/read", std::bind(&Server::ResourcesReadHandler,
129 this, std::placeholders::_1));
130}
131
132llvm::Expected<Response> Server::Handle(const Request &request) {
133 auto it = m_request_handlers.find(request.method);
134 if (it != m_request_handlers.end()) {
135 llvm::Expected<Response> response = it->second(request);
136 if (!response)
137 return response;
138 response->id = request.id;
139 return *response;
140 }
141
142 return llvm::make_error<MCPError>(
143 llvm::formatv("no handler for request: {0}", request.method).str());
144}
145
146void Server::Handle(const Notification &notification) {
147 auto it = m_notification_handlers.find(notification.method);
148 if (it != m_notification_handlers.end()) {
149 it->second(notification);
150 return;
151 }
152}
153
154void Server::AddTool(std::unique_ptr<Tool> tool) {
155 if (!tool)
156 return;
157 m_tools[tool->GetName()] = std::move(tool);
158}
159
161 std::unique_ptr<ResourceProvider> resource_provider) {
162 if (!resource_provider)
163 return;
164 m_resource_providers.push_back(std::move(resource_provider));
165}
166
167void Server::AddRequestHandler(llvm::StringRef method, RequestHandler handler) {
168 m_request_handlers[method] = std::move(handler);
169}
170
171void Server::AddNotificationHandler(llvm::StringRef method,
172 NotificationHandler handler) {
173 m_notification_handlers[method] = std::move(handler);
174}
175
176llvm::Expected<Response> Server::InitializeHandler(const Request &request) {
177 Response response;
178 InitializeResult result;
180 result.capabilities = GetCapabilities();
181 result.serverInfo.name = m_name;
183 response.result = std::move(result);
184 return response;
185}
186
187llvm::Expected<Response> Server::ToolsListHandler(const Request &request) {
188 Response response;
189
190 ListToolsResult result;
191 for (const auto &tool : m_tools)
192 result.tools.emplace_back(tool.second->GetDefinition());
193
194 response.result = std::move(result);
195
196 return response;
197}
198
199llvm::Expected<Response> Server::ToolsCallHandler(const Request &request) {
200 Response response;
201
202 if (!request.params)
203 return llvm::createStringError("no tool parameters");
204 CallToolParams params;
205 json::Path::Root root("params");
206 if (!fromJSON(request.params, params, root))
207 return root.getError();
208
209 llvm::StringRef tool_name = params.name;
210 if (tool_name.empty())
211 return llvm::createStringError("no tool name");
212
213 auto it = m_tools.find(tool_name);
214 if (it == m_tools.end())
215 return llvm::createStringError(llvm::formatv("no tool \"{0}\"", tool_name));
216
217 ToolArguments tool_args;
218 if (params.arguments)
219 tool_args = *params.arguments;
220
221 llvm::Expected<CallToolResult> text_result = it->second->Call(tool_args);
222 if (!text_result)
223 return text_result.takeError();
224
225 response.result = toJSON(*text_result);
226
227 return response;
228}
229
230llvm::Expected<Response> Server::ResourcesListHandler(const Request &request) {
231 Response response;
232
233 ListResourcesResult result;
234 for (std::unique_ptr<ResourceProvider> &resource_provider_up :
236 for (const Resource &resource : resource_provider_up->GetResources())
237 result.resources.push_back(resource);
238
239 response.result = std::move(result);
240
241 return response;
242}
243
244llvm::Expected<Response> Server::ResourcesReadHandler(const Request &request) {
245 Response response;
246
247 if (!request.params)
248 return llvm::createStringError("no resource parameters");
249
250 ReadResourceParams params;
251 json::Path::Root root("params");
252 if (!fromJSON(request.params, params, root))
253 return root.getError();
254
255 llvm::StringRef uri_str = params.uri;
256 if (uri_str.empty())
257 return llvm::createStringError("no resource uri");
258
259 for (std::unique_ptr<ResourceProvider> &resource_provider_up :
261 llvm::Expected<ReadResourceResult> result =
262 resource_provider_up->ReadResource(uri_str);
263 if (result.errorIsA<UnsupportedURI>()) {
264 llvm::consumeError(result.takeError());
265 continue;
266 }
267 if (!result)
268 return result.takeError();
269
270 Response response;
271 response.result = std::move(*result);
272 return response;
273 }
274
275 return make_error<MCPError>(
276 llvm::formatv("no resource handler for uri: {0}", uri_str).str(),
278}
279
282 capabilities.supportsToolsList = true;
283 // FIXME: Support sending notifications when a debugger/target are
284 // added/removed.
285 capabilities.supportsResourcesList = false;
286 return capabilities;
287}
288
289void Server::Log(llvm::StringRef message) {
290 if (m_log_callback)
291 m_log_callback(message);
292}
293
294void Server::Received(const Request &request) {
295 auto SendResponse = [this](const Response &response) {
296 if (llvm::Error error = m_client.Send(response))
297 Log(llvm::toString(std::move(error)));
298 };
299
300 llvm::Expected<Response> response = Handle(request);
301 if (response)
302 return SendResponse(*response);
303
304 lldb_protocol::mcp::Error protocol_error;
305 llvm::handleAllErrors(
306 response.takeError(),
307 [&](const MCPError &err) { protocol_error = err.toProtocolError(); },
308 [&](const llvm::ErrorInfoBase &err) {
309 protocol_error.code = MCPError::kInternalError;
310 protocol_error.message = err.message();
311 });
312 Response error_response;
313 error_response.id = request.id;
314 error_response.result = std::move(protocol_error);
315 SendResponse(error_response);
316}
317
318void Server::Received(const Response &response) {
319 Log("unexpected MCP message: response");
320}
321
322void Server::Received(const Notification &notification) {
323 Handle(notification);
324}
325
326void Server::OnError(llvm::Error error) {
327 Log(llvm::toString(std::move(error)));
328}
329
331 Log("EOF");
334}
static llvm::raw_ostream & error(Stream &strm)
A file utility class.
Definition FileSpec.h:57
FileSpec CopyByAppendingPathComponent(llvm::StringRef component) const
Definition FileSpec.cpp:425
size_t GetPath(char *path, size_t max_path_length, bool denormalize=true) const
Extract the full path to the file.
Definition FileSpec.cpp:374
int Open(const char *path, int flags, int mode=0600)
Wraps open in a platform-independent way.
llvm::vfs::directory_iterator DirBegin(const FileSpec &file_spec, std::error_code &ec)
Get a directory iterator.
static FileSystem & Instance()
std::shared_ptr< DataBuffer > CreateDataBuffer(const llvm::Twine &path, uint64_t size=0, uint64_t offset=0)
Create memory buffer from path.
@ eOpenOptionWriteOnly
Definition File.h:52
@ eOpenOptionCanCreate
Definition File.h:56
@ eOpenOptionTruncate
Definition File.h:57
An error handling class.
Definition Status.h:118
static constexpr int64_t kResourceNotFound
Definition MCPError.h:31
A handle that tracks the server info on disk and cleans up the disk record once it is no longer refer...
Definition Server.h:110
llvm::SmallString< 128 > m_filename
Definition Server.h:128
void Remove()
Remove the file.
Definition Server.cpp:41
ServerInfoHandle & operator=(ServerInfoHandle &&other) noexcept
Definition Server.cpp:36
ServerInfoHandle(llvm::StringRef filename="")
std::function< void(const Notification &)> NotificationHandler
Definition Server.h:39
llvm::Expected< Response > ToolsCallHandler(const Request &)
Definition Server.cpp:199
const std::string m_version
Definition Server.h:80
const std::string m_name
Definition Server.h:79
llvm::unique_function< void()> ClosedCallback
Definition Server.h:32
void AddTool(std::unique_ptr< Tool > tool)
Definition Server.cpp:154
void AddResourceProvider(std::unique_ptr< ResourceProvider > resource_provider)
Definition Server.cpp:160
llvm::StringMap< std::unique_ptr< Tool > > m_tools
Definition Server.h:86
std::vector< std::unique_ptr< ResourceProvider > > m_resource_providers
Definition Server.h:87
llvm::StringMap< NotificationHandler > m_notification_handlers
Definition Server.h:90
LogCallback m_log_callback
Definition Server.h:83
Server(std::string name, std::string version, MCPTransport &client, LogCallback log_callback={}, ClosedCallback closed_callback={})
Definition Server.cpp:111
llvm::StringMap< RequestHandler > m_request_handlers
Definition Server.h:89
ClosedCallback m_closed_callback
Definition Server.h:84
void OnError(llvm::Error) override
Definition Server.cpp:326
llvm::Expected< Response > ResourcesListHandler(const Request &)
Definition Server.cpp:230
llvm::Expected< Response > ResourcesReadHandler(const Request &)
Definition Server.cpp:244
llvm::Expected< Response > Handle(const Request &request)
Definition Server.cpp:132
llvm::Expected< Response > ToolsListHandler(const Request &)
Definition Server.cpp:187
void Log(llvm::StringRef)
Definition Server.cpp:289
MCPTransport & m_client
Definition Server.h:82
void AddRequestHandler(llvm::StringRef method, RequestHandler handler)
Definition Server.cpp:167
std::function< llvm::Expected< Response >(const Request &)> RequestHandler
Definition Server.h:49
void Received(const Request &) override
Definition Server.cpp:294
void OnClosed() override
Definition Server.cpp:330
ServerCapabilities GetCapabilities()
Definition Server.cpp:280
void AddNotificationHandler(llvm::StringRef method, NotificationHandler handler)
Definition Server.cpp:171
llvm::Expected< Response > InitializeHandler(const Request &)
Definition Server.cpp:176
A class that represents a running process on the host machine.
std::variant< std::monostate, llvm::json::Value > ToolArguments
Definition Protocol.h:190
llvm::json::Value toJSON(const Request &)
Definition Protocol.cpp:66
lldb_private::Transport< Request, Response, Notification > MCPTransport
Generic transport that uses the MCP protocol.
Definition Transport.h:21
llvm::unique_function< void(llvm::StringRef message)> LogCallback
Generic logging callback, to allow the MCP server / client / transport layer to be independent of the...
Definition Transport.h:25
static llvm::StringLiteral kProtocolVersion
Definition Protocol.h:25
bool fromJSON(const llvm::json::Value &, Request &, llvm::json::Path)
Definition Protocol.cpp:74
Used by the client to invoke a tool provided by the server.
Definition Protocol.h:291
std::optional< llvm::json::Value > arguments
Definition Protocol.h:293
std::string name
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if ti...
Definition Protocol.h:197
After receiving an initialize request from the client, the server sends this response.
Definition Protocol.h:255
std::string protocolVersion
The version of the Model Context Protocol that the server wants to use.
Definition Protocol.h:259
The server’s response to a resources/list request from the client.
Definition Protocol.h:126
std::vector< Resource > resources
Definition Protocol.h:127
The server's response to a tools/list request from the client.
Definition Protocol.h:280
std::vector< ToolDefinition > tools
Definition Protocol.h:281
A notification which does not expect a response.
Definition Protocol.h:88
std::string method
The method to be invoked.
Definition Protocol.h:90
Sent from the client to the server, to read a specific resource URI.
Definition Protocol.h:151
std::string uri
The URI of the resource to read.
Definition Protocol.h:154
A request that expects a response.
Definition Protocol.h:34
std::optional< llvm::json::Value > params
The method's params.
Definition Protocol.h:40
std::string method
The method to be invoked.
Definition Protocol.h:38
Id id
The request id.
Definition Protocol.h:36
A known resource that the server is capable of reading.
Definition Protocol.h:108
A response to a request, either an error or a result.
Definition Protocol.h:76
Id id
The request id.
Definition Protocol.h:78
std::variant< Error, llvm::json::Value > result
The result of the request, either an Error or the JSON value of the response.
Definition Protocol.h:81
Capabilities that a server may support.
Definition Protocol.h:224
Information about this instance of lldb's MCP server for lldb-mcp to use to coordinate connecting an ...
Definition Server.h:97
static llvm::Expected< std::vector< ServerInfo > > Load()
Loads any server info saved in ~/.lldb.
Definition Server.cpp:85
static llvm::Expected< ServerInfoHandle > Write(const ServerInfo &)
Writes the server info into a unique file in ~/.lldb.
Definition Server.cpp:60