[Lldb-commits] [lldb] a67257b - [lldb] Adding structured types for existing MCP calls. (#155460)
via lldb-commits
lldb-commits at lists.llvm.org
Tue Aug 26 15:09:39 PDT 2025
Author: John Harrison
Date: 2025-08-26T15:09:36-07:00
New Revision: a67257bbfb5bce5a21f21d7e78049cfcbb283e33
URL: https://github.com/llvm/llvm-project/commit/a67257bbfb5bce5a21f21d7e78049cfcbb283e33
DIFF: https://github.com/llvm/llvm-project/commit/a67257bbfb5bce5a21f21d7e78049cfcbb283e33.diff
LOG: [lldb] Adding structured types for existing MCP calls. (#155460)
This adds or renames existing types to match the names of the types on
https://modelcontextprotocol.io/specification/2025-06-18/schema for the
existing calls.
The new types are used in the unit tests and server implementation to
remove the need for crafting various `llvm::json::Object` values by
hand.
Added:
lldb/unittests/Protocol/ProtocolMCPTestUtilities.h
Modified:
lldb/include/lldb/Protocol/MCP/Protocol.h
lldb/include/lldb/Protocol/MCP/Resource.h
lldb/include/lldb/Protocol/MCP/Server.h
lldb/include/lldb/Protocol/MCP/Tool.h
lldb/source/Plugins/Protocol/MCP/Resource.cpp
lldb/source/Plugins/Protocol/MCP/Resource.h
lldb/source/Plugins/Protocol/MCP/Tool.cpp
lldb/source/Plugins/Protocol/MCP/Tool.h
lldb/source/Protocol/MCP/Protocol.cpp
lldb/source/Protocol/MCP/Server.cpp
lldb/unittests/Protocol/ProtocolMCPServerTest.cpp
lldb/unittests/Protocol/ProtocolMCPTest.cpp
Removed:
################################################################################
diff --git a/lldb/include/lldb/Protocol/MCP/Protocol.h b/lldb/include/lldb/Protocol/MCP/Protocol.h
index 49f94902217558..6e1ffcbe1f3e33 100644
--- a/lldb/include/lldb/Protocol/MCP/Protocol.h
+++ b/lldb/include/lldb/Protocol/MCP/Protocol.h
@@ -18,6 +18,7 @@
#include <optional>
#include <string>
#include <variant>
+#include <vector>
namespace lldb_protocol::mcp {
@@ -38,11 +39,24 @@ struct Request {
/// The method's params.
std::optional<llvm::json::Value> params;
};
-
llvm::json::Value toJSON(const Request &);
bool fromJSON(const llvm::json::Value &, Request &, llvm::json::Path);
bool operator==(const Request &, const Request &);
+enum ErrorCode : signed {
+ /// Invalid JSON was received by the server. An error occurred on the server
+ /// while parsing the JSON text.
+ eErrorCodeParseError = -32700,
+ /// The JSON sent is not a valid Request object.
+ eErrorCodeInvalidRequest = -32600,
+ /// The method does not exist / is not available.
+ eErrorCodeMethodNotFound = -32601,
+ /// Invalid method parameter(s).
+ eErrorCodeInvalidParams = -32602,
+ /// Internal JSON-RPC error.
+ eErrorCodeInternalError = -32603,
+};
+
struct Error {
/// The error type that occurred.
int64_t code = 0;
@@ -52,9 +66,8 @@ struct Error {
/// Additional information about the error. The value of this member is
/// defined by the sender (e.g. detailed error information, nested errors
/// etc.).
- std::optional<llvm::json::Value> data;
+ std::optional<llvm::json::Value> data = std::nullopt;
};
-
llvm::json::Value toJSON(const Error &);
bool fromJSON(const llvm::json::Value &, Error &, llvm::json::Path);
bool operator==(const Error &, const Error &);
@@ -67,7 +80,6 @@ struct Response {
/// response.
std::variant<Error, llvm::json::Value> result;
};
-
llvm::json::Value toJSON(const Response &);
bool fromJSON(const llvm::json::Value &, Response &, llvm::json::Path);
bool operator==(const Response &, const Response &);
@@ -79,7 +91,6 @@ struct Notification {
/// The notification's params.
std::optional<llvm::json::Value> params;
};
-
llvm::json::Value toJSON(const Notification &);
bool fromJSON(const llvm::json::Value &, Notification &, llvm::json::Path);
bool operator==(const Notification &, const Notification &);
@@ -90,45 +101,9 @@ using Message = std::variant<Request, Response, Notification>;
// not force it to be checked early here.
static_assert(std::is_convertible_v<Message, Message>,
"Message is not convertible to itself");
-
bool fromJSON(const llvm::json::Value &, Message &, llvm::json::Path);
llvm::json::Value toJSON(const Message &);
-struct ToolCapability {
- /// Whether this server supports notifications for changes to the tool list.
- bool listChanged = false;
-};
-
-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 {
- /// 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.
@@ -138,17 +113,25 @@ struct Resource {
std::string name;
/// A description of what this resource represents.
- std::string description;
+ std::string description = "";
/// The MIME type of this resource, if known.
- std::string mimeType;
+ std::string mimeType = "";
};
llvm::json::Value toJSON(const Resource &);
bool fromJSON(const llvm::json::Value &, Resource &, llvm::json::Path);
+/// The server’s response to a resources/list request from the client.
+struct ListResourcesResult {
+ std::vector<Resource> resources;
+};
+llvm::json::Value toJSON(const ListResourcesResult &);
+bool fromJSON(const llvm::json::Value &, ListResourcesResult &,
+ llvm::json::Path);
+
/// The contents of a specific resource or sub-resource.
-struct ResourceContents {
+struct TextResourceContents {
/// The URI of this resource.
std::string uri;
@@ -160,34 +143,37 @@ struct ResourceContents {
std::string mimeType;
};
-llvm::json::Value toJSON(const ResourceContents &);
-bool fromJSON(const llvm::json::Value &, ResourceContents &, llvm::json::Path);
+llvm::json::Value toJSON(const TextResourceContents &);
+bool fromJSON(const llvm::json::Value &, TextResourceContents &,
+ llvm::json::Path);
-/// The server's response to a resources/read request from the client.
-struct ResourceResult {
- std::vector<ResourceContents> contents;
+/// Sent from the client to the server, to read a specific resource URI.
+struct ReadResourceParams {
+ /// The URI of the resource to read. The URI can use any protocol; it is up to
+ /// the server how to interpret it.
+ std::string uri;
};
+llvm::json::Value toJSON(const ReadResourceParams &);
+bool fromJSON(const llvm::json::Value &, ReadResourceParams &,
+ llvm::json::Path);
-llvm::json::Value toJSON(const ResourceResult &);
-bool fromJSON(const llvm::json::Value &, ResourceResult &, llvm::json::Path);
+/// The server's response to a resources/read request from the client.
+struct ReadResourceResult {
+ std::vector<TextResourceContents> contents;
+};
+llvm::json::Value toJSON(const ReadResourceResult &);
+bool fromJSON(const llvm::json::Value &, ReadResourceResult &,
+ llvm::json::Path);
/// Text provided to or from an LLM.
struct TextContent {
/// The text content of the message.
std::string text;
};
-
llvm::json::Value toJSON(const TextContent &);
bool fromJSON(const llvm::json::Value &, TextContent &, llvm::json::Path);
-struct TextResult {
- std::vector<TextContent> content;
- bool isError = false;
-};
-
-llvm::json::Value toJSON(const TextResult &);
-bool fromJSON(const llvm::json::Value &, TextResult &, llvm::json::Path);
-
+/// Definition for a tool the client can call.
struct ToolDefinition {
/// Unique identifier for the tool.
std::string name;
@@ -198,12 +184,144 @@ struct ToolDefinition {
// JSON Schema for the tool's parameters.
std::optional<llvm::json::Value> inputSchema;
};
-
llvm::json::Value toJSON(const ToolDefinition &);
bool fromJSON(const llvm::json::Value &, ToolDefinition &, llvm::json::Path);
using ToolArguments = std::variant<std::monostate, llvm::json::Value>;
+/// Describes the name and version of an MCP implementation, with an optional
+/// title for UI representation.
+struct Implementation {
+ /// Intended for programmatic or logical use, but used as a display name in
+ /// past specs or fallback (if title isn’t present).
+ std::string name;
+
+ std::string version;
+
+ /// Intended for UI and end-user contexts — optimized to be human-readable and
+ /// easily understood, even by those unfamiliar with domain-specific
+ /// terminology.
+ ///
+ /// If not provided, the name should be used for display (except for Tool,
+ /// where annotations.title should be given precedence over using name, if
+ /// present).
+ std::string title = "";
+};
+llvm::json::Value toJSON(const Implementation &);
+bool fromJSON(const llvm::json::Value &, Implementation &, llvm::json::Path);
+
+/// Capabilities a client may support. Known capabilities are defined here, in
+/// this schema, but this is not a closed set: any client can define its own,
+/// additional capabilities.
+struct ClientCapabilities {};
+llvm::json::Value toJSON(const ClientCapabilities &);
+bool fromJSON(const llvm::json::Value &, ClientCapabilities &,
+ 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 ServerCapabilities {
+ bool supportsToolsList = false;
+ bool supportsResourcesList = false;
+ bool supportsResourcesSubscribe = false;
+
+ /// Utilities.
+ bool supportsCompletions = false;
+ bool supportsLogging = false;
+};
+llvm::json::Value toJSON(const ServerCapabilities &);
+bool fromJSON(const llvm::json::Value &, ServerCapabilities &,
+ llvm::json::Path);
+
+/// Initialization
+
+/// This request is sent from the client to the server when it first connects,
+/// asking it to begin initialization.
+struct InitializeParams {
+ /// The latest version of the Model Context Protocol that the client supports.
+ /// The client MAY decide to support older versions as well.
+ std::string protocolVersion;
+
+ ClientCapabilities capabilities;
+
+ Implementation clientInfo;
+};
+llvm::json::Value toJSON(const InitializeParams &);
+bool fromJSON(const llvm::json::Value &, InitializeParams &, llvm::json::Path);
+
+/// After receiving an initialize request from the client, the server sends this
+/// response.
+struct InitializeResult {
+ /// The version of the Model Context Protocol that the server wants to use.
+ /// This may not match the version that the client requested. If the client
+ /// cannot support this version, it MUST disconnect.
+ std::string protocolVersion;
+
+ ServerCapabilities capabilities;
+ Implementation serverInfo;
+
+ /// Instructions describing how to use the server and its features.
+ ///
+ /// This can be used by clients to improve the LLM's understanding of
+ /// available tools, resources, etc. It can be thought of like a "hint" to the
+ /// model. For example, this information MAY be added to the system prompt.
+ std::string instructions = "";
+};
+llvm::json::Value toJSON(const InitializeResult &);
+bool fromJSON(const llvm::json::Value &, InitializeResult &, llvm::json::Path);
+
+/// Special case parameter or result that has no value.
+using Void = std::monostate;
+llvm::json::Value toJSON(const Void &);
+bool fromJSON(const llvm::json::Value &, Void &, llvm::json::Path);
+
+/// The server's response to a `tools/list` request from the client.
+struct ListToolsResult {
+ std::vector<ToolDefinition> tools;
+};
+llvm::json::Value toJSON(const ListToolsResult &);
+bool fromJSON(const llvm::json::Value &, ListToolsResult &, llvm::json::Path);
+
+/// Supported content types, currently only TextContent, but the spec includes
+/// additional content types.
+using ContentBlock = TextContent;
+
+/// Used by the client to invoke a tool provided by the server.
+struct CallToolParams {
+ std::string name;
+ std::optional<llvm::json::Value> arguments;
+};
+llvm::json::Value toJSON(const CallToolParams &);
+bool fromJSON(const llvm::json::Value &, CallToolParams &, llvm::json::Path);
+
+/// The server’s response to a tool call.
+struct CallToolResult {
+ /// A list of content objects that represent the unstructured result of the
+ /// tool call.
+ std::vector<ContentBlock> content;
+
+ /// Whether the tool call ended in an error.
+ ///
+ /// If not set, this is assumed to be false (the call was successful).
+ ///
+ /// Any errors that originate from the tool SHOULD be reported inside the
+ /// result object, with `isError` set to true, not as an MCP protocol-level
+ /// error response. Otherwise, the LLM would not be able to see that an error
+ /// occurred and self-correct.
+ ///
+ /// However, any errors in finding the tool, an error indicating that the
+ /// server does not support tool calls, or any other exceptional conditions,
+ /// should be reported as an MCP error response.
+ bool isError = false;
+
+ /// An optional JSON object that represents the structured result of the tool
+ /// call.
+ std::optional<llvm::json::Value> structuredContent = std::nullopt;
+};
+llvm::json::Value toJSON(const CallToolResult &);
+bool fromJSON(const llvm::json::Value &, CallToolResult &, llvm::json::Path);
+
} // namespace lldb_protocol::mcp
#endif
diff --git a/lldb/include/lldb/Protocol/MCP/Resource.h b/lldb/include/lldb/Protocol/MCP/Resource.h
index 4835d340cd4c6a..158cffc71ea101 100644
--- a/lldb/include/lldb/Protocol/MCP/Resource.h
+++ b/lldb/include/lldb/Protocol/MCP/Resource.h
@@ -20,7 +20,7 @@ class ResourceProvider {
virtual ~ResourceProvider() = default;
virtual std::vector<lldb_protocol::mcp::Resource> GetResources() const = 0;
- virtual llvm::Expected<lldb_protocol::mcp::ResourceResult>
+ virtual llvm::Expected<lldb_protocol::mcp::ReadResourceResult>
ReadResource(llvm::StringRef uri) const = 0;
};
diff --git a/lldb/include/lldb/Protocol/MCP/Server.h b/lldb/include/lldb/Protocol/MCP/Server.h
index 2b9e919329752c..aa5714e45755e5 100644
--- a/lldb/include/lldb/Protocol/MCP/Server.h
+++ b/lldb/include/lldb/Protocol/MCP/Server.h
@@ -58,7 +58,7 @@ class Server : public MCPTransport::MessageHandler {
llvm::Error Run();
protected:
- Capabilities GetCapabilities();
+ ServerCapabilities GetCapabilities();
using RequestHandler =
std::function<llvm::Expected<Response>(const Request &)>;
diff --git a/lldb/include/lldb/Protocol/MCP/Tool.h b/lldb/include/lldb/Protocol/MCP/Tool.h
index 96669d1357166a..6c9f05161f8e7b 100644
--- a/lldb/include/lldb/Protocol/MCP/Tool.h
+++ b/lldb/include/lldb/Protocol/MCP/Tool.h
@@ -10,6 +10,7 @@
#define LLDB_PROTOCOL_MCP_TOOL_H
#include "lldb/Protocol/MCP/Protocol.h"
+#include "llvm/Support/Error.h"
#include "llvm/Support/JSON.h"
#include <string>
@@ -20,7 +21,7 @@ class Tool {
Tool(std::string name, std::string description);
virtual ~Tool() = default;
- virtual llvm::Expected<lldb_protocol::mcp::TextResult>
+ virtual llvm::Expected<lldb_protocol::mcp::CallToolResult>
Call(const lldb_protocol::mcp::ToolArguments &args) = 0;
virtual std::optional<llvm::json::Value> GetSchema() const {
diff --git a/lldb/source/Plugins/Protocol/MCP/Resource.cpp b/lldb/source/Plugins/Protocol/MCP/Resource.cpp
index e94d2cdd65e07a..581424510d4cf4 100644
--- a/lldb/source/Plugins/Protocol/MCP/Resource.cpp
+++ b/lldb/source/Plugins/Protocol/MCP/Resource.cpp
@@ -8,7 +8,6 @@
#include "lldb/Core/Debugger.h"
#include "lldb/Core/Module.h"
#include "lldb/Protocol/MCP/MCPError.h"
-#include "lldb/Target/Platform.h"
using namespace lldb_private;
using namespace lldb_private::mcp;
@@ -124,7 +123,7 @@ DebuggerResourceProvider::GetResources() const {
return resources;
}
-llvm::Expected<lldb_protocol::mcp::ResourceResult>
+llvm::Expected<lldb_protocol::mcp::ReadResourceResult>
DebuggerResourceProvider::ReadResource(llvm::StringRef uri) const {
auto [protocol, path] = uri.split("://");
@@ -161,7 +160,7 @@ DebuggerResourceProvider::ReadResource(llvm::StringRef uri) const {
return ReadDebuggerResource(uri, debugger_idx);
}
-llvm::Expected<lldb_protocol::mcp::ResourceResult>
+llvm::Expected<lldb_protocol::mcp::ReadResourceResult>
DebuggerResourceProvider::ReadDebuggerResource(llvm::StringRef uri,
lldb::user_id_t debugger_id) {
lldb::DebuggerSP debugger_sp = Debugger::FindDebuggerWithID(debugger_id);
@@ -173,17 +172,17 @@ DebuggerResourceProvider::ReadDebuggerResource(llvm::StringRef uri,
debugger_resource.name = debugger_sp->GetInstanceName();
debugger_resource.num_targets = debugger_sp->GetTargetList().GetNumTargets();
- lldb_protocol::mcp::ResourceContents contents;
+ lldb_protocol::mcp::TextResourceContents contents;
contents.uri = uri;
contents.mimeType = kMimeTypeJSON;
contents.text = llvm::formatv("{0}", toJSON(debugger_resource));
- lldb_protocol::mcp::ResourceResult result;
+ lldb_protocol::mcp::ReadResourceResult result;
result.contents.push_back(contents);
return result;
}
-llvm::Expected<lldb_protocol::mcp::ResourceResult>
+llvm::Expected<lldb_protocol::mcp::ReadResourceResult>
DebuggerResourceProvider::ReadTargetResource(llvm::StringRef uri,
lldb::user_id_t debugger_id,
size_t target_idx) {
@@ -209,12 +208,12 @@ DebuggerResourceProvider::ReadTargetResource(llvm::StringRef uri,
if (lldb::PlatformSP platform_sp = target_sp->GetPlatform())
target_resource.platform = platform_sp->GetName();
- lldb_protocol::mcp::ResourceContents contents;
+ lldb_protocol::mcp::TextResourceContents contents;
contents.uri = uri;
contents.mimeType = kMimeTypeJSON;
contents.text = llvm::formatv("{0}", toJSON(target_resource));
- lldb_protocol::mcp::ResourceResult result;
+ lldb_protocol::mcp::ReadResourceResult result;
result.contents.push_back(contents);
return result;
}
diff --git a/lldb/source/Plugins/Protocol/MCP/Resource.h b/lldb/source/Plugins/Protocol/MCP/Resource.h
index e2382a74f796b2..0c6576602905e5 100644
--- a/lldb/source/Plugins/Protocol/MCP/Resource.h
+++ b/lldb/source/Plugins/Protocol/MCP/Resource.h
@@ -11,7 +11,11 @@
#include "lldb/Protocol/MCP/Protocol.h"
#include "lldb/Protocol/MCP/Resource.h"
-#include "lldb/lldb-private.h"
+#include "lldb/lldb-forward.h"
+#include "lldb/lldb-types.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include <cstddef>
#include <vector>
namespace lldb_private::mcp {
@@ -21,9 +25,8 @@ class DebuggerResourceProvider : public lldb_protocol::mcp::ResourceProvider {
using ResourceProvider::ResourceProvider;
virtual ~DebuggerResourceProvider() = default;
- virtual std::vector<lldb_protocol::mcp::Resource>
- GetResources() const override;
- virtual llvm::Expected<lldb_protocol::mcp::ResourceResult>
+ std::vector<lldb_protocol::mcp::Resource> GetResources() const override;
+ llvm::Expected<lldb_protocol::mcp::ReadResourceResult>
ReadResource(llvm::StringRef uri) const override;
private:
@@ -31,9 +34,9 @@ class DebuggerResourceProvider : public lldb_protocol::mcp::ResourceProvider {
static lldb_protocol::mcp::Resource GetTargetResource(size_t target_idx,
Target &target);
- static llvm::Expected<lldb_protocol::mcp::ResourceResult>
+ static llvm::Expected<lldb_protocol::mcp::ReadResourceResult>
ReadDebuggerResource(llvm::StringRef uri, lldb::user_id_t debugger_id);
- static llvm::Expected<lldb_protocol::mcp::ResourceResult>
+ static llvm::Expected<lldb_protocol::mcp::ReadResourceResult>
ReadTargetResource(llvm::StringRef uri, lldb::user_id_t debugger_id,
size_t target_idx);
};
diff --git a/lldb/source/Plugins/Protocol/MCP/Tool.cpp b/lldb/source/Plugins/Protocol/MCP/Tool.cpp
index 143470702a6fdc..2f451bf76e81de 100644
--- a/lldb/source/Plugins/Protocol/MCP/Tool.cpp
+++ b/lldb/source/Plugins/Protocol/MCP/Tool.cpp
@@ -7,9 +7,9 @@
//===----------------------------------------------------------------------===//
#include "Tool.h"
-#include "lldb/Core/Module.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandReturnObject.h"
+#include "lldb/Protocol/MCP/Protocol.h"
using namespace lldb_private;
using namespace lldb_protocol;
@@ -29,10 +29,10 @@ bool fromJSON(const llvm::json::Value &V, CommandToolArguments &A,
O.mapOptional("arguments", A.arguments);
}
-/// Helper function to create a TextResult from a string output.
-static lldb_protocol::mcp::TextResult createTextResult(std::string output,
- bool is_error = false) {
- lldb_protocol::mcp::TextResult text_result;
+/// Helper function to create a CallToolResult from a string output.
+static lldb_protocol::mcp::CallToolResult
+createTextResult(std::string output, bool is_error = false) {
+ lldb_protocol::mcp::CallToolResult text_result;
text_result.content.emplace_back(
lldb_protocol::mcp::TextContent{{std::move(output)}});
text_result.isError = is_error;
@@ -41,7 +41,7 @@ static lldb_protocol::mcp::TextResult createTextResult(std::string output,
} // namespace
-llvm::Expected<lldb_protocol::mcp::TextResult>
+llvm::Expected<lldb_protocol::mcp::CallToolResult>
CommandTool::Call(const lldb_protocol::mcp::ToolArguments &args) {
if (!std::holds_alternative<json::Value>(args))
return createStringError("CommandTool requires arguments");
diff --git a/lldb/source/Plugins/Protocol/MCP/Tool.h b/lldb/source/Plugins/Protocol/MCP/Tool.h
index b7b1756eb38d7f..1886525b9168f5 100644
--- a/lldb/source/Plugins/Protocol/MCP/Tool.h
+++ b/lldb/source/Plugins/Protocol/MCP/Tool.h
@@ -9,11 +9,11 @@
#ifndef LLDB_PLUGINS_PROTOCOL_MCP_TOOL_H
#define LLDB_PLUGINS_PROTOCOL_MCP_TOOL_H
-#include "lldb/Core/Debugger.h"
#include "lldb/Protocol/MCP/Protocol.h"
#include "lldb/Protocol/MCP/Tool.h"
+#include "llvm/Support/Error.h"
#include "llvm/Support/JSON.h"
-#include <string>
+#include <optional>
namespace lldb_private::mcp {
@@ -22,10 +22,10 @@ class CommandTool : public lldb_protocol::mcp::Tool {
using lldb_protocol::mcp::Tool::Tool;
~CommandTool() = default;
- virtual llvm::Expected<lldb_protocol::mcp::TextResult>
+ llvm::Expected<lldb_protocol::mcp::CallToolResult>
Call(const lldb_protocol::mcp::ToolArguments &args) override;
- virtual std::optional<llvm::json::Value> GetSchema() const override;
+ std::optional<llvm::json::Value> GetSchema() const override;
};
} // namespace lldb_private::mcp
diff --git a/lldb/source/Protocol/MCP/Protocol.cpp b/lldb/source/Protocol/MCP/Protocol.cpp
index 65ddfaee70160b..0988f456adc263 100644
--- a/lldb/source/Protocol/MCP/Protocol.cpp
+++ b/lldb/source/Protocol/MCP/Protocol.cpp
@@ -167,32 +167,6 @@ bool operator==(const Notification &a, const Notification &b) {
return a.method == b.method && a.params == b.params;
}
-llvm::json::Value toJSON(const ToolCapability &TC) {
- return llvm::json::Object{{"listChanged", TC.listChanged}};
-}
-
-bool fromJSON(const llvm::json::Value &V, ToolCapability &TC,
- llvm::json::Path P) {
- llvm::json::ObjectMapper O(V, P);
- return O && O.map("listChanged", TC.listChanged);
-}
-
-llvm::json::Value toJSON(const ResourceCapability &RC) {
- return llvm::json::Object{{"listChanged", RC.listChanged},
- {"subscribe", RC.subscribe}};
-}
-
-bool fromJSON(const llvm::json::Value &V, ResourceCapability &RC,
- llvm::json::Path P) {
- llvm::json::ObjectMapper O(V, P);
- return O && O.map("listChanged", RC.listChanged) &&
- O.map("subscribe", RC.subscribe);
-}
-
-llvm::json::Value toJSON(const Capabilities &C) {
- return llvm::json::Object{{"tools", C.tools}, {"resources", C.resources}};
-}
-
bool fromJSON(const llvm::json::Value &V, Resource &R, llvm::json::Path P) {
llvm::json::ObjectMapper O(V, P);
return O && O.map("uri", R.uri) && O.map("name", R.name) &&
@@ -209,30 +183,25 @@ llvm::json::Value toJSON(const Resource &R) {
return Result;
}
-bool fromJSON(const llvm::json::Value &V, Capabilities &C, llvm::json::Path P) {
- llvm::json::ObjectMapper O(V, P);
- return O && O.map("tools", C.tools);
-}
-
-llvm::json::Value toJSON(const ResourceContents &RC) {
+llvm::json::Value toJSON(const TextResourceContents &RC) {
llvm::json::Object Result{{"uri", RC.uri}, {"text", RC.text}};
if (!RC.mimeType.empty())
Result.insert({"mimeType", RC.mimeType});
return Result;
}
-bool fromJSON(const llvm::json::Value &V, ResourceContents &RC,
+bool fromJSON(const llvm::json::Value &V, TextResourceContents &RC,
llvm::json::Path P) {
llvm::json::ObjectMapper O(V, P);
return O && O.map("uri", RC.uri) && O.map("text", RC.text) &&
O.mapOptional("mimeType", RC.mimeType);
}
-llvm::json::Value toJSON(const ResourceResult &RR) {
+llvm::json::Value toJSON(const ReadResourceResult &RR) {
return llvm::json::Object{{"contents", RR.contents}};
}
-bool fromJSON(const llvm::json::Value &V, ResourceResult &RR,
+bool fromJSON(const llvm::json::Value &V, ReadResourceResult &RR,
llvm::json::Path P) {
llvm::json::ObjectMapper O(V, P);
return O && O.map("contents", RR.contents);
@@ -247,15 +216,6 @@ bool fromJSON(const llvm::json::Value &V, TextContent &TC, llvm::json::Path P) {
return O && O.map("text", TC.text);
}
-llvm::json::Value toJSON(const TextResult &TR) {
- return llvm::json::Object{{"content", TR.content}, {"isError", TR.isError}};
-}
-
-bool fromJSON(const llvm::json::Value &V, TextResult &TR, llvm::json::Path P) {
- llvm::json::ObjectMapper O(V, P);
- return O && O.map("content", TR.content) && O.map("isError", TR.isError);
-}
-
llvm::json::Value toJSON(const ToolDefinition &TD) {
llvm::json::Object Result{{"name", TD.name}};
if (!TD.description.empty())
@@ -325,4 +285,159 @@ bool fromJSON(const llvm::json::Value &V, Message &M, llvm::json::Path P) {
return false;
}
+json::Value toJSON(const Implementation &I) {
+ json::Object result{{"name", I.name}, {"version", I.version}};
+
+ if (!I.title.empty())
+ result.insert({"title", I.title});
+
+ return result;
+}
+
+bool fromJSON(const json::Value &V, Implementation &I, json::Path P) {
+ json::ObjectMapper O(V, P);
+ return O && O.map("name", I.name) && O.mapOptional("title", I.title) &&
+ O.mapOptional("version", I.version);
+}
+
+json::Value toJSON(const ClientCapabilities &C) { return json::Object{}; }
+
+bool fromJSON(const json::Value &, ClientCapabilities &, json::Path) {
+ return true;
+}
+
+json::Value toJSON(const ServerCapabilities &C) {
+ json::Object result{};
+
+ if (C.supportsToolsList)
+ result.insert({"tools", json::Object{{"listChanged", true}}});
+
+ if (C.supportsResourcesList || C.supportsResourcesSubscribe) {
+ json::Object resources;
+ if (C.supportsResourcesList)
+ resources.insert({"listChanged", true});
+ if (C.supportsResourcesSubscribe)
+ resources.insert({"subscribe", true});
+ result.insert({"resources", std::move(resources)});
+ }
+
+ if (C.supportsCompletions)
+ result.insert({"completions", json::Object{}});
+
+ if (C.supportsLogging)
+ result.insert({"logging", json::Object{}});
+
+ return result;
+}
+
+bool fromJSON(const json::Value &V, ServerCapabilities &C, json::Path P) {
+ const json::Object *O = V.getAsObject();
+ if (!O) {
+ P.report("expected object");
+ return false;
+ }
+
+ if (O->find("tools") != O->end())
+ C.supportsToolsList = true;
+
+ return true;
+}
+
+json::Value toJSON(const InitializeParams &P) {
+ return json::Object{
+ {"protocolVersion", P.protocolVersion},
+ {"capabilities", P.capabilities},
+ {"clientInfo", P.clientInfo},
+ };
+}
+
+bool fromJSON(const json::Value &V, InitializeParams &I, json::Path P) {
+ json::ObjectMapper O(V, P);
+ return O && O.map("protocolVersion", I.protocolVersion) &&
+ O.map("capabilities", I.capabilities) &&
+ O.map("clientInfo", I.clientInfo);
+}
+
+json::Value toJSON(const InitializeResult &R) {
+ json::Object result{{"protocolVersion", R.protocolVersion},
+ {"capabilities", R.capabilities},
+ {"serverInfo", R.serverInfo}};
+
+ if (!R.instructions.empty())
+ result.insert({"instructions", R.instructions});
+
+ return result;
+}
+
+bool fromJSON(const json::Value &V, InitializeResult &R, json::Path P) {
+ json::ObjectMapper O(V, P);
+ return O && O.map("protocolVersion", R.protocolVersion) &&
+ O.map("capabilities", R.capabilities) &&
+ O.map("serverInfo", R.serverInfo) &&
+ O.mapOptional("instructions", R.instructions);
+}
+
+json::Value toJSON(const ListToolsResult &R) {
+ return json::Object{{"tools", R.tools}};
+}
+
+bool fromJSON(const json::Value &V, ListToolsResult &R, json::Path P) {
+ json::ObjectMapper O(V, P);
+ return O && O.map("tools", R.tools);
+}
+
+json::Value toJSON(const CallToolResult &R) {
+ json::Object result{{"content", R.content}};
+
+ if (R.isError)
+ result.insert({"isError", R.isError});
+ if (R.structuredContent)
+ result.insert({"structuredContent", *R.structuredContent});
+
+ return result;
+}
+
+bool fromJSON(const json::Value &V, CallToolResult &R, json::Path P) {
+ json::ObjectMapper O(V, P);
+ return O && O.map("content", R.content) &&
+ O.mapOptional("isError", R.isError) &&
+ mapRaw(V, "structuredContent", R.structuredContent, P);
+}
+
+json::Value toJSON(const CallToolParams &R) {
+ json::Object result{{"name", R.name}};
+
+ if (R.arguments)
+ result.insert({"arguments", *R.arguments});
+
+ return result;
+}
+
+bool fromJSON(const json::Value &V, CallToolParams &R, json::Path P) {
+ json::ObjectMapper O(V, P);
+ return O && O.map("name", R.name) && mapRaw(V, "arguments", R.arguments, P);
+}
+
+json::Value toJSON(const ReadResourceParams &R) {
+ return json::Object{{"uri", R.uri}};
+}
+
+bool fromJSON(const json::Value &V, ReadResourceParams &R, json::Path P) {
+ json::ObjectMapper O(V, P);
+ return O && O.map("uri", R.uri);
+}
+
+json::Value toJSON(const ListResourcesResult &R) {
+ return json::Object{{"resources", R.resources}};
+}
+
+bool fromJSON(const json::Value &V, ListResourcesResult &R, json::Path P) {
+ json::ObjectMapper O(V, P);
+ return O && O.map("resources", R.resources);
+}
+
+json::Value toJSON(const Void &R) { return json::Object{}; }
+
+bool fromJSON(const json::Value &V, Void &R, json::Path P) { return true; }
+
} // namespace lldb_protocol::mcp
diff --git a/lldb/source/Protocol/MCP/Server.cpp b/lldb/source/Protocol/MCP/Server.cpp
index c1a6026b11090a..63c2d01d17922a 100644
--- a/lldb/source/Protocol/MCP/Server.cpp
+++ b/lldb/source/Protocol/MCP/Server.cpp
@@ -8,6 +8,8 @@
#include "lldb/Protocol/MCP/Server.h"
#include "lldb/Protocol/MCP/MCPError.h"
+#include "lldb/Protocol/MCP/Protocol.h"
+#include "llvm/Support/JSON.h"
using namespace lldb_protocol::mcp;
using namespace llvm;
@@ -79,22 +81,23 @@ void Server::AddNotificationHandler(llvm::StringRef method,
llvm::Expected<Response> Server::InitializeHandler(const Request &request) {
Response response;
- response.result = llvm::json::Object{
- {"protocolVersion", mcp::kProtocolVersion},
- {"capabilities", GetCapabilities()},
- {"serverInfo",
- llvm::json::Object{{"name", m_name}, {"version", m_version}}}};
+ InitializeResult result;
+ result.protocolVersion = mcp::kProtocolVersion;
+ result.capabilities = GetCapabilities();
+ result.serverInfo.name = m_name;
+ result.serverInfo.version = m_version;
+ response.result = std::move(result);
return response;
}
llvm::Expected<Response> Server::ToolsListHandler(const Request &request) {
Response response;
- llvm::json::Array tools;
+ ListToolsResult result;
for (const auto &tool : m_tools)
- tools.emplace_back(toJSON(tool.second->GetDefinition()));
+ result.tools.emplace_back(tool.second->GetDefinition());
- response.result = llvm::json::Object{{"tools", std::move(tools)}};
+ response.result = std::move(result);
return response;
}
@@ -104,16 +107,12 @@ llvm::Expected<Response> Server::ToolsCallHandler(const Request &request) {
if (!request.params)
return llvm::createStringError("no tool parameters");
+ CallToolParams params;
+ json::Path::Root root("params");
+ if (!fromJSON(request.params, params, root))
+ return root.getError();
- const json::Object *param_obj = request.params->getAsObject();
- if (!param_obj)
- return llvm::createStringError("no tool parameters");
-
- const json::Value *name = param_obj->get("name");
- if (!name)
- return llvm::createStringError("no tool name");
-
- llvm::StringRef tool_name = name->getAsString().value_or("");
+ llvm::StringRef tool_name = params.name;
if (tool_name.empty())
return llvm::createStringError("no tool name");
@@ -122,10 +121,10 @@ llvm::Expected<Response> Server::ToolsCallHandler(const Request &request) {
return llvm::createStringError(llvm::formatv("no tool \"{0}\"", tool_name));
ToolArguments tool_args;
- if (const json::Value *args = param_obj->get("arguments"))
- tool_args = *args;
+ if (params.arguments)
+ tool_args = *params.arguments;
- llvm::Expected<TextResult> text_result = it->second->Call(tool_args);
+ llvm::Expected<CallToolResult> text_result = it->second->Call(tool_args);
if (!text_result)
return text_result.takeError();
@@ -137,14 +136,13 @@ llvm::Expected<Response> Server::ToolsCallHandler(const Request &request) {
llvm::Expected<Response> Server::ResourcesListHandler(const Request &request) {
Response response;
- llvm::json::Array resources;
-
+ ListResourcesResult result;
for (std::unique_ptr<ResourceProvider> &resource_provider_up :
- m_resource_providers) {
+ m_resource_providers)
for (const Resource &resource : resource_provider_up->GetResources())
- resources.push_back(resource);
- }
- response.result = llvm::json::Object{{"resources", std::move(resources)}};
+ result.resources.push_back(resource);
+
+ response.result = std::move(result);
return response;
}
@@ -155,21 +153,18 @@ llvm::Expected<Response> Server::ResourcesReadHandler(const Request &request) {
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");
+ ReadResourceParams params;
+ json::Path::Root root("params");
+ if (!fromJSON(request.params, params, root))
+ return root.getError();
- llvm::StringRef uri_str = uri->getAsString().value_or("");
+ llvm::StringRef uri_str = params.uri;
if (uri_str.empty())
return llvm::createStringError("no resource uri");
for (std::unique_ptr<ResourceProvider> &resource_provider_up :
m_resource_providers) {
- llvm::Expected<ResourceResult> result =
+ llvm::Expected<ReadResourceResult> result =
resource_provider_up->ReadResource(uri_str);
if (result.errorIsA<UnsupportedURI>()) {
llvm::consumeError(result.takeError());
@@ -188,12 +183,12 @@ llvm::Expected<Response> Server::ResourcesReadHandler(const Request &request) {
MCPError::kResourceNotFound);
}
-Capabilities Server::GetCapabilities() {
- lldb_protocol::mcp::Capabilities capabilities;
- capabilities.tools.listChanged = true;
+ServerCapabilities Server::GetCapabilities() {
+ lldb_protocol::mcp::ServerCapabilities capabilities;
+ capabilities.supportsToolsList = true;
// FIXME: Support sending notifications when a debugger/target are
// added/removed.
- capabilities.resources.listChanged = false;
+ capabilities.supportsResourcesList = false;
return capabilities;
}
diff --git a/lldb/unittests/Protocol/ProtocolMCPServerTest.cpp b/lldb/unittests/Protocol/ProtocolMCPServerTest.cpp
index 393748cdc65591..9fa446133d46f7 100644
--- a/lldb/unittests/Protocol/ProtocolMCPServerTest.cpp
+++ b/lldb/unittests/Protocol/ProtocolMCPServerTest.cpp
@@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//
+#include "ProtocolMCPTestUtilities.h"
#include "TestingSupport/Host/JSONTransportTestUtilities.h"
#include "TestingSupport/Host/PipeTestUtilities.h"
#include "TestingSupport/SubsystemRAII.h"
@@ -20,6 +21,7 @@
#include "lldb/Protocol/MCP/Resource.h"
#include "lldb/Protocol/MCP/Server.h"
#include "lldb/Protocol/MCP/Tool.h"
+#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/JSON.h"
#include "llvm/Testing/Support/Error.h"
@@ -27,13 +29,11 @@
#include "gtest/gtest.h"
#include <chrono>
#include <condition_variable>
-#include <mutex>
using namespace llvm;
using namespace lldb;
using namespace lldb_private;
using namespace lldb_protocol::mcp;
-using testing::_;
namespace {
class TestMCPTransport final : public MCPTransport {
@@ -60,7 +60,7 @@ class TestTool : public Tool {
public:
using Tool::Tool;
- llvm::Expected<TextResult> Call(const ToolArguments &args) override {
+ llvm::Expected<CallToolResult> Call(const ToolArguments &args) override {
std::string argument;
if (const json::Object *args_obj =
std::get<json::Value>(args).getAsObject()) {
@@ -69,7 +69,7 @@ class TestTool : public Tool {
}
}
- TextResult text_result;
+ CallToolResult text_result;
text_result.content.emplace_back(TextContent{{argument}});
return text_result;
}
@@ -91,17 +91,17 @@ class TestResourceProvider : public ResourceProvider {
return resources;
}
- llvm::Expected<ResourceResult>
+ llvm::Expected<ReadResourceResult>
ReadResource(llvm::StringRef uri) const override {
if (uri != "lldb://foo/bar")
return llvm::make_error<UnsupportedURI>(uri.str());
- ResourceContents contents;
+ TextResourceContents contents;
contents.uri = "lldb://foo/bar";
contents.mimeType = "application/json";
contents.text = "foobar";
- ResourceResult result;
+ ReadResourceResult result;
result.contents.push_back(contents);
return result;
}
@@ -112,7 +112,7 @@ class ErrorTool : public Tool {
public:
using Tool::Tool;
- llvm::Expected<TextResult> Call(const ToolArguments &args) override {
+ llvm::Expected<CallToolResult> Call(const ToolArguments &args) override {
return llvm::createStringError("error");
}
};
@@ -122,8 +122,8 @@ class FailTool : public Tool {
public:
using Tool::Tool;
- llvm::Expected<TextResult> Call(const ToolArguments &args) override {
- TextResult text_result;
+ llvm::Expected<CallToolResult> Call(const ToolArguments &args) override {
+ CallToolResult text_result;
text_result.content.emplace_back(TextContent{{"failed"}});
text_result.isError = true;
return text_result;
@@ -146,6 +146,8 @@ class ProtocolServerMCPTest : public PipePairTest {
return transport_up->Write(*value);
}
+ llvm::Error Write(json::Value value) { return transport_up->Write(value); }
+
/// Run the transport MainLoop and return any messages received.
llvm::Error
Run(std::chrono::milliseconds timeout = std::chrono::milliseconds(200)) {
@@ -182,37 +184,43 @@ class ProtocolServerMCPTest : public PipePairTest {
}
};
+template <typename T>
+Request make_request(StringLiteral method, T &¶ms, Id id = 1) {
+ return Request{id, method.str(), toJSON(std::forward<T>(params))};
+}
+
+template <typename T> Response make_response(T &&result, Id id = 1) {
+ return Response{id, std::forward<T>(result)};
+}
+
} // namespace
TEST_F(ProtocolServerMCPTest, Initialization) {
- llvm::StringLiteral request =
- R"json({"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"lldb-unit","version":"0.1.0"}},"jsonrpc":"2.0","id":1})json";
- llvm::StringLiteral response =
- R"json({"id":1,"jsonrpc":"2.0","result":{"capabilities":{"resources":{"listChanged":false,"subscribe":false},"tools":{"listChanged":true}},"protocolVersion":"2024-11-05","serverInfo":{"name":"lldb-mcp","version":"0.1.0"}}})json";
+ Request request = make_request(
+ "initialize", InitializeParams{/*protocolVersion=*/"2024-11-05",
+ /*capabilities=*/{},
+ /*clientInfo=*/{"lldb-unit", "0.1.0"}});
+ Response response = make_response(
+ InitializeResult{/*protocolVersion=*/"2024-11-05",
+ /*capabilities=*/{/*supportsToolsList=*/true},
+ /*serverInfo=*/{"lldb-mcp", "0.1.0"}});
ASSERT_THAT_ERROR(Write(request), Succeeded());
- llvm::Expected<Response> expected_resp = json::parse<Response>(response);
- ASSERT_THAT_EXPECTED(expected_resp, llvm::Succeeded());
- EXPECT_CALL(message_handler, Received(*expected_resp));
+ EXPECT_CALL(message_handler, Received(response));
EXPECT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(ProtocolServerMCPTest, ToolsList) {
server_up->AddTool(std::make_unique<TestTool>("test", "test tool"));
- llvm::StringLiteral request =
- R"json({"method":"tools/list","params":{},"jsonrpc":"2.0","id":"one"})json";
+ Request request = make_request("tools/list", Void{}, /*id=*/"one");
ToolDefinition test_tool;
test_tool.name = "test";
test_tool.description = "test tool";
test_tool.inputSchema = json::Object{{"type", "object"}};
- Response response;
- response.id = "one";
- response.result = json::Object{
- {"tools", json::Array{std::move(test_tool)}},
- };
+ Response response = make_response(ListToolsResult{{test_tool}}, /*id=*/"one");
ASSERT_THAT_ERROR(Write(request), llvm::Succeeded());
EXPECT_CALL(message_handler, Received(response));
@@ -222,60 +230,61 @@ TEST_F(ProtocolServerMCPTest, ToolsList) {
TEST_F(ProtocolServerMCPTest, ResourcesList) {
server_up->AddResourceProvider(std::make_unique<TestResourceProvider>());
- llvm::StringLiteral request =
- R"json({"method":"resources/list","params":{},"jsonrpc":"2.0","id":2})json";
- llvm::StringLiteral response =
- R"json({"id":2,"jsonrpc":"2.0","result":{"resources":[{"description":"description","mimeType":"application/json","name":"name","uri":"lldb://foo/bar"}]}})json";
+ Request request = make_request("resources/list", Void{});
+ Response response = make_response(ListResourcesResult{
+ {{/*uri=*/"lldb://foo/bar", /*name=*/"name",
+ /*description=*/"description", /*mimeType=*/"application/json"}}});
ASSERT_THAT_ERROR(Write(request), llvm::Succeeded());
- llvm::Expected<Response> expected_resp = json::parse<Response>(response);
- ASSERT_THAT_EXPECTED(expected_resp, llvm::Succeeded());
- EXPECT_CALL(message_handler, Received(*expected_resp));
+ EXPECT_CALL(message_handler, Received(response));
EXPECT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(ProtocolServerMCPTest, ToolsCall) {
server_up->AddTool(std::make_unique<TestTool>("test", "test tool"));
- llvm::StringLiteral request =
- R"json({"method":"tools/call","params":{"name":"test","arguments":{"arguments":"foo","debugger_id":0}},"jsonrpc":"2.0","id":11})json";
- llvm::StringLiteral response =
- R"json({"id":11,"jsonrpc":"2.0","result":{"content":[{"text":"foo","type":"text"}],"isError":false}})json";
+ Request request = make_request(
+ "tools/call", CallToolParams{/*name=*/"test", /*arguments=*/json::Object{
+ {"arguments", "foo"},
+ {"debugger_id", 0},
+ }});
+ Response response = make_response(CallToolResult{{{/*text=*/"foo"}}});
ASSERT_THAT_ERROR(Write(request), llvm::Succeeded());
- llvm::Expected<Response> expected_resp = json::parse<Response>(response);
- ASSERT_THAT_EXPECTED(expected_resp, llvm::Succeeded());
- EXPECT_CALL(message_handler, Received(*expected_resp));
+ EXPECT_CALL(message_handler, Received(response));
EXPECT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(ProtocolServerMCPTest, ToolsCallError) {
server_up->AddTool(std::make_unique<ErrorTool>("error", "error tool"));
- llvm::StringLiteral request =
- R"json({"method":"tools/call","params":{"name":"error","arguments":{"arguments":"foo","debugger_id":0}},"jsonrpc":"2.0","id":11})json";
- llvm::StringLiteral response =
- R"json({"error":{"code":-32603,"message":"error"},"id":11,"jsonrpc":"2.0"})json";
+ Request request = make_request(
+ "tools/call", CallToolParams{/*name=*/"error", /*arguments=*/json::Object{
+ {"arguments", "foo"},
+ {"debugger_id", 0},
+ }});
+ Response response =
+ make_response(lldb_protocol::mcp::Error{eErrorCodeInternalError,
+ /*message=*/"error"});
ASSERT_THAT_ERROR(Write(request), llvm::Succeeded());
- llvm::Expected<Response> expected_resp = json::parse<Response>(response);
- ASSERT_THAT_EXPECTED(expected_resp, llvm::Succeeded());
- EXPECT_CALL(message_handler, Received(*expected_resp));
+ EXPECT_CALL(message_handler, Received(response));
EXPECT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(ProtocolServerMCPTest, ToolsCallFail) {
server_up->AddTool(std::make_unique<FailTool>("fail", "fail tool"));
- llvm::StringLiteral request =
- R"json({"method":"tools/call","params":{"name":"fail","arguments":{"arguments":"foo","debugger_id":0}},"jsonrpc":"2.0","id":11})json";
- llvm::StringLiteral response =
- R"json({"id":11,"jsonrpc":"2.0","result":{"content":[{"text":"failed","type":"text"}],"isError":true}})json";
+ Request request = make_request(
+ "tools/call", CallToolParams{/*name=*/"fail", /*arguments=*/json::Object{
+ {"arguments", "foo"},
+ {"debugger_id", 0},
+ }});
+ Response response =
+ make_response(CallToolResult{{{/*text=*/"failed"}}, /*isError=*/true});
ASSERT_THAT_ERROR(Write(request), llvm::Succeeded());
- llvm::Expected<Response> expected_resp = json::parse<Response>(response);
- ASSERT_THAT_EXPECTED(expected_resp, llvm::Succeeded());
- EXPECT_CALL(message_handler, Received(*expected_resp));
+ EXPECT_CALL(message_handler, Received(response));
EXPECT_THAT_ERROR(Run(), Succeeded());
}
diff --git a/lldb/unittests/Protocol/ProtocolMCPTest.cpp b/lldb/unittests/Protocol/ProtocolMCPTest.cpp
index ea19922522ffe0..396e361e873fe3 100644
--- a/lldb/unittests/Protocol/ProtocolMCPTest.cpp
+++ b/lldb/unittests/Protocol/ProtocolMCPTest.cpp
@@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//
+#include "ProtocolMCPTestUtilities.h"
#include "TestingSupport/TestUtilities.h"
#include "lldb/Protocol/MCP/Protocol.h"
#include "llvm/Testing/Support/Error.h"
@@ -54,31 +55,16 @@ TEST(ProtocolMCPTest, Notification) {
EXPECT_EQ(notification.params, deserialized_notification->params);
}
-TEST(ProtocolMCPTest, ToolCapability) {
- ToolCapability tool_capability;
- tool_capability.listChanged = true;
+TEST(ProtocolMCPTest, ServerCapabilities) {
+ ServerCapabilities capabilities;
+ capabilities.supportsToolsList = true;
- llvm::Expected<ToolCapability> deserialized_tool_capability =
- roundtripJSON(tool_capability);
- ASSERT_THAT_EXPECTED(deserialized_tool_capability, llvm::Succeeded());
-
- EXPECT_EQ(tool_capability.listChanged,
- deserialized_tool_capability->listChanged);
-}
-
-TEST(ProtocolMCPTest, Capabilities) {
- ToolCapability tool_capability;
- tool_capability.listChanged = true;
-
- Capabilities capabilities;
- capabilities.tools = tool_capability;
-
- llvm::Expected<Capabilities> deserialized_capabilities =
+ llvm::Expected<ServerCapabilities> deserialized_capabilities =
roundtripJSON(capabilities);
ASSERT_THAT_EXPECTED(deserialized_capabilities, llvm::Succeeded());
- EXPECT_EQ(capabilities.tools.listChanged,
- deserialized_capabilities->tools.listChanged);
+ EXPECT_EQ(capabilities.supportsToolsList,
+ deserialized_capabilities->supportsToolsList);
}
TEST(ProtocolMCPTest, TextContent) {
@@ -92,18 +78,18 @@ TEST(ProtocolMCPTest, TextContent) {
EXPECT_EQ(text_content.text, deserialized_text_content->text);
}
-TEST(ProtocolMCPTest, TextResult) {
+TEST(ProtocolMCPTest, CallToolResult) {
TextContent text_content1;
text_content1.text = "Text 1";
TextContent text_content2;
text_content2.text = "Text 2";
- TextResult text_result;
+ CallToolResult text_result;
text_result.content = {text_content1, text_content2};
text_result.isError = true;
- llvm::Expected<TextResult> deserialized_text_result =
+ llvm::Expected<CallToolResult> deserialized_text_result =
roundtripJSON(text_result);
ASSERT_THAT_EXPECTED(deserialized_text_result, llvm::Succeeded());
@@ -237,13 +223,13 @@ TEST(ProtocolMCPTest, ResourceWithoutOptionals) {
EXPECT_TRUE(deserialized_resource->mimeType.empty());
}
-TEST(ProtocolMCPTest, ResourceContents) {
- ResourceContents contents;
+TEST(ProtocolMCPTest, TextResourceContents) {
+ TextResourceContents contents;
contents.uri = "resource://example/content";
contents.text = "This is the content of the resource";
contents.mimeType = "text/plain";
- llvm::Expected<ResourceContents> deserialized_contents =
+ llvm::Expected<TextResourceContents> deserialized_contents =
roundtripJSON(contents);
ASSERT_THAT_EXPECTED(deserialized_contents, llvm::Succeeded());
@@ -252,12 +238,12 @@ TEST(ProtocolMCPTest, ResourceContents) {
EXPECT_EQ(contents.mimeType, deserialized_contents->mimeType);
}
-TEST(ProtocolMCPTest, ResourceContentsWithoutMimeType) {
- ResourceContents contents;
+TEST(ProtocolMCPTest, TextResourceContentsWithoutMimeType) {
+ TextResourceContents contents;
contents.uri = "resource://example/content-no-mime";
contents.text = "Content without mime type specified";
- llvm::Expected<ResourceContents> deserialized_contents =
+ llvm::Expected<TextResourceContents> deserialized_contents =
roundtripJSON(contents);
ASSERT_THAT_EXPECTED(deserialized_contents, llvm::Succeeded());
@@ -266,21 +252,22 @@ TEST(ProtocolMCPTest, ResourceContentsWithoutMimeType) {
EXPECT_TRUE(deserialized_contents->mimeType.empty());
}
-TEST(ProtocolMCPTest, ResourceResult) {
- ResourceContents contents1;
+TEST(ProtocolMCPTest, ReadResourceResult) {
+ TextResourceContents contents1;
contents1.uri = "resource://example/content1";
contents1.text = "First resource content";
contents1.mimeType = "text/plain";
- ResourceContents contents2;
+ TextResourceContents contents2;
contents2.uri = "resource://example/content2";
contents2.text = "Second resource content";
contents2.mimeType = "application/json";
- ResourceResult result;
+ ReadResourceResult result;
result.contents = {contents1, contents2};
- llvm::Expected<ResourceResult> deserialized_result = roundtripJSON(result);
+ llvm::Expected<ReadResourceResult> deserialized_result =
+ roundtripJSON(result);
ASSERT_THAT_EXPECTED(deserialized_result, llvm::Succeeded());
ASSERT_EQ(result.contents.size(), deserialized_result->contents.size());
@@ -296,10 +283,11 @@ TEST(ProtocolMCPTest, ResourceResult) {
deserialized_result->contents[1].mimeType);
}
-TEST(ProtocolMCPTest, ResourceResultEmpty) {
- ResourceResult result;
+TEST(ProtocolMCPTest, ReadResourceResultEmpty) {
+ ReadResourceResult result;
- llvm::Expected<ResourceResult> deserialized_result = roundtripJSON(result);
+ llvm::Expected<ReadResourceResult> deserialized_result =
+ roundtripJSON(result);
ASSERT_THAT_EXPECTED(deserialized_result, llvm::Succeeded());
EXPECT_TRUE(deserialized_result->contents.empty());
diff --git a/lldb/unittests/Protocol/ProtocolMCPTestUtilities.h b/lldb/unittests/Protocol/ProtocolMCPTestUtilities.h
new file mode 100644
index 00000000000000..f8a14f4be03c91
--- /dev/null
+++ b/lldb/unittests/Protocol/ProtocolMCPTestUtilities.h
@@ -0,0 +1,40 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_UNITTESTS_PROTOCOL_PROTOCOLMCPTESTUTILITIES_H
+#define LLDB_UNITTESTS_PROTOCOL_PROTOCOLMCPTESTUTILITIES_H
+
+#include "lldb/Protocol/MCP/Protocol.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/JSON.h" // IWYU pragma: keep
+#include "gtest/gtest.h" // IWYU pragma: keep
+#include <ostream>
+#include <variant>
+
+namespace lldb_protocol::mcp {
+
+inline void PrintTo(const Request &req, std::ostream *os) {
+ *os << llvm::formatv("{0}", toJSON(req)).str();
+}
+
+inline void PrintTo(const Response &resp, std::ostream *os) {
+ *os << llvm::formatv("{0}", toJSON(resp)).str();
+}
+
+inline void PrintTo(const Notification ¬e, std::ostream *os) {
+ *os << llvm::formatv("{0}", toJSON(note)).str();
+}
+
+inline void PrintTo(const Message &message, std::ostream *os) {
+ return std::visit([os](auto &&message) { return PrintTo(message, os); },
+ message);
+}
+
+} // namespace lldb_protocol::mcp
+
+#endif
More information about the lldb-commits
mailing list