444 lines · cpp
1//===- Protocol.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-exception6//7//===----------------------------------------------------------------------===//8 9#include "lldb/Protocol/MCP/Protocol.h"10#include "llvm/Support/ErrorHandling.h"11#include "llvm/Support/JSON.h"12 13using namespace llvm;14 15namespace lldb_protocol::mcp {16 17static bool mapRaw(const json::Value &Params, StringLiteral Prop,18 std::optional<json::Value> &V, json::Path P) {19 const auto *O = Params.getAsObject();20 if (!O) {21 P.report("expected object");22 return false;23 }24 const json::Value *E = O->get(Prop);25 if (E)26 V = std::move(*E);27 return true;28}29 30static llvm::json::Value toJSON(const Id &Id) {31 if (const int64_t *I = std::get_if<int64_t>(&Id))32 return json::Value(*I);33 if (const std::string *S = std::get_if<std::string>(&Id))34 return json::Value(*S);35 llvm_unreachable("unexpected type in protocol::Id");36}37 38static bool mapId(const llvm::json::Value &V, StringLiteral Prop, Id &Id,39 llvm::json::Path P) {40 const auto *O = V.getAsObject();41 if (!O) {42 P.report("expected object");43 return false;44 }45 46 const auto *E = O->get(Prop);47 if (!E) {48 P.field(Prop).report("not found");49 return false;50 }51 52 if (auto S = E->getAsString()) {53 Id = S->str();54 return true;55 }56 57 if (auto I = E->getAsInteger()) {58 Id = *I;59 return true;60 }61 62 P.report("expected string or number");63 return false;64}65 66llvm::json::Value toJSON(const Request &R) {67 json::Object Result{68 {"jsonrpc", "2.0"}, {"id", toJSON(R.id)}, {"method", R.method}};69 if (R.params)70 Result.insert({"params", R.params});71 return Result;72}73 74bool fromJSON(const llvm::json::Value &V, Request &R, llvm::json::Path P) {75 llvm::json::ObjectMapper O(V, P);76 return O && mapId(V, "id", R.id, P) && O.map("method", R.method) &&77 mapRaw(V, "params", R.params, P);78}79 80bool operator==(const Request &a, const Request &b) {81 return a.id == b.id && a.method == b.method && a.params == b.params;82}83 84llvm::json::Value toJSON(const Error &E) {85 llvm::json::Object Result{{"code", E.code}, {"message", E.message}};86 if (E.data)87 Result.insert({"data", *E.data});88 return Result;89}90 91bool fromJSON(const llvm::json::Value &V, Error &E, llvm::json::Path P) {92 llvm::json::ObjectMapper O(V, P);93 return O && O.map("code", E.code) && O.map("message", E.message) &&94 mapRaw(V, "data", E.data, P);95}96 97bool operator==(const Error &a, const Error &b) {98 return a.code == b.code && a.message == b.message && a.data == b.data;99}100 101llvm::json::Value toJSON(const Response &R) {102 llvm::json::Object Result{{"jsonrpc", "2.0"}, {"id", toJSON(R.id)}};103 104 if (const Error *error = std::get_if<Error>(&R.result))105 Result.insert({"error", *error});106 if (const json::Value *result = std::get_if<json::Value>(&R.result))107 Result.insert({"result", *result});108 return Result;109}110 111bool fromJSON(const llvm::json::Value &V, Response &R, llvm::json::Path P) {112 const json::Object *E = V.getAsObject();113 if (!E) {114 P.report("expected object");115 return false;116 }117 118 const json::Value *result = E->get("result");119 const json::Value *raw_error = E->get("error");120 121 if (result && raw_error) {122 P.report("'result' and 'error' fields are mutually exclusive");123 return false;124 }125 126 if (!result && !raw_error) {127 P.report("'result' or 'error' fields are required'");128 return false;129 }130 131 if (result) {132 R.result = std::move(*result);133 } else {134 Error error;135 if (!fromJSON(*raw_error, error, P))136 return false;137 R.result = std::move(error);138 }139 140 return mapId(V, "id", R.id, P);141}142 143bool operator==(const Response &a, const Response &b) {144 return a.id == b.id && a.result == b.result;145}146 147llvm::json::Value toJSON(const Notification &N) {148 llvm::json::Object Result{{"jsonrpc", "2.0"}, {"method", N.method}};149 if (N.params)150 Result.insert({"params", N.params});151 return Result;152}153 154bool fromJSON(const llvm::json::Value &V, Notification &N, llvm::json::Path P) {155 llvm::json::ObjectMapper O(V, P);156 if (!O || !O.map("method", N.method))157 return false;158 auto *Obj = V.getAsObject();159 if (!Obj)160 return false;161 if (auto *Params = Obj->get("params"))162 N.params = *Params;163 return true;164}165 166bool operator==(const Notification &a, const Notification &b) {167 return a.method == b.method && a.params == b.params;168}169 170bool fromJSON(const llvm::json::Value &V, Resource &R, llvm::json::Path P) {171 llvm::json::ObjectMapper O(V, P);172 return O && O.map("uri", R.uri) && O.map("name", R.name) &&173 O.mapOptional("description", R.description) &&174 O.mapOptional("mimeType", R.mimeType);175}176 177llvm::json::Value toJSON(const Resource &R) {178 llvm::json::Object Result{{"uri", R.uri}, {"name", R.name}};179 if (!R.description.empty())180 Result.insert({"description", R.description});181 if (!R.mimeType.empty())182 Result.insert({"mimeType", R.mimeType});183 return Result;184}185 186llvm::json::Value toJSON(const TextResourceContents &RC) {187 llvm::json::Object Result{{"uri", RC.uri}, {"text", RC.text}};188 if (!RC.mimeType.empty())189 Result.insert({"mimeType", RC.mimeType});190 return Result;191}192 193bool fromJSON(const llvm::json::Value &V, TextResourceContents &RC,194 llvm::json::Path P) {195 llvm::json::ObjectMapper O(V, P);196 return O && O.map("uri", RC.uri) && O.map("text", RC.text) &&197 O.mapOptional("mimeType", RC.mimeType);198}199 200llvm::json::Value toJSON(const ReadResourceResult &RR) {201 return llvm::json::Object{{"contents", RR.contents}};202}203 204bool fromJSON(const llvm::json::Value &V, ReadResourceResult &RR,205 llvm::json::Path P) {206 llvm::json::ObjectMapper O(V, P);207 return O && O.map("contents", RR.contents);208}209 210llvm::json::Value toJSON(const TextContent &TC) {211 return llvm::json::Object{{"type", "text"}, {"text", TC.text}};212}213 214bool fromJSON(const llvm::json::Value &V, TextContent &TC, llvm::json::Path P) {215 llvm::json::ObjectMapper O(V, P);216 return O && O.map("text", TC.text);217}218 219llvm::json::Value toJSON(const ToolDefinition &TD) {220 llvm::json::Object Result{{"name", TD.name}};221 if (!TD.description.empty())222 Result.insert({"description", TD.description});223 if (TD.inputSchema)224 Result.insert({"inputSchema", TD.inputSchema});225 return Result;226}227 228bool fromJSON(const llvm::json::Value &V, ToolDefinition &TD,229 llvm::json::Path P) {230 231 llvm::json::ObjectMapper O(V, P);232 if (!O || !O.map("name", TD.name) ||233 !O.mapOptional("description", TD.description))234 return false;235 return mapRaw(V, "inputSchema", TD.inputSchema, P);236}237 238llvm::json::Value toJSON(const Message &M) {239 return std::visit([](auto &M) { return toJSON(M); }, M);240}241 242bool fromJSON(const llvm::json::Value &V, Message &M, llvm::json::Path P) {243 const auto *O = V.getAsObject();244 if (!O) {245 P.report("expected object");246 return false;247 }248 249 if (const json::Value *V = O->get("jsonrpc")) {250 if (V->getAsString().value_or("") != "2.0") {251 P.report("unsupported JSON RPC version");252 return false;253 }254 } else {255 P.report("not a valid JSON RPC message");256 return false;257 }258 259 // A message without an ID is a Notification.260 if (!O->get("id")) {261 Notification N;262 if (!fromJSON(V, N, P))263 return false;264 M = std::move(N);265 return true;266 }267 268 if (O->get("method")) {269 Request R;270 if (!fromJSON(V, R, P))271 return false;272 M = std::move(R);273 return true;274 }275 276 if (O->get("result") || O->get("error")) {277 Response R;278 if (!fromJSON(V, R, P))279 return false;280 M = std::move(R);281 return true;282 }283 284 P.report("unrecognized message type");285 return false;286}287 288json::Value toJSON(const Implementation &I) {289 json::Object result{{"name", I.name}, {"version", I.version}};290 291 if (!I.title.empty())292 result.insert({"title", I.title});293 294 return result;295}296 297bool fromJSON(const json::Value &V, Implementation &I, json::Path P) {298 json::ObjectMapper O(V, P);299 return O && O.map("name", I.name) && O.mapOptional("title", I.title) &&300 O.mapOptional("version", I.version);301}302 303json::Value toJSON(const ClientCapabilities &C) { return json::Object{}; }304 305bool fromJSON(const json::Value &, ClientCapabilities &, json::Path) {306 return true;307}308 309json::Value toJSON(const ServerCapabilities &C) {310 json::Object result{};311 312 if (C.supportsToolsList)313 result.insert({"tools", json::Object{{"listChanged", true}}});314 315 if (C.supportsResourcesList || C.supportsResourcesSubscribe) {316 json::Object resources;317 if (C.supportsResourcesList)318 resources.insert({"listChanged", true});319 if (C.supportsResourcesSubscribe)320 resources.insert({"subscribe", true});321 result.insert({"resources", std::move(resources)});322 }323 324 if (C.supportsCompletions)325 result.insert({"completions", json::Object{}});326 327 if (C.supportsLogging)328 result.insert({"logging", json::Object{}});329 330 return result;331}332 333bool fromJSON(const json::Value &V, ServerCapabilities &C, json::Path P) {334 const json::Object *O = V.getAsObject();335 if (!O) {336 P.report("expected object");337 return false;338 }339 340 if (O->find("tools") != O->end())341 C.supportsToolsList = true;342 343 return true;344}345 346json::Value toJSON(const InitializeParams &P) {347 return json::Object{348 {"protocolVersion", P.protocolVersion},349 {"capabilities", P.capabilities},350 {"clientInfo", P.clientInfo},351 };352}353 354bool fromJSON(const json::Value &V, InitializeParams &I, json::Path P) {355 json::ObjectMapper O(V, P);356 return O && O.map("protocolVersion", I.protocolVersion) &&357 O.map("capabilities", I.capabilities) &&358 O.map("clientInfo", I.clientInfo);359}360 361json::Value toJSON(const InitializeResult &R) {362 json::Object result{{"protocolVersion", R.protocolVersion},363 {"capabilities", R.capabilities},364 {"serverInfo", R.serverInfo}};365 366 if (!R.instructions.empty())367 result.insert({"instructions", R.instructions});368 369 return result;370}371 372bool fromJSON(const json::Value &V, InitializeResult &R, json::Path P) {373 json::ObjectMapper O(V, P);374 return O && O.map("protocolVersion", R.protocolVersion) &&375 O.map("capabilities", R.capabilities) &&376 O.map("serverInfo", R.serverInfo) &&377 O.mapOptional("instructions", R.instructions);378}379 380json::Value toJSON(const ListToolsResult &R) {381 return json::Object{{"tools", R.tools}};382}383 384bool fromJSON(const json::Value &V, ListToolsResult &R, json::Path P) {385 json::ObjectMapper O(V, P);386 return O && O.map("tools", R.tools);387}388 389json::Value toJSON(const CallToolResult &R) {390 json::Object result{{"content", R.content}};391 392 if (R.isError)393 result.insert({"isError", R.isError});394 if (R.structuredContent)395 result.insert({"structuredContent", *R.structuredContent});396 397 return result;398}399 400bool fromJSON(const json::Value &V, CallToolResult &R, json::Path P) {401 json::ObjectMapper O(V, P);402 return O && O.map("content", R.content) &&403 O.mapOptional("isError", R.isError) &&404 mapRaw(V, "structuredContent", R.structuredContent, P);405}406 407json::Value toJSON(const CallToolParams &R) {408 json::Object result{{"name", R.name}};409 410 if (R.arguments)411 result.insert({"arguments", *R.arguments});412 413 return result;414}415 416bool fromJSON(const json::Value &V, CallToolParams &R, json::Path P) {417 json::ObjectMapper O(V, P);418 return O && O.map("name", R.name) && mapRaw(V, "arguments", R.arguments, P);419}420 421json::Value toJSON(const ReadResourceParams &R) {422 return json::Object{{"uri", R.uri}};423}424 425bool fromJSON(const json::Value &V, ReadResourceParams &R, json::Path P) {426 json::ObjectMapper O(V, P);427 return O && O.map("uri", R.uri);428}429 430json::Value toJSON(const ListResourcesResult &R) {431 return json::Object{{"resources", R.resources}};432}433 434bool fromJSON(const json::Value &V, ListResourcesResult &R, json::Path P) {435 json::ObjectMapper O(V, P);436 return O && O.map("resources", R.resources);437}438 439json::Value toJSON(const Void &R) { return json::Object{}; }440 441bool fromJSON(const json::Value &V, Void &R, json::Path P) { return true; }442 443} // namespace lldb_protocol::mcp444