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