Skip to main content

a3s_code_core/mcp/
protocol.rs

1//! MCP Protocol Type Definitions
2//!
3//! Defines the core types for the Model Context Protocol (MCP).
4//! Based on the MCP specification: https://spec.modelcontextprotocol.io/
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// MCP protocol version
10pub const PROTOCOL_VERSION: &str = "2024-11-05";
11
12/// JSON-RPC request
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct JsonRpcRequest {
15    pub jsonrpc: String,
16    pub id: u64,
17    pub method: String,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub params: Option<serde_json::Value>,
20}
21
22impl JsonRpcRequest {
23    pub fn new(id: u64, method: &str, params: Option<serde_json::Value>) -> Self {
24        Self {
25            jsonrpc: "2.0".to_string(),
26            id,
27            method: method.to_string(),
28            params,
29        }
30    }
31}
32
33/// JSON-RPC response
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct JsonRpcResponse {
36    pub jsonrpc: String,
37    pub id: Option<u64>,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub result: Option<serde_json::Value>,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub error: Option<JsonRpcError>,
42}
43
44/// JSON-RPC error
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct JsonRpcError {
47    pub code: i32,
48    pub message: String,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub data: Option<serde_json::Value>,
51}
52
53/// JSON-RPC notification (no id)
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct JsonRpcNotification {
56    pub jsonrpc: String,
57    pub method: String,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub params: Option<serde_json::Value>,
60}
61
62impl JsonRpcNotification {
63    pub fn new(method: &str, params: Option<serde_json::Value>) -> Self {
64        Self {
65            jsonrpc: "2.0".to_string(),
66            method: method.to_string(),
67            params,
68        }
69    }
70}
71
72// ============================================================================
73// MCP Initialize
74// ============================================================================
75
76/// Client capabilities
77#[derive(Debug, Clone, Default, Serialize, Deserialize)]
78pub struct ClientCapabilities {
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub roots: Option<RootsCapability>,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub sampling: Option<SamplingCapability>,
83}
84
85#[derive(Debug, Clone, Default, Serialize, Deserialize)]
86pub struct RootsCapability {
87    #[serde(default)]
88    pub list_changed: bool,
89}
90
91#[derive(Debug, Clone, Default, Serialize, Deserialize)]
92pub struct SamplingCapability {}
93
94/// Client info
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct ClientInfo {
97    pub name: String,
98    pub version: String,
99}
100
101/// Initialize request params
102#[derive(Debug, Clone, Serialize, Deserialize)]
103#[serde(rename_all = "camelCase")]
104pub struct InitializeParams {
105    pub protocol_version: String,
106    pub capabilities: ClientCapabilities,
107    pub client_info: ClientInfo,
108}
109
110/// Server capabilities
111#[derive(Debug, Clone, Default, Serialize, Deserialize)]
112pub struct ServerCapabilities {
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub tools: Option<ToolsCapability>,
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub resources: Option<ResourcesCapability>,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub prompts: Option<PromptsCapability>,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub logging: Option<LoggingCapability>,
121}
122
123#[derive(Debug, Clone, Default, Serialize, Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub struct ToolsCapability {
126    #[serde(default)]
127    pub list_changed: bool,
128}
129
130#[derive(Debug, Clone, Default, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct ResourcesCapability {
133    #[serde(default)]
134    pub subscribe: bool,
135    #[serde(default)]
136    pub list_changed: bool,
137}
138
139#[derive(Debug, Clone, Default, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct PromptsCapability {
142    #[serde(default)]
143    pub list_changed: bool,
144}
145
146#[derive(Debug, Clone, Default, Serialize, Deserialize)]
147pub struct LoggingCapability {}
148
149/// Server info
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct ServerInfo {
152    pub name: String,
153    pub version: String,
154}
155
156/// Initialize result
157#[derive(Debug, Clone, Serialize, Deserialize)]
158#[serde(rename_all = "camelCase")]
159pub struct InitializeResult {
160    pub protocol_version: String,
161    pub capabilities: ServerCapabilities,
162    pub server_info: ServerInfo,
163}
164
165// ============================================================================
166// MCP Tools
167// ============================================================================
168
169/// MCP tool definition
170#[derive(Debug, Clone, Serialize, Deserialize)]
171#[serde(rename_all = "camelCase")]
172pub struct McpTool {
173    pub name: String,
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub description: Option<String>,
176    pub input_schema: serde_json::Value,
177}
178
179/// List tools result
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct ListToolsResult {
182    pub tools: Vec<McpTool>,
183}
184
185/// Call tool params
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct CallToolParams {
188    pub name: String,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub arguments: Option<serde_json::Value>,
191}
192
193/// Tool content types
194#[derive(Debug, Clone, Serialize, Deserialize)]
195#[serde(tag = "type", rename_all = "lowercase")]
196pub enum ToolContent {
197    Text {
198        text: String,
199    },
200    Image {
201        data: String,
202        #[serde(rename = "mimeType")]
203        mime_type: String,
204    },
205    Resource {
206        resource: ResourceContent,
207    },
208}
209
210/// Resource content
211#[derive(Debug, Clone, Serialize, Deserialize)]
212#[serde(rename_all = "camelCase")]
213pub struct ResourceContent {
214    pub uri: String,
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub mime_type: Option<String>,
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub text: Option<String>,
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub blob: Option<String>,
221}
222
223/// Call tool result
224#[derive(Debug, Clone, Serialize, Deserialize)]
225#[serde(rename_all = "camelCase")]
226pub struct CallToolResult {
227    pub content: Vec<ToolContent>,
228    #[serde(default)]
229    pub is_error: bool,
230}
231
232// ============================================================================
233// MCP Resources
234// ============================================================================
235
236/// MCP resource definition
237#[derive(Debug, Clone, Serialize, Deserialize)]
238#[serde(rename_all = "camelCase")]
239pub struct McpResource {
240    pub uri: String,
241    pub name: String,
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub description: Option<String>,
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub mime_type: Option<String>,
246}
247
248/// List resources result
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct ListResourcesResult {
251    pub resources: Vec<McpResource>,
252}
253
254/// Read resource params
255#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct ReadResourceParams {
257    pub uri: String,
258}
259
260/// Read resource result
261#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct ReadResourceResult {
263    pub contents: Vec<ResourceContent>,
264}
265
266// ============================================================================
267// MCP Prompts
268// ============================================================================
269
270/// MCP prompt definition
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct McpPrompt {
273    pub name: String,
274    #[serde(skip_serializing_if = "Option::is_none")]
275    pub description: Option<String>,
276    #[serde(skip_serializing_if = "Option::is_none")]
277    pub arguments: Option<Vec<PromptArgument>>,
278}
279
280/// Prompt argument
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct PromptArgument {
283    pub name: String,
284    #[serde(skip_serializing_if = "Option::is_none")]
285    pub description: Option<String>,
286    #[serde(default)]
287    pub required: bool,
288}
289
290/// List prompts result
291#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct ListPromptsResult {
293    pub prompts: Vec<McpPrompt>,
294}
295
296// ============================================================================
297// MCP Notifications
298// ============================================================================
299
300/// MCP notification types
301#[derive(Debug, Clone)]
302pub enum McpNotification {
303    ToolsListChanged,
304    ResourcesListChanged,
305    PromptsListChanged,
306    Progress {
307        progress_token: String,
308        progress: f64,
309        total: Option<f64>,
310    },
311    Log {
312        level: String,
313        logger: Option<String>,
314        data: serde_json::Value,
315    },
316    Unknown {
317        method: String,
318        params: Option<serde_json::Value>,
319    },
320}
321
322impl McpNotification {
323    pub fn from_json_rpc(notification: &JsonRpcNotification) -> Self {
324        match notification.method.as_str() {
325            "notifications/tools/list_changed" => McpNotification::ToolsListChanged,
326            "notifications/resources/list_changed" => McpNotification::ResourcesListChanged,
327            "notifications/prompts/list_changed" => McpNotification::PromptsListChanged,
328            "notifications/progress" => {
329                if let Some(params) = &notification.params {
330                    let progress_token = params
331                        .get("progressToken")
332                        .and_then(|v| v.as_str())
333                        .unwrap_or("")
334                        .to_string();
335                    let progress = params
336                        .get("progress")
337                        .and_then(|v| v.as_f64())
338                        .unwrap_or(0.0);
339                    let total = params.get("total").and_then(|v| v.as_f64());
340                    McpNotification::Progress {
341                        progress_token,
342                        progress,
343                        total,
344                    }
345                } else {
346                    McpNotification::Unknown {
347                        method: notification.method.clone(),
348                        params: notification.params.clone(),
349                    }
350                }
351            }
352            "notifications/message" => {
353                if let Some(params) = &notification.params {
354                    let level = params
355                        .get("level")
356                        .and_then(|v| v.as_str())
357                        .unwrap_or("info")
358                        .to_string();
359                    let logger = params
360                        .get("logger")
361                        .and_then(|v| v.as_str())
362                        .map(|s| s.to_string());
363                    let data = params
364                        .get("data")
365                        .cloned()
366                        .unwrap_or(serde_json::Value::Null);
367                    McpNotification::Log {
368                        level,
369                        logger,
370                        data,
371                    }
372                } else {
373                    McpNotification::Unknown {
374                        method: notification.method.clone(),
375                        params: notification.params.clone(),
376                    }
377                }
378            }
379            _ => McpNotification::Unknown {
380                method: notification.method.clone(),
381                params: notification.params.clone(),
382            },
383        }
384    }
385}
386
387// ============================================================================
388// Configuration Types
389// ============================================================================
390
391/// MCP server configuration
392#[derive(Debug, Clone, Serialize, Deserialize)]
393pub struct McpServerConfig {
394    /// Server name (used for tool prefix)
395    pub name: String,
396    /// Transport configuration
397    pub transport: McpTransportConfig,
398    /// Whether enabled
399    #[serde(default = "default_true")]
400    pub enabled: bool,
401    /// Environment variables
402    #[serde(default)]
403    pub env: HashMap<String, String>,
404    /// OAuth configuration (optional)
405    #[serde(skip_serializing_if = "Option::is_none")]
406    pub oauth: Option<OAuthConfig>,
407    /// Per-tool execution timeout in seconds (default: 60)
408    #[serde(default = "default_tool_timeout")]
409    pub tool_timeout_secs: u64,
410}
411
412fn default_tool_timeout() -> u64 {
413    60
414}
415
416fn default_true() -> bool {
417    true
418}
419
420/// Transport configuration
421#[derive(Debug, Clone, Serialize, Deserialize)]
422#[serde(tag = "type", rename_all = "lowercase")]
423pub enum McpTransportConfig {
424    /// Local process (stdio)
425    Stdio {
426        command: String,
427        #[serde(default)]
428        args: Vec<String>,
429    },
430    /// Remote HTTP + SSE
431    Http {
432        url: String,
433        #[serde(default)]
434        headers: HashMap<String, String>,
435    },
436}
437
438/// OAuth configuration
439#[derive(Debug, Clone, Serialize, Deserialize)]
440pub struct OAuthConfig {
441    pub auth_url: String,
442    pub token_url: String,
443    pub client_id: String,
444    #[serde(skip_serializing_if = "Option::is_none")]
445    pub client_secret: Option<String>,
446    #[serde(default)]
447    pub scopes: Vec<String>,
448    pub redirect_uri: String,
449}
450
451// ============================================================================
452// Tests
453// ============================================================================
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458
459    #[test]
460    fn test_json_rpc_request_serialize() {
461        let req = JsonRpcRequest::new(1, "initialize", Some(serde_json::json!({"test": true})));
462        let json = serde_json::to_string(&req).unwrap();
463        assert!(json.contains("\"jsonrpc\":\"2.0\""));
464        assert!(json.contains("\"id\":1"));
465        assert!(json.contains("\"method\":\"initialize\""));
466    }
467
468    #[test]
469    fn test_json_rpc_response_deserialize() {
470        let json = r#"{"jsonrpc":"2.0","id":1,"result":{"success":true}}"#;
471        let resp: JsonRpcResponse = serde_json::from_str(json).unwrap();
472        assert_eq!(resp.id, Some(1));
473        assert!(resp.result.is_some());
474        assert!(resp.error.is_none());
475    }
476
477    #[test]
478    fn test_json_rpc_error_deserialize() {
479        let json =
480            r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid Request"}}"#;
481        let resp: JsonRpcResponse = serde_json::from_str(json).unwrap();
482        assert!(resp.error.is_some());
483        let err = resp.error.unwrap();
484        assert_eq!(err.code, -32600);
485    }
486
487    #[test]
488    fn test_mcp_tool_deserialize() {
489        let json = r#"{
490            "name": "create_issue",
491            "description": "Create a GitHub issue",
492            "inputSchema": {
493                "type": "object",
494                "properties": {
495                    "title": {"type": "string"},
496                    "body": {"type": "string"}
497                },
498                "required": ["title"]
499            }
500        }"#;
501        let tool: McpTool = serde_json::from_str(json).unwrap();
502        assert_eq!(tool.name, "create_issue");
503        assert!(tool.description.is_some());
504    }
505
506    #[test]
507    fn test_tool_content_text() {
508        let content = ToolContent::Text {
509            text: "Hello".to_string(),
510        };
511        let json = serde_json::to_string(&content).unwrap();
512        assert!(json.contains("\"type\":\"text\""));
513        assert!(json.contains("\"text\":\"Hello\""));
514    }
515
516    #[test]
517    fn test_mcp_transport_config_stdio() {
518        let json = r#"{
519            "type": "stdio",
520            "command": "npx",
521            "args": ["-y", "@modelcontextprotocol/server-github"]
522        }"#;
523        let config: McpTransportConfig = serde_json::from_str(json).unwrap();
524        match config {
525            McpTransportConfig::Stdio { command, args } => {
526                assert_eq!(command, "npx");
527                assert_eq!(args.len(), 2);
528            }
529            _ => panic!("Expected Stdio transport"),
530        }
531    }
532
533    #[test]
534    fn test_mcp_transport_config_http() {
535        let json = r#"{
536            "type": "http",
537            "url": "https://mcp.example.com/api",
538            "headers": {"Authorization": "Bearer token"}
539        }"#;
540        let config: McpTransportConfig = serde_json::from_str(json).unwrap();
541        match config {
542            McpTransportConfig::Http { url, headers } => {
543                assert_eq!(url, "https://mcp.example.com/api");
544                assert!(headers.contains_key("Authorization"));
545            }
546            _ => panic!("Expected Http transport"),
547        }
548    }
549
550    #[test]
551    fn test_mcp_notification_parse() {
552        let notification = JsonRpcNotification::new("notifications/tools/list_changed", None);
553        let mcp_notif = McpNotification::from_json_rpc(&notification);
554        match mcp_notif {
555            McpNotification::ToolsListChanged => {}
556            _ => panic!("Expected ToolsListChanged"),
557        }
558    }
559    #[test]
560    fn test_json_rpc_request_new_with_params() {
561        let req = JsonRpcRequest::new(1, "initialize", Some(serde_json::json!({"test": true})));
562        assert_eq!(req.jsonrpc, "2.0");
563        assert_eq!(req.id, 1);
564        assert_eq!(req.method, "initialize");
565        assert!(req.params.is_some());
566    }
567
568    #[test]
569    fn test_json_rpc_request_new_without_params() {
570        let req = JsonRpcRequest::new(2, "ping", None);
571        assert_eq!(req.jsonrpc, "2.0");
572        assert_eq!(req.id, 2);
573        assert_eq!(req.method, "ping");
574        assert!(req.params.is_none());
575    }
576
577    #[test]
578    fn test_json_rpc_request_serialization() {
579        let req = JsonRpcRequest::new(1, "test_method", Some(serde_json::json!({"key": "value"})));
580        let json = serde_json::to_string(&req).unwrap();
581        assert!(json.contains("\"jsonrpc\":\"2.0\""));
582        assert!(json.contains("\"id\":1"));
583        assert!(json.contains("\"method\":\"test_method\""));
584        assert!(json.contains("\"params\""));
585    }
586
587    #[test]
588    fn test_json_rpc_response_with_result() {
589        let resp = JsonRpcResponse {
590            jsonrpc: "2.0".to_string(),
591            id: Some(1),
592            result: Some(serde_json::json!({"success": true})),
593            error: None,
594        };
595        assert!(resp.result.is_some());
596        assert!(resp.error.is_none());
597    }
598
599    #[test]
600    fn test_json_rpc_response_with_error() {
601        let resp = JsonRpcResponse {
602            jsonrpc: "2.0".to_string(),
603            id: Some(1),
604            result: None,
605            error: Some(JsonRpcError {
606                code: -32600,
607                message: "Invalid Request".to_string(),
608                data: None,
609            }),
610        };
611        assert!(resp.result.is_none());
612        assert!(resp.error.is_some());
613    }
614
615    #[test]
616    fn test_json_rpc_response_both_none() {
617        let resp = JsonRpcResponse {
618            jsonrpc: "2.0".to_string(),
619            id: Some(1),
620            result: None,
621            error: None,
622        };
623        assert!(resp.result.is_none());
624        assert!(resp.error.is_none());
625    }
626
627    #[test]
628    fn test_json_rpc_response_serialization() {
629        let resp = JsonRpcResponse {
630            jsonrpc: "2.0".to_string(),
631            id: Some(1),
632            result: Some(serde_json::json!({"data": "test"})),
633            error: None,
634        };
635        let json = serde_json::to_string(&resp).unwrap();
636        assert!(json.contains("\"jsonrpc\":\"2.0\""));
637        assert!(json.contains("\"id\":1"));
638        assert!(json.contains("\"result\""));
639    }
640
641    #[test]
642    fn test_json_rpc_notification_new_with_params() {
643        let notif =
644            JsonRpcNotification::new("notification", Some(serde_json::json!({"msg": "hello"})));
645        assert_eq!(notif.jsonrpc, "2.0");
646        assert_eq!(notif.method, "notification");
647        assert!(notif.params.is_some());
648    }
649
650    #[test]
651    fn test_json_rpc_notification_new_without_params() {
652        let notif = JsonRpcNotification::new("ping", None);
653        assert_eq!(notif.jsonrpc, "2.0");
654        assert_eq!(notif.method, "ping");
655        assert!(notif.params.is_none());
656    }
657
658    #[test]
659    fn test_json_rpc_notification_serialization() {
660        let notif = JsonRpcNotification::new(
661            "test_notification",
662            Some(serde_json::json!({"key": "value"})),
663        );
664        let json = serde_json::to_string(&notif).unwrap();
665        assert!(json.contains("\"jsonrpc\":\"2.0\""));
666        assert!(json.contains("\"method\":\"test_notification\""));
667        assert!(!json.contains("\"id\""));
668    }
669
670    #[test]
671    fn test_mcp_tool_serialize() {
672        let tool = McpTool {
673            name: "test_tool".to_string(),
674            description: Some("A test tool".to_string()),
675            input_schema: serde_json::json!({"type": "object"}),
676        };
677        let json = serde_json::to_string(&tool).unwrap();
678        assert!(json.contains("\"name\":\"test_tool\""));
679        assert!(json.contains("\"description\":\"A test tool\""));
680    }
681
682    #[test]
683    fn test_mcp_tool_without_description() {
684        let json = r#"{"name":"tool","inputSchema":{"type":"object"}}"#;
685        let tool: McpTool = serde_json::from_str(json).unwrap();
686        assert_eq!(tool.name, "tool");
687        assert!(tool.description.is_none());
688    }
689
690    #[test]
691    fn test_mcp_resource_serialize() {
692        let resource = McpResource {
693            uri: "file:///test.txt".to_string(),
694            name: "test.txt".to_string(),
695            description: Some("Test file".to_string()),
696            mime_type: Some("text/plain".to_string()),
697        };
698        let json = serde_json::to_string(&resource).unwrap();
699        assert!(json.contains("\"uri\":\"file:///test.txt\""));
700        assert!(json.contains("\"name\":\"test.txt\""));
701    }
702
703    #[test]
704    fn test_mcp_resource_deserialize() {
705        let json = r#"{"uri":"file:///doc.md","name":"doc.md","mimeType":"text/markdown"}"#;
706        let resource: McpResource = serde_json::from_str(json).unwrap();
707        assert_eq!(resource.uri, "file:///doc.md");
708        assert_eq!(resource.name, "doc.md");
709        assert_eq!(resource.mime_type, Some("text/markdown".to_string()));
710    }
711
712    #[test]
713    fn test_initialize_params_serialization() {
714        let params = InitializeParams {
715            protocol_version: PROTOCOL_VERSION.to_string(),
716            capabilities: ClientCapabilities::default(),
717            client_info: ClientInfo {
718                name: "test-client".to_string(),
719                version: "1.0.0".to_string(),
720            },
721        };
722        let json = serde_json::to_string(&params).unwrap();
723        assert!(json.contains("\"protocolVersion\""));
724        assert!(json.contains("\"clientInfo\""));
725    }
726
727    #[test]
728    fn test_initialize_result_serialization() {
729        let result = InitializeResult {
730            protocol_version: PROTOCOL_VERSION.to_string(),
731            capabilities: ServerCapabilities::default(),
732            server_info: ServerInfo {
733                name: "test-server".to_string(),
734                version: "1.0.0".to_string(),
735            },
736        };
737        let json = serde_json::to_string(&result).unwrap();
738        assert!(json.contains("\"protocolVersion\""));
739        assert!(json.contains("\"serverInfo\""));
740    }
741
742    #[test]
743    fn test_call_tool_params_serialization() {
744        let params = CallToolParams {
745            name: "test_tool".to_string(),
746            arguments: Some(serde_json::json!({"arg1": "value1"})),
747        };
748        let json = serde_json::to_string(&params).unwrap();
749        assert!(json.contains("\"name\":\"test_tool\""));
750        assert!(json.contains("\"arguments\""));
751    }
752
753    #[test]
754    fn test_call_tool_params_without_arguments() {
755        let params = CallToolParams {
756            name: "simple_tool".to_string(),
757            arguments: None,
758        };
759        let json = serde_json::to_string(&params).unwrap();
760        assert!(json.contains("\"name\":\"simple_tool\""));
761        assert!(!json.contains("\"arguments\""));
762    }
763
764    #[test]
765    fn test_call_tool_result_serialization() {
766        let result = CallToolResult {
767            content: vec![ToolContent::Text {
768                text: "Result".to_string(),
769            }],
770            is_error: false,
771        };
772        let json = serde_json::to_string(&result).unwrap();
773        assert!(json.contains("\"content\""));
774        assert!(json.contains("\"isError\":false"));
775    }
776
777    #[test]
778    fn test_call_tool_result_error_flag() {
779        let result = CallToolResult {
780            content: vec![ToolContent::Text {
781                text: "Error occurred".to_string(),
782            }],
783            is_error: true,
784        };
785        assert!(result.is_error);
786    }
787
788    #[test]
789    fn test_call_tool_result_default() {
790        let json = r#"{"content":[]}"#;
791        let result: CallToolResult = serde_json::from_str(json).unwrap();
792        assert!(!result.is_error);
793    }
794
795    #[test]
796    fn test_read_resource_params_serialization() {
797        let params = ReadResourceParams {
798            uri: "file:///test.txt".to_string(),
799        };
800        let json = serde_json::to_string(&params).unwrap();
801        assert!(json.contains("\"uri\":\"file:///test.txt\""));
802    }
803
804    #[test]
805    fn test_read_resource_result_serialization() {
806        let result = ReadResourceResult {
807            contents: vec![ResourceContent {
808                uri: "file:///test.txt".to_string(),
809                mime_type: Some("text/plain".to_string()),
810                text: Some("Hello".to_string()),
811                blob: None,
812            }],
813        };
814        let json = serde_json::to_string(&result).unwrap();
815        assert!(json.contains("\"contents\""));
816        assert!(json.contains("\"uri\""));
817    }
818
819    #[test]
820    fn test_list_tools_result_serialization() {
821        let result = ListToolsResult {
822            tools: vec![McpTool {
823                name: "tool1".to_string(),
824                description: None,
825                input_schema: serde_json::json!({"type": "object"}),
826            }],
827        };
828        let json = serde_json::to_string(&result).unwrap();
829        assert!(json.contains("\"tools\""));
830    }
831
832    #[test]
833    fn test_list_resources_result_serialization() {
834        let result = ListResourcesResult {
835            resources: vec![McpResource {
836                uri: "file:///test.txt".to_string(),
837                name: "test.txt".to_string(),
838                description: None,
839                mime_type: None,
840            }],
841        };
842        let json = serde_json::to_string(&result).unwrap();
843        assert!(json.contains("\"resources\""));
844    }
845
846    #[test]
847    fn test_server_capabilities_default() {
848        let caps = ServerCapabilities::default();
849        assert!(caps.tools.is_none());
850        assert!(caps.resources.is_none());
851        assert!(caps.prompts.is_none());
852        assert!(caps.logging.is_none());
853    }
854
855    #[test]
856    fn test_server_capabilities_all_fields() {
857        let caps = ServerCapabilities {
858            tools: Some(ToolsCapability { list_changed: true }),
859            resources: Some(ResourcesCapability {
860                subscribe: true,
861                list_changed: true,
862            }),
863            prompts: Some(PromptsCapability { list_changed: true }),
864            logging: Some(LoggingCapability {}),
865        };
866        assert!(caps.tools.is_some());
867        assert!(caps.resources.is_some());
868        assert!(caps.prompts.is_some());
869        assert!(caps.logging.is_some());
870    }
871
872    #[test]
873    fn test_client_capabilities_default() {
874        let caps = ClientCapabilities::default();
875        assert!(caps.roots.is_none());
876        assert!(caps.sampling.is_none());
877    }
878
879    #[test]
880    fn test_client_capabilities_all_fields() {
881        let caps = ClientCapabilities {
882            roots: Some(RootsCapability { list_changed: true }),
883            sampling: Some(SamplingCapability {}),
884        };
885        assert!(caps.roots.is_some());
886        assert!(caps.sampling.is_some());
887    }
888
889    #[test]
890    fn test_mcp_notification_tools_list_changed() {
891        let notif = JsonRpcNotification::new("notifications/tools/list_changed", None);
892        let mcp_notif = McpNotification::from_json_rpc(&notif);
893        match mcp_notif {
894            McpNotification::ToolsListChanged => {}
895            _ => panic!("Expected ToolsListChanged"),
896        }
897    }
898
899    #[test]
900    fn test_mcp_notification_resources_list_changed() {
901        let notif = JsonRpcNotification::new("notifications/resources/list_changed", None);
902        let mcp_notif = McpNotification::from_json_rpc(&notif);
903        match mcp_notif {
904            McpNotification::ResourcesListChanged => {}
905            _ => panic!("Expected ResourcesListChanged"),
906        }
907    }
908
909    #[test]
910    fn test_mcp_notification_prompts_list_changed() {
911        let notif = JsonRpcNotification::new("notifications/prompts/list_changed", None);
912        let mcp_notif = McpNotification::from_json_rpc(&notif);
913        match mcp_notif {
914            McpNotification::PromptsListChanged => {}
915            _ => panic!("Expected PromptsListChanged"),
916        }
917    }
918
919    #[test]
920    fn test_mcp_notification_progress() {
921        let notif = JsonRpcNotification::new(
922            "notifications/progress",
923            Some(serde_json::json!({
924                "progressToken": "token-123",
925                "progress": 50.0,
926                "total": 100.0
927            })),
928        );
929        let mcp_notif = McpNotification::from_json_rpc(&notif);
930        match mcp_notif {
931            McpNotification::Progress {
932                progress_token,
933                progress,
934                total,
935            } => {
936                assert_eq!(progress_token, "token-123");
937                assert_eq!(progress, 50.0);
938                assert_eq!(total, Some(100.0));
939            }
940            _ => panic!("Expected Progress"),
941        }
942    }
943
944    #[test]
945    fn test_mcp_notification_log() {
946        let notif = JsonRpcNotification::new(
947            "notifications/message",
948            Some(serde_json::json!({
949                "level": "error",
950                "logger": "test-logger",
951                "data": {"message": "test"}
952            })),
953        );
954        let mcp_notif = McpNotification::from_json_rpc(&notif);
955        match mcp_notif {
956            McpNotification::Log {
957                level,
958                logger,
959                data,
960            } => {
961                assert_eq!(level, "error");
962                assert_eq!(logger, Some("test-logger".to_string()));
963                assert!(data.is_object());
964            }
965            _ => panic!("Expected Log"),
966        }
967    }
968
969    #[test]
970    fn test_mcp_notification_log_edge_case_no_logger() {
971        let notif = JsonRpcNotification::new(
972            "notifications/message",
973            Some(serde_json::json!({
974                "level": "info",
975                "data": "simple message"
976            })),
977        );
978        let mcp_notif = McpNotification::from_json_rpc(&notif);
979        match mcp_notif {
980            McpNotification::Log { level, logger, .. } => {
981                assert_eq!(level, "info");
982                assert!(logger.is_none());
983            }
984            _ => panic!("Expected Log"),
985        }
986    }
987
988    #[test]
989    fn test_mcp_notification_log_edge_case_default_level() {
990        let notif = JsonRpcNotification::new(
991            "notifications/message",
992            Some(serde_json::json!({
993                "data": "message"
994            })),
995        );
996        let mcp_notif = McpNotification::from_json_rpc(&notif);
997        match mcp_notif {
998            McpNotification::Log { level, .. } => {
999                assert_eq!(level, "info");
1000            }
1001            _ => panic!("Expected Log"),
1002        }
1003    }
1004
1005    #[test]
1006    fn test_mcp_notification_unknown() {
1007        let notif = JsonRpcNotification::new(
1008            "unknown/notification",
1009            Some(serde_json::json!({"key": "value"})),
1010        );
1011        let mcp_notif = McpNotification::from_json_rpc(&notif);
1012        match mcp_notif {
1013            McpNotification::Unknown { method, params } => {
1014                assert_eq!(method, "unknown/notification");
1015                assert!(params.is_some());
1016            }
1017            _ => panic!("Expected Unknown"),
1018        }
1019    }
1020
1021    #[test]
1022    fn test_tool_content_image() {
1023        let content = ToolContent::Image {
1024            data: "base64data".to_string(),
1025            mime_type: "image/png".to_string(),
1026        };
1027        let json = serde_json::to_string(&content).unwrap();
1028        assert!(json.contains("\"type\":\"image\""));
1029        assert!(json.contains("\"data\":\"base64data\""));
1030        assert!(json.contains("\"mimeType\":\"image/png\""));
1031    }
1032
1033    #[test]
1034    fn test_tool_content_resource() {
1035        let content = ToolContent::Resource {
1036            resource: ResourceContent {
1037                uri: "file:///test.txt".to_string(),
1038                mime_type: Some("text/plain".to_string()),
1039                text: Some("content".to_string()),
1040                blob: None,
1041            },
1042        };
1043        let json = serde_json::to_string(&content).unwrap();
1044        assert!(json.contains("\"type\":\"resource\""));
1045        assert!(json.contains("\"uri\":\"file:///test.txt\""));
1046    }
1047
1048    #[test]
1049    fn test_mcp_server_config_default() {
1050        let config = McpServerConfig {
1051            name: "test-server".to_string(),
1052            transport: McpTransportConfig::Stdio {
1053                command: "node".to_string(),
1054                args: vec!["server.js".to_string()],
1055            },
1056            enabled: true,
1057            env: HashMap::new(),
1058            oauth: None,
1059            tool_timeout_secs: 60,
1060        };
1061        assert!(config.enabled);
1062        assert!(config.oauth.is_none());
1063    }
1064
1065    #[test]
1066    fn test_mcp_server_config_with_env() {
1067        let mut env = HashMap::new();
1068        env.insert("API_KEY".to_string(), "secret".to_string());
1069        let config = McpServerConfig {
1070            name: "test-server".to_string(),
1071            transport: McpTransportConfig::Stdio {
1072                command: "node".to_string(),
1073                args: vec![],
1074            },
1075            enabled: true,
1076            env,
1077            oauth: None,
1078            tool_timeout_secs: 60,
1079        };
1080        assert!(config.env.contains_key("API_KEY"));
1081    }
1082
1083    #[test]
1084    fn test_mcp_server_config_with_oauth() {
1085        let config = McpServerConfig {
1086            name: "test-server".to_string(),
1087            transport: McpTransportConfig::Http {
1088                url: "https://api.example.com".to_string(),
1089                headers: HashMap::new(),
1090            },
1091            enabled: true,
1092            env: HashMap::new(),
1093            oauth: Some(OAuthConfig {
1094                auth_url: "https://auth.example.com".to_string(),
1095                token_url: "https://token.example.com".to_string(),
1096                client_id: "client-123".to_string(),
1097                client_secret: Some("secret".to_string()),
1098                scopes: vec!["read".to_string(), "write".to_string()],
1099                redirect_uri: "http://localhost:8080/callback".to_string(),
1100            }),
1101            tool_timeout_secs: 60,
1102        };
1103        assert!(config.oauth.is_some());
1104    }
1105
1106    #[test]
1107    fn test_mcp_transport_config_stdio_variant() {
1108        let transport = McpTransportConfig::Stdio {
1109            command: "python".to_string(),
1110            args: vec!["-m".to_string(), "server".to_string()],
1111        };
1112        match transport {
1113            McpTransportConfig::Stdio { command, args } => {
1114                assert_eq!(command, "python");
1115                assert_eq!(args.len(), 2);
1116            }
1117            _ => panic!("Expected Stdio"),
1118        }
1119    }
1120
1121    #[test]
1122    fn test_mcp_transport_config_http_variant() {
1123        let mut headers = HashMap::new();
1124        headers.insert("Authorization".to_string(), "Bearer token".to_string());
1125        let transport = McpTransportConfig::Http {
1126            url: "https://mcp.example.com".to_string(),
1127            headers,
1128        };
1129        match transport {
1130            McpTransportConfig::Http { url, headers } => {
1131                assert_eq!(url, "https://mcp.example.com");
1132                assert!(headers.contains_key("Authorization"));
1133            }
1134            _ => panic!("Expected Http"),
1135        }
1136    }
1137
1138    #[test]
1139    fn test_mcp_prompt_serialize() {
1140        let prompt = McpPrompt {
1141            name: "test_prompt".to_string(),
1142            description: Some("A test prompt".to_string()),
1143            arguments: Some(vec![PromptArgument {
1144                name: "arg1".to_string(),
1145                description: Some("First argument".to_string()),
1146                required: true,
1147            }]),
1148        };
1149        let json = serde_json::to_string(&prompt).unwrap();
1150        assert!(json.contains("\"name\":\"test_prompt\""));
1151        assert!(json.contains("\"arguments\""));
1152    }
1153
1154    #[test]
1155    fn test_prompt_argument_default() {
1156        let json = r#"{"name":"arg"}"#;
1157        let arg: PromptArgument = serde_json::from_str(json).unwrap();
1158        assert_eq!(arg.name, "arg");
1159        assert!(!arg.required);
1160    }
1161}