1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6pub const JSONRPC_VERSION: &str = "2.0";
8
9pub const PREFERRED_PROTOCOL_VERSION: &str = "2025-06-18";
18
19pub const MIN_PROTOCOL_VERSION: &str = "2024-11-05";
24
25pub const SUPPORTED_PROTOCOL_VERSIONS: &[&str] = &["2025-06-18", "2025-03-26", "2024-11-05"];
32
33#[must_use]
35pub fn is_known_protocol_version(version: &str) -> bool {
36 SUPPORTED_PROTOCOL_VERSIONS.contains(&version)
37}
38
39#[derive(Clone, Debug, Serialize, Deserialize)]
41pub struct JsonRpcRequest {
42 pub jsonrpc: String,
44 pub method: String,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub params: Option<Value>,
49 pub id: RequestId,
51}
52
53impl JsonRpcRequest {
54 #[must_use]
56 pub fn new(method: impl Into<String>, params: Option<Value>, id: u64) -> Self {
57 Self {
58 jsonrpc: JSONRPC_VERSION.to_string(),
59 method: method.into(),
60 params,
61 id: RequestId::Number(id),
62 }
63 }
64}
65
66#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
68#[serde(untagged)]
69pub enum RequestId {
70 Number(u64),
72 String(String),
74}
75
76#[derive(Clone, Debug, Serialize, Deserialize)]
78pub struct JsonRpcResponse {
79 pub jsonrpc: String,
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub result: Option<Value>,
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub error: Option<JsonRpcError>,
87 pub id: RequestId,
89}
90
91impl JsonRpcResponse {
92 #[must_use]
94 pub const fn is_error(&self) -> bool {
95 self.error.is_some()
96 }
97
98 #[must_use]
100 pub const fn result(&self) -> Option<&Value> {
101 self.result.as_ref()
102 }
103}
104
105#[derive(Clone, Debug, Serialize, Deserialize)]
107pub struct JsonRpcError {
108 pub code: i32,
110 pub message: String,
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub data: Option<Value>,
115}
116
117pub mod error_codes {
119 pub const PARSE_ERROR: i32 = -32700;
121 pub const INVALID_REQUEST: i32 = -32600;
123 pub const METHOD_NOT_FOUND: i32 = -32601;
125 pub const INVALID_PARAMS: i32 = -32602;
127 pub const INTERNAL_ERROR: i32 = -32603;
129}
130
131#[derive(Clone, Debug, Serialize, Deserialize)]
133pub struct McpToolDefinition {
134 pub name: String,
136 #[serde(default)]
138 pub description: Option<String>,
139 #[serde(rename = "inputSchema")]
141 pub input_schema: Value,
142}
143
144#[derive(Clone, Debug, Serialize, Deserialize)]
146pub struct McpToolCallResult {
147 pub content: Vec<McpContent>,
149 #[serde(default, rename = "isError")]
151 pub is_error: bool,
152}
153
154#[derive(Clone, Debug, Serialize, Deserialize)]
156#[serde(tag = "type")]
157pub enum McpContent {
158 #[serde(rename = "text")]
160 Text {
161 text: String,
163 },
164 #[serde(rename = "image")]
166 Image {
167 data: String,
169 #[serde(rename = "mimeType")]
171 mime_type: String,
172 },
173 #[serde(rename = "resource")]
175 Resource {
176 uri: String,
178 #[serde(rename = "mimeType")]
180 mime_type: Option<String>,
181 text: Option<String>,
183 },
184}
185
186#[derive(Clone, Debug, Default, Serialize, Deserialize)]
188pub struct McpServerCapabilities {
189 #[serde(default)]
191 pub tools: Option<McpToolsCapability>,
192 #[serde(default)]
194 pub resources: Option<McpResourcesCapability>,
195 #[serde(default)]
197 pub prompts: Option<McpPromptsCapability>,
198}
199
200#[derive(Clone, Debug, Default, Serialize, Deserialize)]
202pub struct McpToolsCapability {
203 #[serde(default, rename = "listChanged")]
205 pub list_changed: bool,
206}
207
208#[derive(Clone, Debug, Default, Serialize, Deserialize)]
210pub struct McpResourcesCapability {
211 #[serde(default)]
213 pub subscribe: bool,
214 #[serde(default, rename = "listChanged")]
216 pub list_changed: bool,
217}
218
219#[derive(Clone, Debug, Default, Serialize, Deserialize)]
221pub struct McpPromptsCapability {
222 #[serde(default, rename = "listChanged")]
224 pub list_changed: bool,
225}
226
227#[derive(Clone, Debug, Serialize, Deserialize)]
229pub struct InitializeParams {
230 #[serde(rename = "protocolVersion")]
232 pub protocol_version: String,
233 pub capabilities: ClientCapabilities,
235 #[serde(rename = "clientInfo")]
237 pub client_info: ClientInfo,
238}
239
240#[derive(Clone, Debug, Default, Serialize, Deserialize)]
242pub struct ClientCapabilities {
243 #[serde(default)]
245 pub roots: Option<RootsCapability>,
246 #[serde(default)]
248 pub sampling: Option<SamplingCapability>,
249}
250
251#[derive(Clone, Debug, Default, Serialize, Deserialize)]
253pub struct RootsCapability {
254 #[serde(default, rename = "listChanged")]
256 pub list_changed: bool,
257}
258
259#[derive(Clone, Debug, Default, Serialize, Deserialize)]
261pub struct SamplingCapability {}
262
263#[derive(Clone, Debug, Serialize, Deserialize)]
265pub struct ClientInfo {
266 pub name: String,
268 pub version: String,
270}
271
272#[derive(Clone, Debug, Serialize, Deserialize)]
274pub struct InitializeResult {
275 #[serde(rename = "protocolVersion")]
277 pub protocol_version: String,
278 pub capabilities: McpServerCapabilities,
280 #[serde(rename = "serverInfo")]
282 pub server_info: ServerInfo,
283}
284
285#[derive(Clone, Debug, Serialize, Deserialize)]
287pub struct ServerInfo {
288 pub name: String,
290 #[serde(default)]
292 pub version: Option<String>,
293}
294
295#[derive(Clone, Debug, Serialize, Deserialize)]
297pub struct ToolsListResult {
298 pub tools: Vec<McpToolDefinition>,
300}
301
302#[derive(Clone, Debug, Serialize, Deserialize)]
304pub struct ToolCallParams {
305 pub name: String,
307 #[serde(default)]
309 pub arguments: Option<Value>,
310}
311
312#[derive(Clone, Debug, Serialize, Deserialize)]
316pub struct McpResource {
317 pub uri: String,
319 #[serde(default)]
321 pub name: Option<String>,
322 #[serde(default)]
324 pub description: Option<String>,
325 #[serde(default, rename = "mimeType")]
327 pub mime_type: Option<String>,
328}
329
330#[derive(Clone, Debug, Serialize, Deserialize)]
332pub struct ResourcesListResult {
333 pub resources: Vec<McpResource>,
335 #[serde(default, rename = "nextCursor")]
337 pub next_cursor: Option<String>,
338}
339
340#[derive(Clone, Debug, Serialize, Deserialize)]
342pub struct ResourceReadParams {
343 pub uri: String,
345}
346
347#[derive(Clone, Debug, Serialize, Deserialize)]
349pub struct McpResourceContents {
350 pub uri: String,
352 #[serde(default, rename = "mimeType")]
354 pub mime_type: Option<String>,
355 #[serde(default)]
357 pub text: Option<String>,
358 #[serde(default)]
360 pub blob: Option<String>,
361}
362
363#[derive(Clone, Debug, Serialize, Deserialize)]
365pub struct ResourceReadResult {
366 pub contents: Vec<McpResourceContents>,
368}
369
370#[derive(Clone, Debug, Serialize, Deserialize)]
374pub struct McpPromptArgument {
375 pub name: String,
377 #[serde(default)]
379 pub description: Option<String>,
380 #[serde(default)]
382 pub required: bool,
383}
384
385#[derive(Clone, Debug, Serialize, Deserialize)]
387pub struct McpPrompt {
388 pub name: String,
390 #[serde(default)]
392 pub description: Option<String>,
393 #[serde(default)]
395 pub arguments: Vec<McpPromptArgument>,
396}
397
398#[derive(Clone, Debug, Serialize, Deserialize)]
400pub struct PromptsListResult {
401 pub prompts: Vec<McpPrompt>,
403 #[serde(default, rename = "nextCursor")]
405 pub next_cursor: Option<String>,
406}
407
408#[derive(Clone, Debug, Serialize, Deserialize)]
410pub struct PromptGetParams {
411 pub name: String,
413 #[serde(default, skip_serializing_if = "Option::is_none")]
415 pub arguments: Option<Value>,
416}
417
418#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
420#[serde(rename_all = "lowercase")]
421pub enum McpRole {
422 User,
424 Assistant,
426}
427
428#[derive(Clone, Debug, Serialize, Deserialize)]
430pub struct McpPromptMessage {
431 pub role: McpRole,
433 pub content: McpContent,
435}
436
437#[derive(Clone, Debug, Serialize, Deserialize)]
439pub struct PromptGetResult {
440 #[serde(default)]
442 pub description: Option<String>,
443 pub messages: Vec<McpPromptMessage>,
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450
451 #[test]
452 fn test_json_rpc_request_serialization() {
453 let request =
454 JsonRpcRequest::new("test_method", Some(serde_json::json!({"key": "value"})), 1);
455
456 let json = serde_json::to_string(&request).expect("serialize");
457 assert!(json.contains("test_method"));
458 assert!(json.contains("2.0"));
459 }
460
461 #[test]
462 fn test_json_rpc_response_success() {
463 let response = JsonRpcResponse {
464 jsonrpc: JSONRPC_VERSION.to_string(),
465 result: Some(serde_json::json!({"success": true})),
466 error: None,
467 id: RequestId::Number(1),
468 };
469
470 assert!(!response.is_error());
471 assert!(response.result().is_some());
472 }
473
474 #[test]
475 fn test_json_rpc_response_error() {
476 let response = JsonRpcResponse {
477 jsonrpc: JSONRPC_VERSION.to_string(),
478 result: None,
479 error: Some(JsonRpcError {
480 code: error_codes::METHOD_NOT_FOUND,
481 message: "Method not found".to_string(),
482 data: None,
483 }),
484 id: RequestId::Number(1),
485 };
486
487 assert!(response.is_error());
488 assert!(response.result().is_none());
489 }
490
491 #[test]
492 fn test_mcp_tool_definition_deserialization() {
493 let json = r#"{
494 "name": "test_tool",
495 "description": "A test tool",
496 "inputSchema": {
497 "type": "object",
498 "properties": {}
499 }
500 }"#;
501
502 let tool: McpToolDefinition = serde_json::from_str(json).expect("deserialize");
503 assert_eq!(tool.name, "test_tool");
504 assert_eq!(tool.description.as_deref(), Some("A test tool"));
505 }
506
507 #[test]
508 fn test_mcp_content_text() {
509 let content = McpContent::Text {
510 text: "Hello".to_string(),
511 };
512
513 let json = serde_json::to_string(&content).expect("serialize");
514 assert!(json.contains("text"));
515 assert!(json.contains("Hello"));
516 }
517
518 #[test]
519 fn test_request_id_variants() {
520 let num_id = RequestId::Number(42);
521 let str_id = RequestId::String("req-1".to_string());
522
523 let json_num = serde_json::to_string(&num_id).expect("serialize");
524 let json_str = serde_json::to_string(&str_id).expect("serialize");
525
526 assert_eq!(json_num, "42");
527 assert_eq!(json_str, "\"req-1\"");
528 }
529
530 #[test]
531 fn preferred_protocol_version_is_newer_than_floor() {
532 assert_ne!(PREFERRED_PROTOCOL_VERSION, MIN_PROTOCOL_VERSION);
535 assert!(is_known_protocol_version(PREFERRED_PROTOCOL_VERSION));
536 assert!(is_known_protocol_version(MIN_PROTOCOL_VERSION));
537 assert!(!is_known_protocol_version("1999-01-01"));
538 assert_eq!(SUPPORTED_PROTOCOL_VERSIONS[0], PREFERRED_PROTOCOL_VERSION);
540 }
541
542 #[test]
543 fn test_resources_list_deserialization() {
544 let json = r#"{
545 "resources": [
546 {"uri": "file:///a.txt", "name": "A", "mimeType": "text/plain"},
547 {"uri": "mem://b"}
548 ],
549 "nextCursor": "page2"
550 }"#;
551 let parsed: ResourcesListResult = serde_json::from_str(json).expect("deserialize");
552 assert_eq!(parsed.resources.len(), 2);
553 assert_eq!(parsed.resources[0].uri, "file:///a.txt");
554 assert_eq!(parsed.resources[0].mime_type.as_deref(), Some("text/plain"));
555 assert_eq!(parsed.resources[1].name, None);
556 assert_eq!(parsed.next_cursor.as_deref(), Some("page2"));
557 }
558
559 #[test]
560 fn test_resource_read_text_and_blob() {
561 let json = r#"{
562 "contents": [
563 {"uri": "file:///a.txt", "mimeType": "text/plain", "text": "hello"},
564 {"uri": "file:///b.bin", "blob": "AAAA"}
565 ]
566 }"#;
567 let parsed: ResourceReadResult = serde_json::from_str(json).expect("deserialize");
568 assert_eq!(parsed.contents.len(), 2);
569 assert_eq!(parsed.contents[0].text.as_deref(), Some("hello"));
570 assert_eq!(parsed.contents[1].blob.as_deref(), Some("AAAA"));
571 assert_eq!(parsed.contents[1].text, None);
572 }
573
574 #[test]
575 fn test_prompts_list_deserialization() {
576 let json = r#"{
577 "prompts": [
578 {
579 "name": "summarize",
580 "description": "Summarize text",
581 "arguments": [
582 {"name": "text", "required": true},
583 {"name": "tone", "description": "voice", "required": false}
584 ]
585 }
586 ]
587 }"#;
588 let parsed: PromptsListResult = serde_json::from_str(json).expect("deserialize");
589 assert_eq!(parsed.prompts.len(), 1);
590 assert_eq!(parsed.prompts[0].name, "summarize");
591 assert_eq!(parsed.prompts[0].arguments.len(), 2);
592 assert!(parsed.prompts[0].arguments[0].required);
593 assert!(!parsed.prompts[0].arguments[1].required);
594 }
595
596 #[test]
597 fn test_prompt_get_messages() {
598 let json = r#"{
599 "description": "rendered",
600 "messages": [
601 {"role": "user", "content": {"type": "text", "text": "hi"}},
602 {"role": "assistant", "content": {"type": "text", "text": "hello"}}
603 ]
604 }"#;
605 let parsed: PromptGetResult = serde_json::from_str(json).expect("deserialize");
606 assert_eq!(parsed.messages.len(), 2);
607 assert_eq!(parsed.messages[0].role, McpRole::User);
608 assert_eq!(parsed.messages[1].role, McpRole::Assistant);
609 match &parsed.messages[0].content {
610 McpContent::Text { text } => assert_eq!(text, "hi"),
611 _ => panic!("expected text content"),
612 }
613 }
614}