1use crate::core::error::{McpError, McpResult};
8use crate::protocol::{messages::*, methods, types::*};
9use serde_json::Value;
10
11pub fn validate_jsonrpc_message(message: &Value) -> McpResult<()> {
13 let obj = message
14 .as_object()
15 .ok_or_else(|| McpError::Validation("Message must be a JSON object".to_string()))?;
16
17 let jsonrpc = obj
19 .get("jsonrpc")
20 .and_then(|v| v.as_str())
21 .ok_or_else(|| McpError::Validation("Missing or invalid 'jsonrpc' field".to_string()))?;
22
23 if jsonrpc != "2.0" {
24 return Err(McpError::Validation("jsonrpc must be '2.0'".to_string()));
25 }
26
27 let has_method = obj.contains_key("method");
29 let has_result = obj.contains_key("result");
30 let has_error = obj.contains_key("error");
31 let has_id = obj.contains_key("id");
32
33 if has_method {
34 if has_result || has_error {
36 return Err(McpError::Validation(
37 "Request/notification cannot have 'result' or 'error' fields".to_string(),
38 ));
39 }
40
41 } else if has_result || has_error {
44 if !has_id {
46 return Err(McpError::Validation(
47 "Response must have an 'id' field".to_string(),
48 ));
49 }
50
51 if has_result && has_error {
52 return Err(McpError::Validation(
53 "Response cannot have both 'result' and 'error' fields".to_string(),
54 ));
55 }
56 } else {
57 return Err(McpError::Validation(
58 "Message must be a request, response, or notification".to_string(),
59 ));
60 }
61
62 Ok(())
63}
64
65pub fn validate_jsonrpc_request(request: &JsonRpcRequest) -> McpResult<()> {
67 if request.jsonrpc != "2.0" {
68 return Err(McpError::Validation("jsonrpc must be '2.0'".to_string()));
69 }
70
71 if request.method.is_empty() {
72 return Err(McpError::Validation(
73 "Method name cannot be empty".to_string(),
74 ));
75 }
76
77 if request.method.starts_with("rpc.") && !request.method.starts_with("rpc.discover") {
79 return Err(McpError::Validation(
80 "Method names starting with 'rpc.' are reserved".to_string(),
81 ));
82 }
83
84 Ok(())
85}
86
87pub fn validate_jsonrpc_response(response: &JsonRpcResponse) -> McpResult<()> {
89 if response.jsonrpc != "2.0" {
90 return Err(McpError::Validation("jsonrpc must be '2.0'".to_string()));
91 }
92
93 Ok(())
96}
97
98pub fn validate_jsonrpc_notification(notification: &JsonRpcNotification) -> McpResult<()> {
100 if notification.jsonrpc != "2.0" {
101 return Err(McpError::Validation("jsonrpc must be '2.0'".to_string()));
102 }
103
104 if notification.method.is_empty() {
105 return Err(McpError::Validation(
106 "Method name cannot be empty".to_string(),
107 ));
108 }
109
110 Ok(())
111}
112
113pub fn validate_initialize_params(params: &InitializeParams) -> McpResult<()> {
115 if params.client_info.name.is_empty() {
116 return Err(McpError::Validation(
117 "Client name cannot be empty".to_string(),
118 ));
119 }
120
121 if params.client_info.version.is_empty() {
122 return Err(McpError::Validation(
123 "Client version cannot be empty".to_string(),
124 ));
125 }
126
127 if params.protocol_version.is_empty() {
128 return Err(McpError::Validation(
129 "Protocol version cannot be empty".to_string(),
130 ));
131 }
132
133 Ok(())
134}
135
136pub fn validate_tool_info(tool: &Tool) -> McpResult<()> {
138 if tool.name.is_empty() {
139 return Err(McpError::Validation(
140 "Tool name cannot be empty".to_string(),
141 ));
142 }
143
144 if tool.input_schema.schema_type != "object" {
146 return Err(McpError::Validation(
147 "Tool input_schema type must be 'object'".to_string(),
148 ));
149 }
150
151 if let Some(annotations) = &tool.annotations {
153 validate_tool_annotations(annotations)?;
154 }
155
156 Ok(())
157}
158
159pub fn validate_call_tool_params(params: &CallToolParams) -> McpResult<()> {
161 if params.name.is_empty() {
162 return Err(McpError::Validation(
163 "Tool name cannot be empty".to_string(),
164 ));
165 }
166
167 Ok(())
168}
169
170pub fn validate_resource_info(resource: &Resource) -> McpResult<()> {
172 if resource.uri.is_empty() {
173 return Err(McpError::Validation(
174 "Resource URI cannot be empty".to_string(),
175 ));
176 }
177
178 if resource.name.is_empty() {
179 return Err(McpError::Validation(
180 "Resource name cannot be empty".to_string(),
181 ));
182 }
183
184 validate_uri(&resource.uri)?;
186
187 if let Some(annotations) = &resource.annotations {
189 validate_annotations(annotations)?;
190 }
191
192 Ok(())
193}
194
195pub fn validate_read_resource_params(params: &ReadResourceParams) -> McpResult<()> {
197 if params.uri.is_empty() {
198 return Err(McpError::Validation(
199 "Resource URI cannot be empty".to_string(),
200 ));
201 }
202
203 validate_uri(¶ms.uri)?;
204
205 Ok(())
206}
207
208pub fn validate_resource_content(content: &ResourceContents) -> McpResult<()> {
210 match content {
211 ResourceContents::Text { uri, text, .. } => {
212 if uri.is_empty() {
213 return Err(McpError::Validation(
214 "Resource content URI cannot be empty".to_string(),
215 ));
216 }
217 if text.is_empty() {
218 return Err(McpError::Validation(
219 "Text resource content cannot be empty".to_string(),
220 ));
221 }
222 }
223 ResourceContents::Blob { uri, blob, .. } => {
224 if uri.is_empty() {
225 return Err(McpError::Validation(
226 "Resource content URI cannot be empty".to_string(),
227 ));
228 }
229 if blob.is_empty() {
230 return Err(McpError::Validation(
231 "Blob resource content cannot be empty".to_string(),
232 ));
233 }
234 }
235 }
236
237 Ok(())
238}
239
240pub fn validate_prompt_info(prompt: &Prompt) -> McpResult<()> {
242 if prompt.name.is_empty() {
243 return Err(McpError::Validation(
244 "Prompt name cannot be empty".to_string(),
245 ));
246 }
247
248 if let Some(args) = &prompt.arguments {
249 for arg in args {
250 if arg.name.is_empty() {
251 return Err(McpError::Validation(
252 "Prompt argument name cannot be empty".to_string(),
253 ));
254 }
255 }
256 }
257
258 Ok(())
259}
260
261pub fn validate_get_prompt_params(params: &GetPromptParams) -> McpResult<()> {
263 if params.name.is_empty() {
264 return Err(McpError::Validation(
265 "Prompt name cannot be empty".to_string(),
266 ));
267 }
268
269 Ok(())
270}
271
272pub fn validate_prompt_messages(messages: &[PromptMessage]) -> McpResult<()> {
274 if messages.is_empty() {
275 return Err(McpError::Validation(
276 "Prompt must have at least one message".to_string(),
277 ));
278 }
279
280 for message in messages {
281 validate_content(&message.content)?;
283 }
284
285 Ok(())
286}
287
288pub fn validate_sampling_messages(messages: &[SamplingMessage]) -> McpResult<()> {
290 if messages.is_empty() {
291 return Err(McpError::Validation(
292 "Sampling request must have at least one message".to_string(),
293 ));
294 }
295
296 for message in messages {
297 validate_sampling_content(&message.content)?;
299 }
300
301 Ok(())
302}
303
304pub fn validate_create_message_params(params: &CreateMessageParams) -> McpResult<()> {
306 validate_sampling_messages(¶ms.messages)?;
307
308 if params.max_tokens == 0 {
310 return Err(McpError::Validation(
311 "max_tokens must be greater than 0".to_string(),
312 ));
313 }
314
315 if let Some(prefs) = ¶ms.model_preferences {
317 validate_model_preferences(prefs)?;
318 }
319
320 Ok(())
321}
322
323pub fn validate_sampling_content(content: &SamplingContent) -> McpResult<()> {
325 match content {
326 SamplingContent::Text {
327 text, annotations, ..
328 } => {
329 if text.is_empty() {
330 return Err(McpError::Validation(
331 "Text content cannot be empty".to_string(),
332 ));
333 }
334 if let Some(annotations) = annotations {
335 validate_annotations(annotations)?;
336 }
337 }
338 SamplingContent::Image {
339 data,
340 mime_type,
341 annotations,
342 ..
343 } => {
344 if data.is_empty() {
345 return Err(McpError::Validation(
346 "Image data cannot be empty".to_string(),
347 ));
348 }
349 if !mime_type.starts_with("image/") {
350 return Err(McpError::Validation(
351 "Image MIME type must start with 'image/'".to_string(),
352 ));
353 }
354 if let Some(annotations) = annotations {
355 validate_annotations(annotations)?;
356 }
357 }
358 SamplingContent::Audio {
359 data,
360 mime_type,
361 annotations,
362 ..
363 } => {
364 if data.is_empty() {
365 return Err(McpError::Validation(
366 "Audio data cannot be empty".to_string(),
367 ));
368 }
369 if !mime_type.starts_with("audio/") {
370 return Err(McpError::Validation(
371 "Audio MIME type must start with 'audio/'".to_string(),
372 ));
373 }
374 if let Some(annotations) = annotations {
375 validate_annotations(annotations)?;
376 }
377 }
378 }
379 Ok(())
380}
381
382pub fn validate_content(content: &ContentBlock) -> McpResult<()> {
384 match content {
385 ContentBlock::Text {
386 text, annotations, ..
387 } => {
388 if text.is_empty() {
389 return Err(McpError::Validation(
390 "Text content cannot be empty".to_string(),
391 ));
392 }
393 if let Some(annotations) = annotations {
394 validate_annotations(annotations)?;
395 }
396 }
397 ContentBlock::Image {
398 data,
399 mime_type,
400 annotations,
401 ..
402 } => {
403 if data.is_empty() {
404 return Err(McpError::Validation(
405 "Image data cannot be empty".to_string(),
406 ));
407 }
408 if mime_type.is_empty() {
409 return Err(McpError::Validation(
410 "Image MIME type cannot be empty".to_string(),
411 ));
412 }
413 if !mime_type.starts_with("image/") {
414 return Err(McpError::Validation(
415 "Image MIME type must start with 'image/'".to_string(),
416 ));
417 }
418 if let Some(annotations) = annotations {
419 validate_annotations(annotations)?;
420 }
421 }
422 ContentBlock::Audio {
423 data,
424 mime_type,
425 annotations,
426 ..
427 } => {
428 if data.is_empty() {
429 return Err(McpError::Validation(
430 "Audio data cannot be empty".to_string(),
431 ));
432 }
433 if mime_type.is_empty() {
434 return Err(McpError::Validation(
435 "Audio MIME type cannot be empty".to_string(),
436 ));
437 }
438 if !mime_type.starts_with("audio/") {
439 return Err(McpError::Validation(
440 "Audio MIME type must start with 'audio/'".to_string(),
441 ));
442 }
443 if let Some(annotations) = annotations {
444 validate_annotations(annotations)?;
445 }
446 }
447 ContentBlock::Resource {
448 resource,
449 annotations,
450 ..
451 } => {
452 match resource {
454 ResourceContents::Text { uri, text, .. } => {
455 if uri.is_empty() {
456 return Err(McpError::Validation(
457 "Resource URI cannot be empty".to_string(),
458 ));
459 }
460 if text.is_empty() {
461 return Err(McpError::Validation(
462 "Text resource content cannot be empty".to_string(),
463 ));
464 }
465 validate_uri(uri)?;
466 }
467 ResourceContents::Blob { uri, blob, .. } => {
468 if uri.is_empty() {
469 return Err(McpError::Validation(
470 "Resource URI cannot be empty".to_string(),
471 ));
472 }
473 if blob.is_empty() {
474 return Err(McpError::Validation(
475 "Blob resource content cannot be empty".to_string(),
476 ));
477 }
478 validate_uri(uri)?;
479 }
480 }
481 if let Some(annotations) = annotations {
482 validate_annotations(annotations)?;
483 }
484 }
485 ContentBlock::ResourceLink {
486 uri,
487 name,
488 annotations,
489 ..
490 } => {
491 if uri.is_empty() {
492 return Err(McpError::Validation(
493 "Resource link URI cannot be empty".to_string(),
494 ));
495 }
496 if name.is_empty() {
497 return Err(McpError::Validation(
498 "Resource link name cannot be empty".to_string(),
499 ));
500 }
501 validate_uri(uri)?;
502 if let Some(annotations) = annotations {
503 validate_annotations(annotations)?;
504 }
505 }
506 }
507
508 Ok(())
509}
510
511pub fn validate_annotations(annotations: &Annotations) -> McpResult<()> {
513 if let Some(priority) = annotations.priority {
515 if !(0.0..=1.0).contains(&priority) {
516 return Err(McpError::Validation(
517 "Annotation priority must be between 0.0 and 1.0".to_string(),
518 ));
519 }
520 }
521
522 if let Some(last_modified) = &annotations.last_modified {
524 if last_modified.is_empty() {
525 return Err(McpError::Validation(
526 "Annotation lastModified cannot be empty".to_string(),
527 ));
528 }
529 }
531
532 Ok(())
534}
535
536pub fn validate_tool_annotations(
538 _annotations: &crate::protocol::types::ToolAnnotations,
539) -> McpResult<()> {
540 Ok(())
543}
544
545pub fn validate_completion_reference(reference: &CompletionReference) -> McpResult<()> {
547 match reference {
548 CompletionReference::Prompt { name } => {
549 if name.is_empty() {
550 return Err(McpError::Validation(
551 "Completion prompt name cannot be empty".to_string(),
552 ));
553 }
554 }
555 CompletionReference::Resource { uri } => {
556 if uri.is_empty() {
557 return Err(McpError::Validation(
558 "Completion resource URI cannot be empty".to_string(),
559 ));
560 }
561 validate_uri(uri)?;
562 }
563 CompletionReference::Tool { name } => {
564 if name.is_empty() {
565 return Err(McpError::Validation(
566 "Completion tool name cannot be empty".to_string(),
567 ));
568 }
569 }
570 }
571
572 Ok(())
573}
574
575pub fn validate_completion_argument(argument: &CompletionArgument) -> McpResult<()> {
577 if argument.name.is_empty() {
578 return Err(McpError::Validation(
579 "Completion argument name cannot be empty".to_string(),
580 ));
581 }
582
583 Ok(())
585}
586
587pub fn validate_complete_params(params: &CompleteParams) -> McpResult<()> {
589 validate_completion_reference(¶ms.reference)?;
590 validate_completion_argument(¶ms.argument)?;
591
592 Ok(())
593}
594
595pub fn validate_root(root: &Root) -> McpResult<()> {
597 if root.uri.is_empty() {
598 return Err(McpError::Validation("Root URI cannot be empty".to_string()));
599 }
600
601 if !root.uri.starts_with("file://") {
603 return Err(McpError::Validation(
604 "Root URI must start with 'file://'".to_string(),
605 ));
606 }
607
608 Ok(())
609}
610
611pub fn validate_model_preferences(preferences: &ModelPreferences) -> McpResult<()> {
613 if let Some(cost) = preferences.cost_priority {
614 if !(0.0..=1.0).contains(&cost) {
615 return Err(McpError::Validation(
616 "Cost priority must be between 0.0 and 1.0".to_string(),
617 ));
618 }
619 }
620
621 if let Some(speed) = preferences.speed_priority {
622 if !(0.0..=1.0).contains(&speed) {
623 return Err(McpError::Validation(
624 "Speed priority must be between 0.0 and 1.0".to_string(),
625 ));
626 }
627 }
628
629 if let Some(intelligence) = preferences.intelligence_priority {
630 if !(0.0..=1.0).contains(&intelligence) {
631 return Err(McpError::Validation(
632 "Intelligence priority must be between 0.0 and 1.0".to_string(),
633 ));
634 }
635 }
636
637 Ok(())
638}
639pub fn validate_uri(uri: &str) -> McpResult<()> {
640 if uri.is_empty() {
641 return Err(McpError::Validation("URI cannot be empty".to_string()));
642 }
643
644 if !uri.contains("://") && !uri.starts_with('/') && !uri.starts_with("file:") {
646 return Err(McpError::Validation(
647 "URI must have a scheme or be an absolute path".to_string(),
648 ));
649 }
650
651 Ok(())
652}
653
654pub fn validate_method_name(method: &str) -> McpResult<()> {
656 if method.is_empty() {
657 return Err(McpError::Validation(
658 "Method name cannot be empty".to_string(),
659 ));
660 }
661
662 match method {
664 methods::INITIALIZE
665 | methods::INITIALIZED
666 | methods::PING
667 | methods::TOOLS_LIST
668 | methods::TOOLS_CALL
669 | methods::TOOLS_LIST_CHANGED
670 | methods::RESOURCES_LIST
671 | methods::RESOURCES_TEMPLATES_LIST | methods::RESOURCES_READ
673 | methods::RESOURCES_SUBSCRIBE
674 | methods::RESOURCES_UNSUBSCRIBE
675 | methods::RESOURCES_UPDATED
676 | methods::RESOURCES_LIST_CHANGED
677 | methods::PROMPTS_LIST
678 | methods::PROMPTS_GET
679 | methods::PROMPTS_LIST_CHANGED
680 | methods::SAMPLING_CREATE_MESSAGE
681 | methods::ROOTS_LIST | methods::ROOTS_LIST_CHANGED | methods::COMPLETION_COMPLETE | methods::LOGGING_SET_LEVEL
685 | methods::LOGGING_MESSAGE
686 | methods::PROGRESS
687 | methods::CANCELLED => Ok(()), _ => {
689 if method.contains('/') || method.contains('.') {
691 Ok(())
692 } else {
693 Err(McpError::Validation(format!(
694 "Unknown or invalid method name: {method}"
695 )))
696 }
697 }
698 }
699}
700
701pub fn validate_server_capabilities(_capabilities: &ServerCapabilities) -> McpResult<()> {
703 Ok(())
706}
707
708pub fn validate_client_capabilities(_capabilities: &ClientCapabilities) -> McpResult<()> {
710 Ok(())
713}
714
715pub fn validate_progress_params(params: &ProgressNotificationParams) -> McpResult<()> {
717 if !(0.0..=1.0).contains(¶ms.progress) {
718 return Err(McpError::Validation(
719 "Progress must be between 0.0 and 1.0".to_string(),
720 ));
721 }
722
723 Ok(())
724}
725
726pub fn validate_logging_message_params(params: &LoggingMessageNotificationParams) -> McpResult<()> {
728 if params.data.is_null() {
730 return Err(McpError::Validation(
731 "Log message data cannot be null".to_string(),
732 ));
733 }
734
735 Ok(())
736}
737
738pub fn validate_mcp_request(method: &str, params: Option<&Value>) -> McpResult<()> {
740 validate_method_name(method)?;
741
742 if let Some(params_value) = params {
743 match method {
744 methods::INITIALIZE => {
745 let params: InitializeParams = serde_json::from_value(params_value.clone())
746 .map_err(|e| McpError::Validation(format!("Invalid initialize params: {e}")))?;
747 validate_initialize_params(¶ms)?;
748 }
749 methods::TOOLS_CALL => {
750 let params: CallToolParams = serde_json::from_value(params_value.clone())
751 .map_err(|e| McpError::Validation(format!("Invalid call tool params: {e}")))?;
752 validate_call_tool_params(¶ms)?;
753 }
754 methods::RESOURCES_READ => {
755 let params: ReadResourceParams = serde_json::from_value(params_value.clone())
756 .map_err(|e| {
757 McpError::Validation(format!("Invalid read resource params: {e}"))
758 })?;
759 validate_read_resource_params(¶ms)?;
760 }
761 methods::PROMPTS_GET => {
762 let params: GetPromptParams = serde_json::from_value(params_value.clone())
763 .map_err(|e| McpError::Validation(format!("Invalid get prompt params: {e}")))?;
764 validate_get_prompt_params(¶ms)?;
765 }
766 methods::SAMPLING_CREATE_MESSAGE => {
767 let params: CreateMessageParams = serde_json::from_value(params_value.clone())
768 .map_err(|e| {
769 McpError::Validation(format!("Invalid create message params: {e}"))
770 })?;
771 validate_create_message_params(¶ms)?;
772 }
773 methods::COMPLETION_COMPLETE => {
774 let params: CompleteParams = serde_json::from_value(params_value.clone())
776 .map_err(|e| McpError::Validation(format!("Invalid complete params: {e}")))?;
777 validate_complete_params(¶ms)?;
778 }
779 methods::PROGRESS => {
780 let params: ProgressNotificationParams =
781 serde_json::from_value(params_value.clone()).map_err(|e| {
782 McpError::Validation(format!("Invalid progress params: {e}"))
783 })?;
784 validate_progress_params(¶ms)?;
785 }
786 methods::LOGGING_MESSAGE => {
787 let params: LoggingMessageNotificationParams =
788 serde_json::from_value(params_value.clone()).map_err(|e| {
789 McpError::Validation(format!("Invalid logging message params: {e}"))
790 })?;
791 validate_logging_message_params(¶ms)?;
792 }
793 _ => {
794 if !params_value.is_object() && !params_value.is_null() {
796 return Err(McpError::Validation(
797 "Parameters must be a JSON object or null".to_string(),
798 ));
799 }
800 }
801 }
802 }
803
804 Ok(())
805}
806
807#[cfg(test)]
808mod tests {
809 use super::*;
810 use serde_json::json;
811
812 #[test]
813 fn test_validate_jsonrpc_request() {
814 let valid_request = JsonRpcRequest {
815 jsonrpc: "2.0".to_string(),
816 id: json!(1),
817 method: "test_method".to_string(),
818 params: None,
819 };
820 assert!(validate_jsonrpc_request(&valid_request).is_ok());
821
822 let invalid_request = JsonRpcRequest {
823 jsonrpc: "1.0".to_string(),
824 id: json!(1),
825 method: "test_method".to_string(),
826 params: None,
827 };
828 assert!(validate_jsonrpc_request(&invalid_request).is_err());
829 }
830
831 #[test]
832 fn test_validate_uri() {
833 assert!(validate_uri("https://example.com").is_ok());
834 assert!(validate_uri("file:///path/to/file").is_ok());
835 assert!(validate_uri("/absolute/path").is_ok());
836 assert!(validate_uri("").is_err());
837 assert!(validate_uri("invalid").is_err());
838 }
839
840 #[test]
841 fn test_validate_tool_info() {
842 let valid_tool = Tool {
843 name: "test_tool".to_string(),
844 description: Some("A test tool".to_string()),
845 input_schema: ToolInputSchema {
846 schema_type: "object".to_string(),
847 properties: Some(
848 json!({
849 "param": {"type": "string"}
850 })
851 .as_object()
852 .unwrap()
853 .iter()
854 .map(|(k, v)| (k.clone(), v.clone()))
855 .collect(),
856 ),
857 required: None,
858 additional_properties: std::collections::HashMap::new(),
859 },
860 annotations: None,
861 title: Some("Test Tool".to_string()),
862 meta: None,
863 };
864 assert!(validate_tool_info(&valid_tool).is_ok());
865
866 let invalid_tool = Tool {
867 name: "".to_string(),
868 description: None,
869 input_schema: ToolInputSchema {
870 schema_type: "string".to_string(), properties: None,
872 required: None,
873 additional_properties: std::collections::HashMap::new(),
874 },
875 annotations: None,
876 title: None,
877 meta: None,
878 };
879 assert!(validate_tool_info(&invalid_tool).is_err());
880 }
881
882 #[test]
883 fn test_validate_create_message_params() {
884 let valid_params = CreateMessageParams {
885 messages: vec![SamplingMessage::user_text("Hello")],
886 model_preferences: None,
887 system_prompt: None,
888 include_context: None,
889 max_tokens: 100,
890 temperature: None,
891 stop_sequences: None,
892 metadata: None,
893 meta: None,
894 };
895 assert!(validate_create_message_params(&valid_params).is_ok());
896
897 let invalid_params = CreateMessageParams {
898 messages: vec![],
899 model_preferences: None,
900 system_prompt: None,
901 include_context: None,
902 max_tokens: 0, temperature: None,
904 stop_sequences: None,
905 metadata: None,
906 meta: None,
907 };
908 assert!(validate_create_message_params(&invalid_params).is_err());
909 }
910
911 #[test]
912 fn test_validate_content() {
913 let valid_text = Content::text("Hello, world!");
914 assert!(validate_content(&valid_text).is_ok());
915
916 let valid_image = Content::image("base64data", "image/png");
917 assert!(validate_content(&valid_image).is_ok());
918
919 let valid_audio = Content::audio("base64data", "audio/wav");
921 assert!(validate_content(&valid_audio).is_ok());
922
923 let invalid_text = Content::Text {
924 text: "".to_string(),
925 annotations: None,
926 meta: None,
927 };
928 assert!(validate_content(&invalid_text).is_err());
929
930 let invalid_image = Content::Image {
931 data: "data".to_string(),
932 mime_type: "text/plain".to_string(), annotations: None,
934 meta: None,
935 };
936 assert!(validate_content(&invalid_image).is_err());
937
938 let invalid_audio = Content::Audio {
939 data: "data".to_string(),
940 mime_type: "image/png".to_string(), annotations: None,
942 meta: None,
943 };
944 assert!(validate_content(&invalid_audio).is_err());
945 }
946
947 #[test]
948 fn test_validate_method_name() {
949 assert!(validate_method_name(methods::INITIALIZE).is_ok());
950 assert!(validate_method_name(methods::TOOLS_LIST).is_ok());
951 assert!(validate_method_name("custom/method").is_ok());
952 assert!(validate_method_name("custom.method").is_ok());
953 assert!(validate_method_name("").is_err());
954 }
955
956 #[test]
957 fn test_validate_mcp_request() {
958 let init_params = json!({
959 "clientInfo": {
960 "name": "test-client",
961 "version": "1.0.0"
962 },
963 "capabilities": {},
964 "protocolVersion": "2025-03-26"
965 });
966
967 assert!(validate_mcp_request(methods::INITIALIZE, Some(&init_params)).is_ok());
968 assert!(validate_mcp_request(methods::PING, None).is_ok());
969 assert!(validate_mcp_request("", None).is_err());
970
971 assert!(validate_mcp_request(methods::ROOTS_LIST, None).is_ok());
973 assert!(validate_mcp_request(methods::COMPLETION_COMPLETE, None).is_ok());
974 assert!(validate_mcp_request(methods::RESOURCES_TEMPLATES_LIST, None).is_ok());
975 }
976}