1use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use serde_with::{base64::Base64, serde_as, skip_serializing_none};
14
15pub const PROTOCOL_VERSION: &str = "2025-11-25";
17
18#[allow(deprecated)]
22pub use crate::jsonrpc::{
23 Body as JsonRpcBody, Error as JsonRpcError, Request as JsonRpcRequest,
24 Response as JsonRpcResponse, Version as JsonRpcVersion,
25};
26
27#[skip_serializing_none]
31#[derive(Debug, Clone, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct ServerInfo {
34 pub name: String,
35 #[serde(default)]
36 pub version: Option<String>,
37}
38
39#[skip_serializing_none]
41#[derive(Debug, Clone, Default, Serialize, Deserialize)]
42pub struct ServerCapabilities {
43 #[serde(default)]
44 pub tools: Option<Value>,
45 #[serde(default)]
46 pub resources: Option<Value>,
47 #[serde(default)]
48 pub prompts: Option<Value>,
49}
50
51#[skip_serializing_none]
53#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct InitializeResult {
56 pub protocol_version: String,
57 pub server_info: ServerInfo,
58 #[serde(default)]
59 pub capabilities: Option<ServerCapabilities>,
60}
61
62#[skip_serializing_none]
66#[derive(Debug, Clone, Serialize, Deserialize)]
67#[serde(rename_all = "camelCase")]
68pub struct ToolDefinition {
69 pub name: String,
70 #[serde(default)]
71 pub description: Option<String>,
72 #[serde(default = "default_object_schema")]
73 pub input_schema: Value,
74 #[serde(default)]
75 pub annotations: Option<ToolAnnotations>,
76}
77
78fn default_object_schema() -> Value {
79 serde_json::json!({"type": "object"})
80}
81
82#[skip_serializing_none]
84#[derive(Debug, Clone, Default, Serialize, Deserialize)]
85#[serde(rename_all = "camelCase")]
86pub struct ToolAnnotations {
87 #[serde(default)]
88 pub read_only_hint: Option<bool>,
89 #[serde(default)]
90 pub idempotent_hint: Option<bool>,
91 #[serde(default)]
92 pub destructive_hint: Option<bool>,
93 #[serde(default)]
94 pub open_world_hint: Option<bool>,
95}
96
97#[skip_serializing_none]
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct ListToolsResult {
101 pub tools: Vec<ToolDefinition>,
102 #[serde(default)]
103 pub next_cursor: Option<String>,
104}
105
106#[skip_serializing_none]
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct CallToolParams {
110 pub name: String,
111 #[serde(default)]
112 pub arguments: Option<Value>,
113}
114
115#[skip_serializing_none]
117#[derive(Debug, Clone, Serialize, Deserialize)]
118#[serde(rename_all = "camelCase")]
119pub struct CallToolResult {
120 pub content: Vec<ContentItem>,
121 #[serde(default)]
122 pub is_error: Option<bool>,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
131#[serde(tag = "type", rename_all = "lowercase")]
132pub enum ContentItem {
133 Text(TextContent),
134 Image(ImageContent),
135 Resource(ResourceContent),
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct TextContent {
141 pub text: String,
142}
143
144#[serde_as]
148#[derive(Debug, Clone, Serialize, Deserialize)]
149#[serde(rename_all = "camelCase")]
150pub struct ImageContent {
151 #[serde_as(as = "Base64")]
152 pub data: Vec<u8>,
153 pub mime_type: String,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct ResourceContent {
159 pub resource: EmbeddedResource,
160}
161
162#[serde_as]
166#[skip_serializing_none]
167#[derive(Debug, Clone, Serialize, Deserialize)]
168#[serde(rename_all = "camelCase")]
169pub struct EmbeddedResource {
170 pub uri: String,
171 #[serde(default)]
172 pub mime_type: Option<String>,
173 #[serde(default)]
174 pub text: Option<String>,
175 #[serde_as(as = "Option<Base64>")]
176 #[serde(default)]
177 pub blob: Option<Vec<u8>>,
178}
179
180pub fn error_kind_to_jsonrpc_code(kind: &str) -> i32 {
184 use crate::constants::*;
185 match kind {
186 ERR_NOT_FOUND => -32601,
187 ERR_INVALID_ARGS => -32602,
188 ERR_INTERNAL => -32603,
189 _ => -32000,
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use serde_json::json;
197
198 #[test]
199 fn tool_definition_deserialize() {
200 let json = json!({
201 "name": "get_weather",
202 "description": "Get weather",
203 "inputSchema": {
204 "type": "object",
205 "properties": { "city": { "type": "string" } }
206 },
207 "annotations": {
208 "readOnlyHint": true,
209 "destructiveHint": false
210 }
211 });
212 let tool: ToolDefinition = serde_json::from_value(json).unwrap();
213 assert_eq!(tool.name, "get_weather");
214 assert_eq!(tool.description.as_deref(), Some("Get weather"));
215 let ann = tool.annotations.unwrap();
216 assert_eq!(ann.read_only_hint, Some(true));
217 assert_eq!(ann.destructive_hint, Some(false));
218 assert_eq!(ann.idempotent_hint, None);
219 }
220
221 #[test]
222 fn tool_definition_minimal() {
223 let json = json!({ "name": "simple" });
224 let tool: ToolDefinition = serde_json::from_value(json).unwrap();
225 assert_eq!(tool.name, "simple");
226 assert_eq!(tool.input_schema, json!({"type": "object"}));
227 assert!(tool.annotations.is_none());
228 }
229
230 #[test]
231 fn tool_definition_omits_none_fields() {
232 let tool = ToolDefinition {
233 name: "x".to_string(),
234 description: None,
235 input_schema: default_object_schema(),
236 annotations: None,
237 };
238 let json = serde_json::to_string(&tool).unwrap();
239 assert!(!json.contains("\"description\""));
240 assert!(!json.contains("\"annotations\""));
241 }
242
243 #[test]
244 fn annotations_omits_none_hints() {
245 let ann = ToolAnnotations {
246 read_only_hint: Some(true),
247 ..Default::default()
248 };
249 let json = serde_json::to_string(&ann).unwrap();
250 assert!(json.contains("readOnlyHint"));
251 assert!(!json.contains("idempotentHint"));
252 assert!(!json.contains("destructiveHint"));
253 assert!(!json.contains("openWorldHint"));
254 }
255
256 #[test]
257 fn content_item_text() {
258 let item: ContentItem = serde_json::from_value(json!({
259 "type": "text",
260 "text": "hello"
261 }))
262 .unwrap();
263 match item {
264 ContentItem::Text(t) => assert_eq!(t.text, "hello"),
265 _ => panic!("expected text"),
266 }
267 }
268
269 #[test]
270 fn content_item_image_roundtrip() {
271 let original = ImageContent {
272 data: b"\x89PNG\r\n".to_vec(),
273 mime_type: "image/png".to_string(),
274 };
275 let json = serde_json::to_value(&ContentItem::Image(original.clone())).unwrap();
276 assert_eq!(json["data"], "iVBORw0K");
277 assert_eq!(json["mimeType"], "image/png");
278
279 let item: ContentItem = serde_json::from_value(json).unwrap();
280 match item {
281 ContentItem::Image(i) => {
282 assert_eq!(i.data, b"\x89PNG\r\n");
283 assert_eq!(i.mime_type, "image/png");
284 }
285 _ => panic!("expected image"),
286 }
287 }
288
289 #[test]
290 fn content_item_resource_text() {
291 let item: ContentItem = serde_json::from_value(json!({
292 "type": "resource",
293 "resource": {
294 "uri": "file:///tmp/test.txt",
295 "text": "contents",
296 "mimeType": "text/plain"
297 }
298 }))
299 .unwrap();
300 match item {
301 ContentItem::Resource(r) => {
302 assert_eq!(r.resource.uri, "file:///tmp/test.txt");
303 assert_eq!(r.resource.text.as_deref(), Some("contents"));
304 assert!(r.resource.blob.is_none());
305 }
306 _ => panic!("expected resource"),
307 }
308 }
309
310 #[test]
311 fn content_item_resource_blob_roundtrip() {
312 let resource = EmbeddedResource {
313 uri: "file:///tmp/data.bin".to_string(),
314 mime_type: Some("application/octet-stream".to_string()),
315 text: None,
316 blob: Some(b"\x00\x01\x02".to_vec()),
317 };
318 let json = serde_json::to_value(&ResourceContent { resource }).unwrap();
319 assert_eq!(json["resource"]["blob"], "AAEC");
320 assert!(json["resource"].get("text").is_none());
321
322 let item: ContentItem = serde_json::from_value(json!({
323 "type": "resource",
324 "resource": {
325 "uri": "file:///tmp/data.bin",
326 "blob": "AAEC",
327 "mimeType": "application/octet-stream"
328 }
329 }))
330 .unwrap();
331 match item {
332 ContentItem::Resource(r) => {
333 assert_eq!(r.resource.blob.as_deref(), Some(b"\x00\x01\x02".as_slice()));
334 }
335 _ => panic!("expected resource"),
336 }
337 }
338
339 #[test]
340 fn call_tool_result_with_error() {
341 let result: CallToolResult = serde_json::from_value(json!({
342 "content": [{ "type": "text", "text": "oops" }],
343 "isError": true
344 }))
345 .unwrap();
346 assert_eq!(result.is_error, Some(true));
347 assert_eq!(result.content.len(), 1);
348 }
349
350 #[test]
351 fn call_tool_result_omits_is_error_when_none() {
352 let result = CallToolResult {
353 content: vec![],
354 is_error: None,
355 };
356 let json = serde_json::to_string(&result).unwrap();
357 assert!(!json.contains("isError"));
358 }
359
360 #[test]
361 fn call_tool_params_serialize() {
362 let params = CallToolParams {
363 name: "test".to_string(),
364 arguments: Some(json!({"key": "value"})),
365 };
366 let json = serde_json::to_value(¶ms).unwrap();
367 assert_eq!(json["name"], "test");
368 assert_eq!(json["arguments"]["key"], "value");
369 }
370
371 #[test]
372 fn initialize_result_serialize() {
373 let result = InitializeResult {
374 protocol_version: "2025-11-25".to_string(),
375 server_info: ServerInfo {
376 name: "test".to_string(),
377 version: Some("1.0".to_string()),
378 },
379 capabilities: Some(ServerCapabilities {
380 tools: Some(json!({})),
381 ..Default::default()
382 }),
383 };
384 let json = serde_json::to_value(&result).unwrap();
385 assert_eq!(json["protocolVersion"], "2025-11-25");
386 assert_eq!(json["serverInfo"]["name"], "test");
387 assert!(json["capabilities"]["tools"].is_object());
388 assert!(json["capabilities"].get("resources").is_none());
389 }
390}