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-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "Protocol.h" 10 #include "llvm/Support/JSON.h" 11 12 using namespace llvm; 13 14 namespace lldb_private::mcp::protocol { 15 16 static bool mapRaw(const json::Value &Params, StringLiteral Prop, 17 std::optional<json::Value> &V, json::Path P) { 18 const auto *O = Params.getAsObject(); 19 if (!O) { 20 P.report("expected object"); 21 return false; 22 } 23 const json::Value *E = O->get(Prop); 24 if (E) 25 V = std::move(*E); 26 return true; 27 } 28 29 llvm::json::Value toJSON(const Request &R) { 30 json::Object Result{{"jsonrpc", "2.0"}, {"id", R.id}, {"method", R.method}}; 31 if (R.params) 32 Result.insert({"params", R.params}); 33 return Result; 34 } 35 36 bool fromJSON(const llvm::json::Value &V, Request &R, llvm::json::Path P) { 37 llvm::json::ObjectMapper O(V, P); 38 if (!O || !O.map("id", R.id) || !O.map("method", R.method)) 39 return false; 40 return mapRaw(V, "params", R.params, P); 41 } 42 43 llvm::json::Value toJSON(const ErrorInfo &EI) { 44 llvm::json::Object Result{{"code", EI.code}, {"message", EI.message}}; 45 if (!EI.data.empty()) 46 Result.insert({"data", EI.data}); 47 return Result; 48 } 49 50 bool fromJSON(const llvm::json::Value &V, ErrorInfo &EI, llvm::json::Path P) { 51 llvm::json::ObjectMapper O(V, P); 52 return O && O.map("code", EI.code) && O.map("message", EI.message) && 53 O.mapOptional("data", EI.data); 54 } 55 56 llvm::json::Value toJSON(const Error &E) { 57 return json::Object{{"jsonrpc", "2.0"}, {"id", E.id}, {"error", E.error}}; 58 } 59 60 bool fromJSON(const llvm::json::Value &V, Error &E, llvm::json::Path P) { 61 llvm::json::ObjectMapper O(V, P); 62 return O && O.map("id", E.id) && O.map("error", E.error); 63 } 64 65 llvm::json::Value toJSON(const Response &R) { 66 llvm::json::Object Result{{"jsonrpc", "2.0"}, {"id", R.id}}; 67 if (R.result) 68 Result.insert({"result", R.result}); 69 if (R.error) 70 Result.insert({"error", R.error}); 71 return Result; 72 } 73 74 bool fromJSON(const llvm::json::Value &V, Response &R, llvm::json::Path P) { 75 llvm::json::ObjectMapper O(V, P); 76 if (!O || !O.map("id", R.id) || !O.map("error", R.error)) 77 return false; 78 return mapRaw(V, "result", R.result, P); 79 } 80 81 llvm::json::Value toJSON(const Notification &N) { 82 llvm::json::Object Result{{"jsonrpc", "2.0"}, {"method", N.method}}; 83 if (N.params) 84 Result.insert({"params", N.params}); 85 return Result; 86 } 87 88 bool fromJSON(const llvm::json::Value &V, Notification &N, llvm::json::Path P) { 89 llvm::json::ObjectMapper O(V, P); 90 if (!O || !O.map("method", N.method)) 91 return false; 92 auto *Obj = V.getAsObject(); 93 if (!Obj) 94 return false; 95 if (auto *Params = Obj->get("params")) 96 N.params = *Params; 97 return true; 98 } 99 100 llvm::json::Value toJSON(const ToolCapability &TC) { 101 return llvm::json::Object{{"listChanged", TC.listChanged}}; 102 } 103 104 bool fromJSON(const llvm::json::Value &V, ToolCapability &TC, 105 llvm::json::Path P) { 106 llvm::json::ObjectMapper O(V, P); 107 return O && O.map("listChanged", TC.listChanged); 108 } 109 110 llvm::json::Value toJSON(const ResourceCapability &RC) { 111 return llvm::json::Object{{"listChanged", RC.listChanged}, 112 {"subscribe", RC.subscribe}}; 113 } 114 115 bool fromJSON(const llvm::json::Value &V, ResourceCapability &RC, 116 llvm::json::Path P) { 117 llvm::json::ObjectMapper O(V, P); 118 return O && O.map("listChanged", RC.listChanged) && 119 O.map("subscribe", RC.subscribe); 120 } 121 122 llvm::json::Value toJSON(const Capabilities &C) { 123 return llvm::json::Object{{"tools", C.tools}, {"resources", C.resources}}; 124 } 125 126 bool fromJSON(const llvm::json::Value &V, Resource &R, llvm::json::Path P) { 127 llvm::json::ObjectMapper O(V, P); 128 return O && O.map("uri", R.uri) && O.map("name", R.name) && 129 O.mapOptional("description", R.description) && 130 O.mapOptional("mimeType", R.mimeType); 131 } 132 133 llvm::json::Value toJSON(const Resource &R) { 134 llvm::json::Object Result{{"uri", R.uri}, {"name", R.name}}; 135 if (!R.description.empty()) 136 Result.insert({"description", R.description}); 137 if (!R.mimeType.empty()) 138 Result.insert({"mimeType", R.mimeType}); 139 return Result; 140 } 141 142 bool fromJSON(const llvm::json::Value &V, Capabilities &C, llvm::json::Path P) { 143 llvm::json::ObjectMapper O(V, P); 144 return O && O.map("tools", C.tools); 145 } 146 147 llvm::json::Value toJSON(const ResourceContents &RC) { 148 llvm::json::Object Result{{"uri", RC.uri}, {"text", RC.text}}; 149 if (!RC.mimeType.empty()) 150 Result.insert({"mimeType", RC.mimeType}); 151 return Result; 152 } 153 154 bool fromJSON(const llvm::json::Value &V, ResourceContents &RC, 155 llvm::json::Path P) { 156 llvm::json::ObjectMapper O(V, P); 157 return O && O.map("uri", RC.uri) && O.map("text", RC.text) && 158 O.mapOptional("mimeType", RC.mimeType); 159 } 160 161 llvm::json::Value toJSON(const ResourceResult &RR) { 162 return llvm::json::Object{{"contents", RR.contents}}; 163 } 164 165 bool fromJSON(const llvm::json::Value &V, ResourceResult &RR, 166 llvm::json::Path P) { 167 llvm::json::ObjectMapper O(V, P); 168 return O && O.map("contents", RR.contents); 169 } 170 171 llvm::json::Value toJSON(const TextContent &TC) { 172 return llvm::json::Object{{"type", "text"}, {"text", TC.text}}; 173 } 174 175 bool fromJSON(const llvm::json::Value &V, TextContent &TC, llvm::json::Path P) { 176 llvm::json::ObjectMapper O(V, P); 177 return O && O.map("text", TC.text); 178 } 179 180 llvm::json::Value toJSON(const TextResult &TR) { 181 return llvm::json::Object{{"content", TR.content}, {"isError", TR.isError}}; 182 } 183 184 bool fromJSON(const llvm::json::Value &V, TextResult &TR, llvm::json::Path P) { 185 llvm::json::ObjectMapper O(V, P); 186 return O && O.map("content", TR.content) && O.map("isError", TR.isError); 187 } 188 189 llvm::json::Value toJSON(const ToolDefinition &TD) { 190 llvm::json::Object Result{{"name", TD.name}}; 191 if (!TD.description.empty()) 192 Result.insert({"description", TD.description}); 193 if (TD.inputSchema) 194 Result.insert({"inputSchema", TD.inputSchema}); 195 return Result; 196 } 197 198 bool fromJSON(const llvm::json::Value &V, ToolDefinition &TD, 199 llvm::json::Path P) { 200 201 llvm::json::ObjectMapper O(V, P); 202 if (!O || !O.map("name", TD.name) || 203 !O.mapOptional("description", TD.description)) 204 return false; 205 return mapRaw(V, "inputSchema", TD.inputSchema, P); 206 } 207 208 llvm::json::Value toJSON(const Message &M) { 209 return std::visit([](auto &M) { return toJSON(M); }, M); 210 } 211 212 bool fromJSON(const llvm::json::Value &V, Message &M, llvm::json::Path P) { 213 const auto *O = V.getAsObject(); 214 if (!O) { 215 P.report("expected object"); 216 return false; 217 } 218 219 if (const json::Value *V = O->get("jsonrpc")) { 220 if (V->getAsString().value_or("") != "2.0") { 221 P.report("unsupported JSON RPC version"); 222 return false; 223 } 224 } else { 225 P.report("not a valid JSON RPC message"); 226 return false; 227 } 228 229 // A message without an ID is a Notification. 230 if (!O->get("id")) { 231 protocol::Notification N; 232 if (!fromJSON(V, N, P)) 233 return false; 234 M = std::move(N); 235 return true; 236 } 237 238 if (O->get("error")) { 239 protocol::Error E; 240 if (!fromJSON(V, E, P)) 241 return false; 242 M = std::move(E); 243 return true; 244 } 245 246 if (O->get("result")) { 247 protocol::Response R; 248 if (!fromJSON(V, R, P)) 249 return false; 250 M = std::move(R); 251 return true; 252 } 253 254 if (O->get("method")) { 255 protocol::Request R; 256 if (!fromJSON(V, R, P)) 257 return false; 258 M = std::move(R); 259 return true; 260 } 261 262 P.report("unrecognized message type"); 263 return false; 264 } 265 266 } // namespace lldb_private::mcp::protocol 267