1use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17
18#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
20#[serde(rename_all = "camelCase")]
21pub enum ElicitationAction {
22 Accept,
24 Decline,
26 Cancel,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct ElicitationSchema {
33 #[serde(rename = "type")]
35 pub schema_type: String,
36 pub properties: HashMap<String, PrimitiveSchemaDefinition>,
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub required: Option<Vec<String>>,
41 #[serde(
43 rename = "additionalProperties",
44 skip_serializing_if = "Option::is_none"
45 )]
46 pub additional_properties: Option<bool>,
47}
48
49impl ElicitationSchema {
50 pub fn new() -> Self {
52 Self {
53 schema_type: "object".to_string(),
54 properties: HashMap::new(),
55 required: Some(Vec::new()),
56 additional_properties: Some(false),
57 }
58 }
59
60 pub fn add_string_property(
62 mut self,
63 name: String,
64 required: bool,
65 description: Option<String>,
66 ) -> Self {
67 let property = PrimitiveSchemaDefinition::String {
68 title: None,
69 description,
70 format: None,
71 min_length: None,
72 max_length: None,
73 enum_values: None,
74 enum_names: None,
75 };
76
77 self.properties.insert(name.clone(), property);
78
79 if required && let Some(ref mut required_fields) = self.required {
80 required_fields.push(name);
81 }
82
83 self
84 }
85
86 pub fn add_number_property(
88 mut self,
89 name: String,
90 required: bool,
91 description: Option<String>,
92 minimum: Option<f64>,
93 maximum: Option<f64>,
94 ) -> Self {
95 let property = PrimitiveSchemaDefinition::Number {
96 title: None,
97 description,
98 minimum,
99 maximum,
100 };
101
102 self.properties.insert(name.clone(), property);
103
104 if required && let Some(ref mut required_fields) = self.required {
105 required_fields.push(name);
106 }
107
108 self
109 }
110
111 pub fn add_boolean_property(
113 mut self,
114 name: String,
115 required: bool,
116 description: Option<String>,
117 default: Option<bool>,
118 ) -> Self {
119 let property = PrimitiveSchemaDefinition::Boolean {
120 title: None,
121 description,
122 default,
123 };
124
125 self.properties.insert(name.clone(), property);
126
127 if required && let Some(ref mut required_fields) = self.required {
128 required_fields.push(name);
129 }
130
131 self
132 }
133}
134
135impl Default for ElicitationSchema {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
147#[serde(tag = "type")]
148pub enum PrimitiveSchemaDefinition {
149 #[serde(rename = "string")]
154 String {
155 #[serde(skip_serializing_if = "Option::is_none")]
157 title: Option<String>,
158 #[serde(skip_serializing_if = "Option::is_none")]
160 description: Option<String>,
161 #[serde(skip_serializing_if = "Option::is_none")]
163 format: Option<String>,
164 #[serde(skip_serializing_if = "Option::is_none")]
166 #[serde(rename = "minLength")]
167 min_length: Option<u32>,
168 #[serde(skip_serializing_if = "Option::is_none")]
170 #[serde(rename = "maxLength")]
171 max_length: Option<u32>,
172 #[serde(skip_serializing_if = "Option::is_none")]
176 #[serde(rename = "enum")]
177 enum_values: Option<Vec<String>>,
178 #[serde(skip_serializing_if = "Option::is_none")]
183 #[serde(rename = "enumNames")]
184 enum_names: Option<Vec<String>>,
185 },
186 #[serde(rename = "number")]
188 Number {
189 #[serde(skip_serializing_if = "Option::is_none")]
191 title: Option<String>,
192 #[serde(skip_serializing_if = "Option::is_none")]
194 description: Option<String>,
195 #[serde(skip_serializing_if = "Option::is_none")]
197 minimum: Option<f64>,
198 #[serde(skip_serializing_if = "Option::is_none")]
200 maximum: Option<f64>,
201 },
202 #[serde(rename = "integer")]
204 Integer {
205 #[serde(skip_serializing_if = "Option::is_none")]
207 title: Option<String>,
208 #[serde(skip_serializing_if = "Option::is_none")]
210 description: Option<String>,
211 #[serde(skip_serializing_if = "Option::is_none")]
213 minimum: Option<i64>,
214 #[serde(skip_serializing_if = "Option::is_none")]
216 maximum: Option<i64>,
217 },
218 #[serde(rename = "boolean")]
220 Boolean {
221 #[serde(skip_serializing_if = "Option::is_none")]
223 title: Option<String>,
224 #[serde(skip_serializing_if = "Option::is_none")]
226 description: Option<String>,
227 #[serde(skip_serializing_if = "Option::is_none")]
229 default: Option<bool>,
230 },
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
245#[cfg(feature = "mcp-enum-improvements")]
246pub struct EnumOption {
247 #[serde(rename = "const")]
249 pub const_value: String,
250 pub title: String,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
271#[cfg(feature = "mcp-enum-improvements")]
272pub struct TitledSingleSelectEnumSchema {
273 #[serde(rename = "type")]
275 pub schema_type: String,
276 #[serde(rename = "oneOf")]
278 pub one_of: Vec<EnumOption>,
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub title: Option<String>,
282 #[serde(skip_serializing_if = "Option::is_none")]
284 pub description: Option<String>,
285 #[serde(skip_serializing_if = "Option::is_none")]
287 pub default: Option<String>,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
303#[cfg(feature = "mcp-enum-improvements")]
304pub struct UntitledSingleSelectEnumSchema {
305 #[serde(rename = "type")]
307 pub schema_type: String,
308 #[serde(rename = "enum")]
310 pub enum_values: Vec<String>,
311 #[serde(skip_serializing_if = "Option::is_none")]
313 pub title: Option<String>,
314 #[serde(skip_serializing_if = "Option::is_none")]
316 pub description: Option<String>,
317 #[serde(skip_serializing_if = "Option::is_none")]
319 pub default: Option<String>,
320}
321
322#[derive(Debug, Clone, Serialize, Deserialize)]
344#[cfg(feature = "mcp-enum-improvements")]
345pub struct TitledMultiSelectEnumSchema {
346 #[serde(rename = "type")]
348 pub schema_type: String,
349 #[serde(rename = "minItems", skip_serializing_if = "Option::is_none")]
351 pub min_items: Option<u32>,
352 #[serde(rename = "maxItems", skip_serializing_if = "Option::is_none")]
354 pub max_items: Option<u32>,
355 pub items: MultiSelectItems,
357 #[serde(skip_serializing_if = "Option::is_none")]
359 pub title: Option<String>,
360 #[serde(skip_serializing_if = "Option::is_none")]
362 pub description: Option<String>,
363 #[serde(skip_serializing_if = "Option::is_none")]
365 pub default: Option<Vec<String>>,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize)]
384#[cfg(feature = "mcp-enum-improvements")]
385pub struct UntitledMultiSelectEnumSchema {
386 #[serde(rename = "type")]
388 pub schema_type: String,
389 #[serde(rename = "minItems", skip_serializing_if = "Option::is_none")]
391 pub min_items: Option<u32>,
392 #[serde(rename = "maxItems", skip_serializing_if = "Option::is_none")]
394 pub max_items: Option<u32>,
395 pub items: UntitledMultiSelectItems,
397 #[serde(skip_serializing_if = "Option::is_none")]
399 pub title: Option<String>,
400 #[serde(skip_serializing_if = "Option::is_none")]
402 pub description: Option<String>,
403 #[serde(skip_serializing_if = "Option::is_none")]
405 pub default: Option<Vec<String>>,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize)]
410#[cfg(feature = "mcp-enum-improvements")]
411pub struct MultiSelectItems {
412 #[serde(rename = "anyOf")]
414 pub any_of: Vec<EnumOption>,
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize)]
419#[cfg(feature = "mcp-enum-improvements")]
420pub struct UntitledMultiSelectItems {
421 #[serde(rename = "type")]
423 pub schema_type: String,
424 #[serde(rename = "enum")]
426 pub enum_values: Vec<String>,
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize)]
446#[cfg(feature = "mcp-enum-improvements")]
447pub struct LegacyTitledEnumSchema {
448 #[serde(rename = "type")]
450 pub schema_type: String,
451 #[serde(rename = "enum")]
453 pub enum_values: Vec<String>,
454 #[serde(rename = "enumNames")]
456 pub enum_names: Vec<String>,
457 #[serde(skip_serializing_if = "Option::is_none")]
459 pub title: Option<String>,
460 #[serde(skip_serializing_if = "Option::is_none")]
462 pub description: Option<String>,
463 #[serde(skip_serializing_if = "Option::is_none")]
465 pub default: Option<String>,
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize)]
491#[serde(untagged)]
492#[cfg(feature = "mcp-enum-improvements")]
493pub enum EnumSchema {
494 TitledSingleSelect(TitledSingleSelectEnumSchema),
496 UntitledSingleSelect(UntitledSingleSelectEnumSchema),
498 TitledMultiSelect(TitledMultiSelectEnumSchema),
500 UntitledMultiSelect(UntitledMultiSelectEnumSchema),
502 LegacyTitled(LegacyTitledEnumSchema),
504}
505
506#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
508#[serde(rename_all = "lowercase")]
509#[cfg(feature = "mcp-url-elicitation")]
510pub enum ElicitMode {
511 Form,
513 Url,
515}
516
517#[derive(Debug, Clone, Serialize, Deserialize)]
522#[cfg(feature = "mcp-url-elicitation")]
523pub struct URLElicitRequestParams {
524 pub mode: ElicitMode,
526
527 #[serde(rename = "elicitationId")]
529 pub elicitation_id: String,
530
531 pub message: String,
533
534 #[serde(with = "url_serde")]
536 pub url: url::Url,
537}
538
539#[cfg(feature = "mcp-url-elicitation")]
541mod url_serde {
542 use serde::{Deserialize, Deserializer, Serializer};
543 use url::Url;
544
545 pub(super) fn serialize<S>(url: &Url, serializer: S) -> Result<S::Ok, S::Error>
546 where
547 S: Serializer,
548 {
549 serializer.serialize_str(url.as_str())
550 }
551
552 pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Url, D::Error>
553 where
554 D: Deserializer<'de>,
555 {
556 let s = String::deserialize(deserializer)?;
557 Url::parse(&s).map_err(serde::de::Error::custom)
558 }
559}
560
561#[derive(Debug, Clone, Serialize, Deserialize)]
565pub struct FormElicitRequestParams {
566 pub message: String,
568
569 #[serde(rename = "requestedSchema")]
571 pub schema: ElicitationSchema,
572
573 #[serde(rename = "timeoutMs", skip_serializing_if = "Option::is_none")]
575 pub timeout_ms: Option<u32>,
576
577 #[serde(skip_serializing_if = "Option::is_none")]
579 pub cancellable: Option<bool>,
580}
581
582#[derive(Debug, Clone, Serialize, Deserialize)]
584#[serde(untagged)]
585pub enum ElicitRequestParams {
586 #[cfg(not(feature = "mcp-url-elicitation"))]
588 Form(FormElicitRequestParams),
589
590 #[cfg(feature = "mcp-url-elicitation")]
592 Form(FormElicitRequestParams),
593
594 #[cfg(feature = "mcp-url-elicitation")]
596 Url(URLElicitRequestParams),
597}
598
599impl ElicitRequestParams {
600 pub fn form(
602 message: String,
603 schema: ElicitationSchema,
604 timeout_ms: Option<u32>,
605 cancellable: Option<bool>,
606 ) -> Self {
607 ElicitRequestParams::Form(FormElicitRequestParams {
608 message,
609 schema,
610 timeout_ms,
611 cancellable,
612 })
613 }
614
615 #[cfg(feature = "mcp-url-elicitation")]
617 pub fn url(elicitation_id: String, message: String, url: url::Url) -> Self {
618 ElicitRequestParams::Url(URLElicitRequestParams {
619 mode: ElicitMode::Url,
620 elicitation_id,
621 message,
622 url,
623 })
624 }
625
626 pub fn message(&self) -> &str {
628 match self {
629 ElicitRequestParams::Form(form) => &form.message,
630 #[cfg(feature = "mcp-url-elicitation")]
631 ElicitRequestParams::Url(url_params) => &url_params.message,
632 }
633 }
634}
635
636#[derive(Debug, Clone, Serialize, Deserialize)]
638pub struct ElicitRequest {
639 #[serde(flatten)]
641 pub params: ElicitRequestParams,
642 #[cfg(feature = "mcp-tasks")]
648 #[serde(skip_serializing_if = "Option::is_none")]
649 pub task: Option<crate::types::tasks::TaskMetadata>,
650 #[serde(skip_serializing_if = "Option::is_none")]
652 pub _meta: Option<serde_json::Value>,
653}
654
655impl Default for ElicitRequest {
656 fn default() -> Self {
657 Self {
658 params: ElicitRequestParams::form(String::new(), ElicitationSchema::new(), None, None),
659 #[cfg(feature = "mcp-tasks")]
660 task: None,
661 _meta: None,
662 }
663 }
664}
665
666#[derive(Debug, Clone, Serialize, Deserialize)]
703pub struct ElicitResult {
704 pub action: ElicitationAction,
710
711 #[serde(skip_serializing_if = "Option::is_none")]
721 pub content: Option<std::collections::HashMap<String, serde_json::Value>>,
722
723 #[serde(skip_serializing_if = "Option::is_none")]
725 pub _meta: Option<serde_json::Value>,
726}
727
728#[derive(Debug, Clone, Serialize, Deserialize)]
733#[cfg(feature = "mcp-url-elicitation")]
734pub struct ElicitationCompleteParams {
735 #[serde(rename = "elicitationId")]
737 pub elicitation_id: String,
738}
739
740#[cfg(test)]
741mod tests {
742 use super::*;
743
744 #[test]
745 fn test_elicitation_action_serialization() {
746 assert_eq!(
747 serde_json::to_string(&ElicitationAction::Accept).unwrap(),
748 "\"accept\""
749 );
750 assert_eq!(
751 serde_json::to_string(&ElicitationAction::Decline).unwrap(),
752 "\"decline\""
753 );
754 assert_eq!(
755 serde_json::to_string(&ElicitationAction::Cancel).unwrap(),
756 "\"cancel\""
757 );
758 }
759
760 #[test]
761 fn test_form_elicit_params() {
762 let schema = ElicitationSchema::new().add_string_property(
763 "name".to_string(),
764 true,
765 Some("User name".to_string()),
766 );
767
768 let params = ElicitRequestParams::form(
769 "Please provide your name".to_string(),
770 schema,
771 Some(30000),
772 Some(true),
773 );
774
775 assert_eq!(params.message(), "Please provide your name");
776
777 let json = serde_json::to_string(¶ms).unwrap();
779 assert!(json.contains("Please provide your name"));
780 assert!(json.contains("requestedSchema"));
781 }
782
783 #[test]
784 #[cfg(feature = "mcp-url-elicitation")]
785 fn test_url_elicit_params() {
786 use url::Url;
787
788 let url = Url::parse("https://example.com/oauth/authorize").unwrap();
789 let params = ElicitRequestParams::url(
790 "test-id-123".to_string(),
791 "Please authorize the connection".to_string(),
792 url,
793 );
794
795 assert_eq!(params.message(), "Please authorize the connection");
796
797 let json = serde_json::to_string(¶ms).unwrap();
799 assert!(json.contains("test-id-123"));
800 assert!(json.contains("https://example.com/oauth/authorize"));
801 assert!(json.contains("\"mode\":\"url\""));
802 }
803
804 #[test]
805 #[cfg(feature = "mcp-url-elicitation")]
806 fn test_elicit_mode_serialization() {
807 assert_eq!(
808 serde_json::to_string(&ElicitMode::Form).unwrap(),
809 "\"form\""
810 );
811 assert_eq!(serde_json::to_string(&ElicitMode::Url).unwrap(), "\"url\"");
812 }
813
814 #[test]
815 #[cfg(feature = "mcp-url-elicitation")]
816 fn test_completion_notification() {
817 let params = ElicitationCompleteParams {
818 elicitation_id: "550e8400-e29b-41d4-a716-446655440000".to_string(),
819 };
820
821 let json = serde_json::to_string(¶ms).unwrap();
822 assert!(json.contains("550e8400-e29b-41d4-a716-446655440000"));
823 assert!(json.contains("elicitationId"));
824 }
825
826 #[test]
827 fn test_elicit_result_form_mode() {
828 let mut content = std::collections::HashMap::new();
829 content.insert("name".to_string(), serde_json::json!("Alice"));
830
831 let result = ElicitResult {
832 action: ElicitationAction::Accept,
833 content: Some(content),
834 _meta: None,
835 };
836
837 let json = serde_json::to_string(&result).unwrap();
838 assert!(json.contains("\"action\":\"accept\""));
839 assert!(json.contains("\"name\":\"Alice\""));
840 }
841
842 #[test]
843 fn test_elicit_result_url_mode() {
844 let result = ElicitResult {
846 action: ElicitationAction::Accept,
847 content: None,
848 _meta: None,
849 };
850
851 let json = serde_json::to_string(&result).unwrap();
852 assert!(json.contains("\"action\":\"accept\""));
853 assert!(!json.contains("content"));
854 }
855
856 #[test]
859 #[cfg(feature = "mcp-enum-improvements")]
860 fn test_titled_single_select_enum_schema() {
861 use super::{EnumOption, TitledSingleSelectEnumSchema};
862
863 let schema = TitledSingleSelectEnumSchema {
864 schema_type: "string".to_string(),
865 one_of: vec![
866 EnumOption {
867 const_value: "#FF0000".to_string(),
868 title: "Red".to_string(),
869 },
870 EnumOption {
871 const_value: "#00FF00".to_string(),
872 title: "Green".to_string(),
873 },
874 EnumOption {
875 const_value: "#0000FF".to_string(),
876 title: "Blue".to_string(),
877 },
878 ],
879 title: Some("Color Selection".to_string()),
880 description: Some("Choose your favorite color".to_string()),
881 default: Some("#FF0000".to_string()),
882 };
883
884 let json = serde_json::to_string(&schema).unwrap();
885 assert!(json.contains("\"type\":\"string\""));
886 assert!(json.contains("\"oneOf\""));
887 assert!(json.contains("\"const\":\"#FF0000\""));
888 assert!(json.contains("\"title\":\"Red\""));
889 assert!(json.contains("\"default\":\"#FF0000\""));
890
891 let deserialized: TitledSingleSelectEnumSchema = serde_json::from_str(&json).unwrap();
893 assert_eq!(deserialized.one_of.len(), 3);
894 assert_eq!(deserialized.one_of[0].const_value, "#FF0000");
895 assert_eq!(deserialized.one_of[0].title, "Red");
896 }
897
898 #[test]
899 #[cfg(feature = "mcp-enum-improvements")]
900 fn test_untitled_single_select_enum_schema() {
901 use super::UntitledSingleSelectEnumSchema;
902
903 let schema = UntitledSingleSelectEnumSchema {
904 schema_type: "string".to_string(),
905 enum_values: vec!["red".to_string(), "green".to_string(), "blue".to_string()],
906 title: Some("Color Selection".to_string()),
907 description: Some("Choose a color".to_string()),
908 default: Some("red".to_string()),
909 };
910
911 let json = serde_json::to_string(&schema).unwrap();
912 assert!(json.contains("\"type\":\"string\""));
913 assert!(json.contains("\"enum\""));
914 assert!(json.contains("\"red\""));
915 assert!(json.contains("\"green\""));
916 assert!(json.contains("\"blue\""));
917 assert!(!json.contains("oneOf"));
918
919 let deserialized: UntitledSingleSelectEnumSchema = serde_json::from_str(&json).unwrap();
921 assert_eq!(deserialized.enum_values.len(), 3);
922 assert_eq!(deserialized.enum_values[0], "red");
923 }
924
925 #[test]
926 #[cfg(feature = "mcp-enum-improvements")]
927 fn test_titled_multi_select_enum_schema() {
928 use super::{EnumOption, MultiSelectItems, TitledMultiSelectEnumSchema};
929
930 let schema = TitledMultiSelectEnumSchema {
931 schema_type: "array".to_string(),
932 min_items: Some(1),
933 max_items: Some(2),
934 items: MultiSelectItems {
935 any_of: vec![
936 EnumOption {
937 const_value: "#FF0000".to_string(),
938 title: "Red".to_string(),
939 },
940 EnumOption {
941 const_value: "#00FF00".to_string(),
942 title: "Green".to_string(),
943 },
944 ],
945 },
946 title: Some("Color Selection".to_string()),
947 description: Some("Choose up to 2 colors".to_string()),
948 default: Some(vec!["#FF0000".to_string()]),
949 };
950
951 let json = serde_json::to_string(&schema).unwrap();
952 assert!(json.contains("\"type\":\"array\""));
953 assert!(json.contains("\"minItems\":1"));
954 assert!(json.contains("\"maxItems\":2"));
955 assert!(json.contains("\"anyOf\""));
956 assert!(json.contains("\"const\":\"#FF0000\""));
957
958 let deserialized: TitledMultiSelectEnumSchema = serde_json::from_str(&json).unwrap();
960 assert_eq!(deserialized.items.any_of.len(), 2);
961 assert_eq!(deserialized.min_items, Some(1));
962 assert_eq!(deserialized.max_items, Some(2));
963 }
964
965 #[test]
966 #[cfg(feature = "mcp-enum-improvements")]
967 fn test_untitled_multi_select_enum_schema() {
968 use super::{UntitledMultiSelectEnumSchema, UntitledMultiSelectItems};
969
970 let schema = UntitledMultiSelectEnumSchema {
971 schema_type: "array".to_string(),
972 min_items: Some(1),
973 max_items: None,
974 items: UntitledMultiSelectItems {
975 schema_type: "string".to_string(),
976 enum_values: vec!["red".to_string(), "green".to_string(), "blue".to_string()],
977 },
978 title: Some("Color Selection".to_string()),
979 description: Some("Choose colors".to_string()),
980 default: Some(vec!["red".to_string(), "green".to_string()]),
981 };
982
983 let json = serde_json::to_string(&schema).unwrap();
984 assert!(json.contains("\"type\":\"array\""));
985 assert!(json.contains("\"minItems\":1"));
986 assert!(json.contains("\"items\""));
987 assert!(json.contains("\"enum\""));
988 assert!(!json.contains("anyOf"));
989
990 let deserialized: UntitledMultiSelectEnumSchema = serde_json::from_str(&json).unwrap();
992 assert_eq!(deserialized.items.enum_values.len(), 3);
993 assert_eq!(deserialized.default.as_ref().unwrap().len(), 2);
994 }
995
996 #[test]
997 #[cfg(feature = "mcp-enum-improvements")]
998 fn test_legacy_titled_enum_schema() {
999 use super::LegacyTitledEnumSchema;
1000
1001 let schema = LegacyTitledEnumSchema {
1002 schema_type: "string".to_string(),
1003 enum_values: vec!["#FF0000".to_string(), "#00FF00".to_string()],
1004 enum_names: vec!["Red".to_string(), "Green".to_string()],
1005 title: Some("Color Selection".to_string()),
1006 description: Some("Choose a color (legacy)".to_string()),
1007 default: Some("#FF0000".to_string()),
1008 };
1009
1010 let json = serde_json::to_string(&schema).unwrap();
1011 assert!(json.contains("\"type\":\"string\""));
1012 assert!(json.contains("\"enum\""));
1013 assert!(json.contains("\"enumNames\""));
1014 assert!(json.contains("\"Red\""));
1015 assert!(!json.contains("oneOf"));
1016
1017 let deserialized: LegacyTitledEnumSchema = serde_json::from_str(&json).unwrap();
1019 assert_eq!(deserialized.enum_values.len(), 2);
1020 assert_eq!(deserialized.enum_names.len(), 2);
1021 assert_eq!(deserialized.enum_names[0], "Red");
1022 }
1023
1024 #[test]
1025 #[cfg(feature = "mcp-enum-improvements")]
1026 fn test_enum_schema_union_type() {
1027 use super::EnumSchema;
1028
1029 let titled_json = r#"{
1031 "type": "string",
1032 "oneOf": [
1033 {"const": "red", "title": "Red"},
1034 {"const": "green", "title": "Green"}
1035 ]
1036 }"#;
1037
1038 let schema: EnumSchema = serde_json::from_str(titled_json).unwrap();
1039 match schema {
1040 EnumSchema::TitledSingleSelect(s) => {
1041 assert_eq!(s.one_of.len(), 2);
1042 assert_eq!(s.one_of[0].const_value, "red");
1043 }
1044 _ => panic!("Expected TitledSingleSelect variant"),
1045 }
1046
1047 let untitled_json = r#"{
1049 "type": "string",
1050 "enum": ["red", "green", "blue"]
1051 }"#;
1052
1053 let schema: EnumSchema = serde_json::from_str(untitled_json).unwrap();
1054 match schema {
1055 EnumSchema::UntitledSingleSelect(s) => {
1056 assert_eq!(s.enum_values.len(), 3);
1057 }
1058 _ => panic!("Expected UntitledSingleSelect variant"),
1059 }
1060 }
1061
1062 #[test]
1063 #[cfg(feature = "mcp-enum-improvements")]
1064 fn test_enum_option_serialization() {
1065 use super::EnumOption;
1066
1067 let option = EnumOption {
1068 const_value: "#FF0000".to_string(),
1069 title: "Red".to_string(),
1070 };
1071
1072 let json = serde_json::to_string(&option).unwrap();
1073 assert!(json.contains("\"const\":\"#FF0000\""));
1074 assert!(json.contains("\"title\":\"Red\""));
1075 assert!(!json.contains("const_value")); let deserialized: EnumOption = serde_json::from_str(&json).unwrap();
1079 assert_eq!(deserialized.const_value, "#FF0000");
1080 assert_eq!(deserialized.title, "Red");
1081 }
1082}