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 Tool {
33 pub name: String,
34 #[serde(default, skip_serializing_if = "Option::is_none")]
35 #[ts(optional)]
36 pub title: Option<String>,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 #[ts(optional)]
39 pub description: Option<String>,
40 pub input_schema: serde_json::Value,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
42 #[ts(optional)]
43 pub output_schema: Option<serde_json::Value>,
44 #[serde(default, skip_serializing_if = "Option::is_none")]
45 #[ts(optional)]
46 pub annotations: Option<serde_json::Value>,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
48 #[ts(optional)]
49 pub icons: Option<Vec<serde_json::Value>>,
50 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
51 #[ts(optional)]
52 pub meta: Option<serde_json::Value>,
53}
54
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
57#[serde(rename_all = "camelCase")]
58pub struct Resource {
59 #[serde(default, skip_serializing_if = "Option::is_none")]
60 #[ts(optional)]
61 pub annotations: Option<serde_json::Value>,
62 #[serde(default, skip_serializing_if = "Option::is_none")]
63 #[ts(optional)]
64 pub description: Option<String>,
65 #[serde(default, skip_serializing_if = "Option::is_none")]
66 #[ts(optional)]
67 pub mime_type: Option<String>,
68 pub name: String,
69 #[serde(default, skip_serializing_if = "Option::is_none")]
70 #[ts(optional)]
71 #[ts(type = "number")]
72 pub size: Option<i64>,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
74 #[ts(optional)]
75 pub title: Option<String>,
76 pub uri: String,
77 #[serde(default, skip_serializing_if = "Option::is_none")]
78 #[ts(optional)]
79 pub icons: Option<Vec<serde_json::Value>>,
80 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
81 #[ts(optional)]
82 pub meta: Option<serde_json::Value>,
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
87#[serde(rename_all = "camelCase")]
88pub struct ResourceTemplate {
89 #[serde(default, skip_serializing_if = "Option::is_none")]
90 #[ts(optional)]
91 pub annotations: Option<serde_json::Value>,
92 pub uri_template: String,
93 pub name: String,
94 #[serde(default, skip_serializing_if = "Option::is_none")]
95 #[ts(optional)]
96 pub title: Option<String>,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
98 #[ts(optional)]
99 pub description: Option<String>,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
101 #[ts(optional)]
102 pub mime_type: Option<String>,
103}
104
105#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
107#[serde(rename_all = "camelCase")]
108pub struct CallToolResult {
109 pub content: Vec<serde_json::Value>,
110 #[serde(default, skip_serializing_if = "Option::is_none")]
111 #[ts(optional)]
112 pub structured_content: Option<serde_json::Value>,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
114 #[ts(optional)]
115 pub is_error: Option<bool>,
116 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
117 #[ts(optional)]
118 pub meta: Option<serde_json::Value>,
119}
120
121fn deserialize_lossy_opt_i64<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
128where
129 D: serde::Deserializer<'de>,
130{
131 match Option::<serde_json::Number>::deserialize(deserializer)? {
132 Some(number) => {
133 if let Some(v) = number.as_i64() {
134 Ok(Some(v))
135 } else if let Some(v) = number.as_u64() {
136 Ok(i64::try_from(v).ok())
137 } else {
138 Ok(None)
139 }
140 }
141 None => Ok(None),
142 }
143}
144
145#[derive(Debug, Deserialize)]
146#[serde(rename_all = "camelCase")]
147struct ToolSerde {
148 name: String,
149 #[serde(default)]
150 title: Option<String>,
151 #[serde(default)]
152 description: Option<String>,
153 #[serde(default, rename = "inputSchema", alias = "input_schema")]
154 input_schema: serde_json::Value,
155 #[serde(default, rename = "outputSchema", alias = "output_schema")]
156 output_schema: Option<serde_json::Value>,
157 #[serde(default)]
158 annotations: Option<serde_json::Value>,
159 #[serde(default)]
160 icons: Option<Vec<serde_json::Value>>,
161 #[serde(rename = "_meta", default)]
162 meta: Option<serde_json::Value>,
163}
164
165impl From<ToolSerde> for Tool {
166 fn from(value: ToolSerde) -> Self {
167 let ToolSerde {
168 name,
169 title,
170 description,
171 input_schema,
172 output_schema,
173 annotations,
174 icons,
175 meta,
176 } = value;
177 Self {
178 name,
179 title,
180 description,
181 input_schema,
182 output_schema,
183 annotations,
184 icons,
185 meta,
186 }
187 }
188}
189
190#[derive(Debug, Deserialize)]
191#[serde(rename_all = "camelCase")]
192struct ResourceSerde {
193 #[serde(default)]
194 annotations: Option<serde_json::Value>,
195 #[serde(default)]
196 description: Option<String>,
197 #[serde(rename = "mimeType", alias = "mime_type", default)]
198 mime_type: Option<String>,
199 name: String,
200 #[serde(default, deserialize_with = "deserialize_lossy_opt_i64")]
201 size: Option<i64>,
202 #[serde(default)]
203 title: Option<String>,
204 uri: String,
205 #[serde(default)]
206 icons: Option<Vec<serde_json::Value>>,
207 #[serde(rename = "_meta", default)]
208 meta: Option<serde_json::Value>,
209}
210
211impl From<ResourceSerde> for Resource {
212 fn from(value: ResourceSerde) -> Self {
213 let ResourceSerde {
214 annotations,
215 description,
216 mime_type,
217 name,
218 size,
219 title,
220 uri,
221 icons,
222 meta,
223 } = value;
224 Self {
225 annotations,
226 description,
227 mime_type,
228 name,
229 size,
230 title,
231 uri,
232 icons,
233 meta,
234 }
235 }
236}
237
238#[derive(Debug, Deserialize)]
239#[serde(rename_all = "camelCase")]
240struct ResourceTemplateSerde {
241 #[serde(default)]
242 annotations: Option<serde_json::Value>,
243 #[serde(rename = "uriTemplate", alias = "uri_template")]
244 uri_template: String,
245 name: String,
246 #[serde(default)]
247 title: Option<String>,
248 #[serde(default)]
249 description: Option<String>,
250 #[serde(rename = "mimeType", alias = "mime_type", default)]
251 mime_type: Option<String>,
252}
253
254impl From<ResourceTemplateSerde> for ResourceTemplate {
255 fn from(value: ResourceTemplateSerde) -> Self {
256 let ResourceTemplateSerde {
257 annotations,
258 uri_template,
259 name,
260 title,
261 description,
262 mime_type,
263 } = value;
264 Self {
265 annotations,
266 uri_template,
267 name,
268 title,
269 description,
270 mime_type,
271 }
272 }
273}
274
275impl Tool {
276 pub fn from_mcp_value(value: serde_json::Value) -> Result<Self, serde_json::Error> {
277 Ok(serde_json::from_value::<ToolSerde>(value)?.into())
278 }
279}
280
281impl Resource {
282 pub fn from_mcp_value(value: serde_json::Value) -> Result<Self, serde_json::Error> {
283 Ok(serde_json::from_value::<ResourceSerde>(value)?.into())
284 }
285}
286
287impl ResourceTemplate {
288 pub fn from_mcp_value(value: serde_json::Value) -> Result<Self, serde_json::Error> {
289 Ok(serde_json::from_value::<ResourceTemplateSerde>(value)?.into())
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use pretty_assertions::assert_eq;
296
297 use super::*;
298
299 #[test]
300 fn resource_size_deserializes_without_narrowing() {
301 let resource = serde_json::json!({
302 "name": "big",
303 "uri": "file:///tmp/big",
304 "size": 5_000_000_000u64,
305 });
306
307 let parsed = Resource::from_mcp_value(resource).expect("should deserialize");
308 assert_eq!(parsed.size, Some(5_000_000_000));
309
310 let resource = serde_json::json!({
311 "name": "negative",
312 "uri": "file:///tmp/negative",
313 "size": -1,
314 });
315
316 let parsed = Resource::from_mcp_value(resource).expect("should deserialize");
317 assert_eq!(parsed.size, Some(-1));
318
319 let resource = serde_json::json!({
320 "name": "too_big_for_i64",
321 "uri": "file:///tmp/too_big_for_i64",
322 "size": 18446744073709551615u64,
323 });
324
325 let parsed = Resource::from_mcp_value(resource).expect("should deserialize");
326 assert_eq!(parsed.size, None);
327 }
328}