-
Notifications
You must be signed in to change notification settings - Fork 14.7k
[lldb] Expose debuggers and target as resources through MCP #148075
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,17 +76,75 @@ struct ToolCapability { | |
llvm::json::Value toJSON(const ToolCapability &); | ||
bool fromJSON(const llvm::json::Value &, ToolCapability &, llvm::json::Path); | ||
|
||
struct ResourceCapability { | ||
/// Whether this server supports notifications for changes to the resources | ||
/// list. | ||
bool listChanged = false; | ||
|
||
/// Whether subscriptions are supported. | ||
bool subscribe = false; | ||
}; | ||
|
||
llvm::json::Value toJSON(const ResourceCapability &); | ||
bool fromJSON(const llvm::json::Value &, ResourceCapability &, | ||
llvm::json::Path); | ||
|
||
/// Capabilities that a server may support. Known capabilities are defined here, | ||
/// in this schema, but this is not a closed set: any server can define its own, | ||
/// additional capabilities. | ||
struct Capabilities { | ||
/// Present if the server offers any tools to call. | ||
/// Tool capabilities of the server. | ||
ToolCapability tools; | ||
|
||
/// Resource capabilities of the server. | ||
ResourceCapability resources; | ||
}; | ||
|
||
llvm::json::Value toJSON(const Capabilities &); | ||
bool fromJSON(const llvm::json::Value &, Capabilities &, llvm::json::Path); | ||
|
||
/// A known resource that the server is capable of reading. | ||
struct Resource { | ||
/// The URI of this resource. | ||
std::string uri; | ||
|
||
/// A human-readable name for this resource. | ||
std::string name; | ||
|
||
/// A description of what this resource represents. | ||
std::optional<std::string> description; | ||
|
||
/// The MIME type of this resource, if known. | ||
std::optional<std::string> mimeType; | ||
Comment on lines
+115
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been using When encoding There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me do this as a followup for all of MCP 👍 |
||
}; | ||
|
||
llvm::json::Value toJSON(const Resource &); | ||
bool fromJSON(const llvm::json::Value &, Resource &, llvm::json::Path); | ||
|
||
/// The contents of a specific resource or sub-resource. | ||
struct ResourceContents { | ||
/// The URI of this resource. | ||
std::string uri; | ||
|
||
/// The text of the item. This must only be set if the item can actually be | ||
/// represented as text (not binary data). | ||
std::string text; | ||
|
||
/// The MIME type of this resource, if known. | ||
std::optional<std::string> mimeType; | ||
}; | ||
|
||
llvm::json::Value toJSON(const ResourceContents &); | ||
bool fromJSON(const llvm::json::Value &, ResourceContents &, llvm::json::Path); | ||
|
||
/// The server's response to a resources/read request from the client. | ||
struct ResourceResult { | ||
std::vector<ResourceContents> contents; | ||
}; | ||
|
||
llvm::json::Value toJSON(const ResourceResult &); | ||
bool fromJSON(const llvm::json::Value &, ResourceResult &, llvm::json::Path); | ||
|
||
/// Text provided to or from an LLM. | ||
struct TextContent { | ||
/// The text content of the message. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,20 +28,29 @@ ProtocolServerMCP::ProtocolServerMCP() : ProtocolServer() { | |
AddRequestHandler("initialize", | ||
std::bind(&ProtocolServerMCP::InitializeHandler, this, | ||
std::placeholders::_1)); | ||
|
||
AddRequestHandler("tools/list", | ||
std::bind(&ProtocolServerMCP::ToolsListHandler, this, | ||
std::placeholders::_1)); | ||
AddRequestHandler("tools/call", | ||
std::bind(&ProtocolServerMCP::ToolsCallHandler, this, | ||
std::placeholders::_1)); | ||
|
||
AddRequestHandler("resources/list", | ||
std::bind(&ProtocolServerMCP::ResourcesListHandler, this, | ||
std::placeholders::_1)); | ||
AddRequestHandler("resources/read", | ||
std::bind(&ProtocolServerMCP::ResourcesReadHandler, this, | ||
std::placeholders::_1)); | ||
AddNotificationHandler( | ||
"notifications/initialized", [](const protocol::Notification &) { | ||
LLDB_LOG(GetLog(LLDBLog::Host), "MCP initialization complete"); | ||
}); | ||
|
||
AddTool( | ||
std::make_unique<CommandTool>("lldb_command", "Run an lldb command.")); | ||
AddTool(std::make_unique<DebuggerListTool>( | ||
"lldb_debugger_list", "List debugger instances with their debugger_id.")); | ||
|
||
AddResourceProvider(std::make_unique<DebuggerResourceProvider>()); | ||
} | ||
|
||
ProtocolServerMCP::~ProtocolServerMCP() { llvm::consumeError(Stop()); } | ||
|
@@ -75,7 +84,7 @@ ProtocolServerMCP::Handle(protocol::Request request) { | |
} | ||
|
||
return make_error<MCPError>( | ||
llvm::formatv("no handler for request: {0}", request.method).str(), 1); | ||
llvm::formatv("no handler for request: {0}", request.method).str()); | ||
} | ||
|
||
void ProtocolServerMCP::Handle(protocol::Notification notification) { | ||
|
@@ -216,7 +225,7 @@ ProtocolServerMCP::HandleData(llvm::StringRef data) { | |
response.takeError(), | ||
[&](const MCPError &err) { protocol_error = err.toProtcolError(); }, | ||
[&](const llvm::ErrorInfoBase &err) { | ||
protocol_error.error.code = -1; | ||
protocol_error.error.code = MCPError::kInternalError; | ||
protocol_error.error.message = err.message(); | ||
}); | ||
protocol_error.id = request->id; | ||
|
@@ -244,6 +253,9 @@ ProtocolServerMCP::HandleData(llvm::StringRef data) { | |
protocol::Capabilities ProtocolServerMCP::GetCapabilities() { | ||
protocol::Capabilities capabilities; | ||
capabilities.tools.listChanged = true; | ||
// FIXME: Support sending notifications when a debugger/target are | ||
// added/removed. | ||
capabilities.resources.listChanged = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we support sending notifications when a debugger/target are added/removed? Otherwise the client could get out of sync with the state of lldb. Maybe as a FIXME/TODO if we can't easily do that now. |
||
return capabilities; | ||
} | ||
|
||
|
@@ -255,6 +267,15 @@ void ProtocolServerMCP::AddTool(std::unique_ptr<Tool> tool) { | |
m_tools[tool->GetName()] = std::move(tool); | ||
} | ||
|
||
void ProtocolServerMCP::AddResourceProvider( | ||
std::unique_ptr<ResourceProvider> resource_provider) { | ||
std::lock_guard<std::mutex> guard(m_server_mutex); | ||
|
||
if (!resource_provider) | ||
return; | ||
m_resource_providers.push_back(std::move(resource_provider)); | ||
} | ||
|
||
void ProtocolServerMCP::AddRequestHandler(llvm::StringRef method, | ||
RequestHandler handler) { | ||
std::lock_guard<std::mutex> guard(m_server_mutex); | ||
|
@@ -327,3 +348,63 @@ ProtocolServerMCP::ToolsCallHandler(const protocol::Request &request) { | |
|
||
return response; | ||
} | ||
|
||
llvm::Expected<protocol::Response> | ||
ProtocolServerMCP::ResourcesListHandler(const protocol::Request &request) { | ||
protocol::Response response; | ||
|
||
llvm::json::Array resources; | ||
|
||
std::lock_guard<std::mutex> guard(m_server_mutex); | ||
for (std::unique_ptr<ResourceProvider> &resource_provider_up : | ||
m_resource_providers) { | ||
for (const protocol::Resource &resource : | ||
resource_provider_up->GetResources()) | ||
resources.push_back(resource); | ||
} | ||
response.result.emplace( | ||
llvm::json::Object{{"resources", std::move(resources)}}); | ||
|
||
return response; | ||
} | ||
|
||
llvm::Expected<protocol::Response> | ||
ProtocolServerMCP::ResourcesReadHandler(const protocol::Request &request) { | ||
protocol::Response response; | ||
|
||
if (!request.params) | ||
return llvm::createStringError("no resource parameters"); | ||
|
||
const json::Object *param_obj = request.params->getAsObject(); | ||
if (!param_obj) | ||
return llvm::createStringError("no resource parameters"); | ||
|
||
const json::Value *uri = param_obj->get("uri"); | ||
if (!uri) | ||
return llvm::createStringError("no resource uri"); | ||
|
||
llvm::StringRef uri_str = uri->getAsString().value_or(""); | ||
if (uri_str.empty()) | ||
return llvm::createStringError("no resource uri"); | ||
|
||
std::lock_guard<std::mutex> guard(m_server_mutex); | ||
for (std::unique_ptr<ResourceProvider> &resource_provider_up : | ||
m_resource_providers) { | ||
llvm::Expected<protocol::ResourceResult> result = | ||
resource_provider_up->ReadResource(uri_str); | ||
if (result.errorIsA<UnsupportedURI>()) { | ||
llvm::consumeError(result.takeError()); | ||
continue; | ||
} | ||
if (!result) | ||
return result.takeError(); | ||
|
||
protocol::Response response; | ||
response.result.emplace(std::move(*result)); | ||
return response; | ||
} | ||
|
||
return make_error<MCPError>( | ||
llvm::formatv("no resource handler for uri: {0}", uri_str).str(), | ||
MCPError::kResourceNotFound); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the MCP spec, there are 2 error codes for resources:
See https://modelcontextprotocol.io/specification/2025-06-18/server/resources#error-handling
Should we use those here?