xref: /freebsd/contrib/llvm-project/lldb/source/Plugins/Protocol/MCP/Protocol.cpp (revision 700637cbb5e582861067a11aaca4d053546871d2)
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 
mapRaw(const json::Value & Params,StringLiteral Prop,std::optional<json::Value> & V,json::Path P)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 
toJSON(const Request & R)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 
fromJSON(const llvm::json::Value & V,Request & R,llvm::json::Path P)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 
toJSON(const ErrorInfo & EI)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 
fromJSON(const llvm::json::Value & V,ErrorInfo & EI,llvm::json::Path P)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 
toJSON(const Error & E)56 llvm::json::Value toJSON(const Error &E) {
57   return json::Object{{"jsonrpc", "2.0"}, {"id", E.id}, {"error", E.error}};
58 }
59 
fromJSON(const llvm::json::Value & V,Error & E,llvm::json::Path P)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 
toJSON(const Response & R)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 
fromJSON(const llvm::json::Value & V,Response & R,llvm::json::Path P)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 
toJSON(const Notification & N)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 
fromJSON(const llvm::json::Value & V,Notification & N,llvm::json::Path P)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 
toJSON(const ToolCapability & TC)100 llvm::json::Value toJSON(const ToolCapability &TC) {
101   return llvm::json::Object{{"listChanged", TC.listChanged}};
102 }
103 
fromJSON(const llvm::json::Value & V,ToolCapability & TC,llvm::json::Path P)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 
toJSON(const ResourceCapability & RC)110 llvm::json::Value toJSON(const ResourceCapability &RC) {
111   return llvm::json::Object{{"listChanged", RC.listChanged},
112                             {"subscribe", RC.subscribe}};
113 }
114 
fromJSON(const llvm::json::Value & V,ResourceCapability & RC,llvm::json::Path P)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 
toJSON(const Capabilities & C)122 llvm::json::Value toJSON(const Capabilities &C) {
123   return llvm::json::Object{{"tools", C.tools}, {"resources", C.resources}};
124 }
125 
fromJSON(const llvm::json::Value & V,Resource & R,llvm::json::Path P)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 
toJSON(const Resource & R)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 
fromJSON(const llvm::json::Value & V,Capabilities & C,llvm::json::Path P)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 
toJSON(const ResourceContents & RC)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 
fromJSON(const llvm::json::Value & V,ResourceContents & RC,llvm::json::Path P)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 
toJSON(const ResourceResult & RR)161 llvm::json::Value toJSON(const ResourceResult &RR) {
162   return llvm::json::Object{{"contents", RR.contents}};
163 }
164 
fromJSON(const llvm::json::Value & V,ResourceResult & RR,llvm::json::Path P)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 
toJSON(const TextContent & TC)171 llvm::json::Value toJSON(const TextContent &TC) {
172   return llvm::json::Object{{"type", "text"}, {"text", TC.text}};
173 }
174 
fromJSON(const llvm::json::Value & V,TextContent & TC,llvm::json::Path P)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 
toJSON(const TextResult & TR)180 llvm::json::Value toJSON(const TextResult &TR) {
181   return llvm::json::Object{{"content", TR.content}, {"isError", TR.isError}};
182 }
183 
fromJSON(const llvm::json::Value & V,TextResult & TR,llvm::json::Path P)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 
toJSON(const ToolDefinition & TD)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 
fromJSON(const llvm::json::Value & V,ToolDefinition & TD,llvm::json::Path P)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 
toJSON(const Message & M)208 llvm::json::Value toJSON(const Message &M) {
209   return std::visit([](auto &M) { return toJSON(M); }, M);
210 }
211 
fromJSON(const llvm::json::Value & V,Message & M,llvm::json::Path P)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