1use schemars::JsonSchema;
7use serde::Deserialize;
8use serde::Serialize;
9use ts_rs::TS;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, TS)]
13#[serde(untagged)]
14pub enum RequestId {
15 String(String),
16 #[ts(type = "number")]
17 Integer(i64),
18}
19
20impl std::fmt::Display for RequestId {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 match self {
23 RequestId::String(s) => f.write_str(s),
24 RequestId::Integer(i) => i.fmt(f),
25 }
26 }
27}
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
31#[serde(rename_all = "camelCase")]
32pub struct McpServerInfo {
33 pub name: String,
34 pub title: Option<String>,
35 pub version: String,
36 pub description: Option<String>,
37 pub icons: Option<Vec<serde_json::Value>>,
38 pub website_url: Option<String>,
39}
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
43#[serde(rename_all = "camelCase")]
44pub struct Tool {
45 pub name: String,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
47 #[ts(optional)]
48 pub title: Option<String>,
49 #[serde(default, skip_serializing_if = "Option::is_none")]
50 #[ts(optional)]
51 pub description: Option<String>,
52 pub input_schema: serde_json::Value,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
54 #[ts(optional)]
55 pub output_schema: Option<serde_json::Value>,
56 #[serde(default, skip_serializing_if = "Option::is_none")]
57 #[ts(optional)]
58 pub annotations: Option<serde_json::Value>,
59 #[serde(default, skip_serializing_if = "Option::is_none")]
60 #[ts(optional)]
61 pub icons: Option<Vec<serde_json::Value>>,
62 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
63 #[ts(optional)]
64 pub meta: Option<serde_json::Value>,
65}
66
67#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
69#[serde(rename_all = "camelCase")]
70pub struct Resource {
71 #[serde(default, skip_serializing_if = "Option::is_none")]
72 #[ts(optional)]
73 pub annotations: Option<serde_json::Value>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
75 #[ts(optional)]
76 pub description: Option<String>,
77 #[serde(default, skip_serializing_if = "Option::is_none")]
78 #[ts(optional)]
79 pub mime_type: Option<String>,
80 pub name: String,
81 #[serde(default, skip_serializing_if = "Option::is_none")]
82 #[ts(optional)]
83 #[ts(type = "number")]
84 pub size: Option<i64>,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
86 #[ts(optional)]
87 pub title: Option<String>,
88 pub uri: String,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
90 #[ts(optional)]
91 pub icons: Option<Vec<serde_json::Value>>,
92 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
93 #[ts(optional)]
94 pub meta: Option<serde_json::Value>,
95}
96
97#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
99#[serde(untagged)]
100pub enum ResourceContent {
101 #[serde(rename_all = "camelCase")]
102 #[ts(rename_all = "camelCase")]
103 Text {
104 uri: String,
106 #[serde(default, skip_serializing_if = "Option::is_none")]
107 #[ts(optional)]
108 mime_type: Option<String>,
109 text: String,
110 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
111 #[ts(optional)]
112 meta: Option<serde_json::Value>,
113 },
114 #[serde(rename_all = "camelCase")]
115 #[ts(rename_all = "camelCase")]
116 Blob {
117 uri: String,
119 #[serde(default, skip_serializing_if = "Option::is_none")]
120 #[ts(optional)]
121 mime_type: Option<String>,
122 blob: String,
123 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
124 #[ts(optional)]
125 meta: Option<serde_json::Value>,
126 },
127}
128
129#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
131#[serde(rename_all = "camelCase")]
132pub struct ResourceTemplate {
133 #[serde(default, skip_serializing_if = "Option::is_none")]
134 #[ts(optional)]
135 pub annotations: Option<serde_json::Value>,
136 pub uri_template: String,
137 pub name: String,
138 #[serde(default, skip_serializing_if = "Option::is_none")]
139 #[ts(optional)]
140 pub title: Option<String>,
141 #[serde(default, skip_serializing_if = "Option::is_none")]
142 #[ts(optional)]
143 pub description: Option<String>,
144 #[serde(default, skip_serializing_if = "Option::is_none")]
145 #[ts(optional)]
146 pub mime_type: Option<String>,
147}
148
149#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
151#[serde(rename_all = "camelCase")]
152pub struct CallToolResult {
153 pub content: Vec<serde_json::Value>,
154 #[serde(default, skip_serializing_if = "Option::is_none")]
155 #[ts(optional)]
156 pub structured_content: Option<serde_json::Value>,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
158 #[ts(optional)]
159 pub is_error: Option<bool>,
160 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
161 #[ts(optional)]
162 pub meta: Option<serde_json::Value>,
163}
164
165fn deserialize_lossy_opt_i64<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
172where
173 D: serde::Deserializer<'de>,
174{
175 match Option::<serde_json::Number>::deserialize(deserializer)? {
176 Some(number) => {
177 if let Some(v) = number.as_i64() {
178 Ok(Some(v))
179 } else if let Some(v) = number.as_u64() {
180 Ok(i64::try_from(v).ok())
181 } else {
182 Ok(None)
183 }
184 }
185 None => Ok(None),
186 }
187}
188
189#[derive(Debug, Deserialize)]
190#[serde(rename_all = "camelCase")]
191struct ToolSerde {
192 name: String,
193 #[serde(default)]
194 title: Option<String>,
195 #[serde(default)]
196 description: Option<String>,
197 #[serde(default, rename = "inputSchema", alias = "input_schema")]
198 input_schema: serde_json::Value,
199 #[serde(default, rename = "outputSchema", alias = "output_schema")]
200 output_schema: Option<serde_json::Value>,
201 #[serde(default)]
202 annotations: Option<serde_json::Value>,
203 #[serde(default)]
204 icons: Option<Vec<serde_json::Value>>,
205 #[serde(rename = "_meta", default)]
206 meta: Option<serde_json::Value>,
207}
208
209impl From<ToolSerde> for Tool {
210 fn from(value: ToolSerde) -> Self {
211 let ToolSerde {
212 name,
213 title,
214 description,
215 input_schema,
216 output_schema,
217 annotations,
218 icons,
219 meta,
220 } = value;
221 Self {
222 name,
223 title,
224 description,
225 input_schema,
226 output_schema,
227 annotations,
228 icons,
229 meta,
230 }
231 }
232}
233
234#[derive(Debug, Deserialize)]
235#[serde(rename_all = "camelCase")]
236struct ResourceSerde {
237 #[serde(default)]
238 annotations: Option<serde_json::Value>,
239 #[serde(default)]
240 description: Option<String>,
241 #[serde(rename = "mimeType", alias = "mime_type", default)]
242 mime_type: Option<String>,
243 name: String,
244 #[serde(default, deserialize_with = "deserialize_lossy_opt_i64")]
245 size: Option<i64>,
246 #[serde(default)]
247 title: Option<String>,
248 uri: String,
249 #[serde(default)]
250 icons: Option<Vec<serde_json::Value>>,
251 #[serde(rename = "_meta", default)]
252 meta: Option<serde_json::Value>,
253}
254
255impl From<ResourceSerde> for Resource {
256 fn from(value: ResourceSerde) -> Self {
257 let ResourceSerde {
258 annotations,
259 description,
260 mime_type,
261 name,
262 size,
263 title,
264 uri,
265 icons,
266 meta,
267 } = value;
268 Self {
269 annotations,
270 description,
271 mime_type,
272 name,
273 size,
274 title,
275 uri,
276 icons,
277 meta,
278 }
279 }
280}
281
282#[derive(Debug, Deserialize)]
283#[serde(rename_all = "camelCase")]
284struct ResourceTemplateSerde {
285 #[serde(default)]
286 annotations: Option<serde_json::Value>,
287 #[serde(rename = "uriTemplate", alias = "uri_template")]
288 uri_template: String,
289 name: String,
290 #[serde(default)]
291 title: Option<String>,
292 #[serde(default)]
293 description: Option<String>,
294 #[serde(rename = "mimeType", alias = "mime_type", default)]
295 mime_type: Option<String>,
296}
297
298impl From<ResourceTemplateSerde> for ResourceTemplate {
299 fn from(value: ResourceTemplateSerde) -> Self {
300 let ResourceTemplateSerde {
301 annotations,
302 uri_template,
303 name,
304 title,
305 description,
306 mime_type,
307 } = value;
308 Self {
309 annotations,
310 uri_template,
311 name,
312 title,
313 description,
314 mime_type,
315 }
316 }
317}
318
319impl Tool {
320 pub fn from_mcp_value(value: serde_json::Value) -> Result<Self, serde_json::Error> {
321 Ok(serde_json::from_value::<ToolSerde>(value)?.into())
322 }
323}
324
325impl Resource {
326 pub fn from_mcp_value(value: serde_json::Value) -> Result<Self, serde_json::Error> {
327 Ok(serde_json::from_value::<ResourceSerde>(value)?.into())
328 }
329}
330
331impl ResourceTemplate {
332 pub fn from_mcp_value(value: serde_json::Value) -> Result<Self, serde_json::Error> {
333 Ok(serde_json::from_value::<ResourceTemplateSerde>(value)?.into())
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use pretty_assertions::assert_eq;
340
341 use super::*;
342
343 #[test]
344 fn resource_size_deserializes_without_narrowing() {
345 let resource = serde_json::json!({
346 "name": "big",
347 "uri": "file:///tmp/big",
348 "size": 5_000_000_000u64,
349 });
350
351 let parsed = Resource::from_mcp_value(resource).expect("should deserialize");
352 assert_eq!(parsed.size, Some(5_000_000_000));
353
354 let resource = serde_json::json!({
355 "name": "negative",
356 "uri": "file:///tmp/negative",
357 "size": -1,
358 });
359
360 let parsed = Resource::from_mcp_value(resource).expect("should deserialize");
361 assert_eq!(parsed.size, Some(-1));
362
363 let resource = serde_json::json!({
364 "name": "too_big_for_i64",
365 "uri": "file:///tmp/too_big_for_i64",
366 "size": 18446744073709551615u64,
367 });
368
369 let parsed = Resource::from_mcp_value(resource).expect("should deserialize");
370 assert_eq!(parsed.size, None);
371 }
372}