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 #[serde(skip_serializing_if = "Option::is_none")]
648 pub task: Option<crate::types::tasks::TaskMetadata>,
649 #[serde(skip_serializing_if = "Option::is_none")]
651 pub _meta: Option<serde_json::Value>,
652}
653
654impl Default for ElicitRequest {
655 fn default() -> Self {
656 Self {
657 params: ElicitRequestParams::form(String::new(), ElicitationSchema::new(), None, None),
658 task: None,
659 _meta: None,
660 }
661 }
662}
663
664#[derive(Debug, Clone, Serialize, Deserialize)]
701pub struct ElicitResult {
702 pub action: ElicitationAction,
708
709 #[serde(skip_serializing_if = "Option::is_none")]
719 pub content: Option<std::collections::HashMap<String, serde_json::Value>>,
720
721 #[serde(skip_serializing_if = "Option::is_none")]
723 pub _meta: Option<serde_json::Value>,
724}
725
726#[derive(Debug, Clone, Serialize, Deserialize)]
731#[cfg(feature = "mcp-url-elicitation")]
732pub struct ElicitationCompleteParams {
733 #[serde(rename = "elicitationId")]
735 pub elicitation_id: String,
736}
737
738#[cfg(test)]
739mod tests {
740 use super::*;
741
742 #[test]
743 fn test_elicitation_action_serialization() {
744 assert_eq!(
745 serde_json::to_string(&ElicitationAction::Accept).unwrap(),
746 "\"accept\""
747 );
748 assert_eq!(
749 serde_json::to_string(&ElicitationAction::Decline).unwrap(),
750 "\"decline\""
751 );
752 assert_eq!(
753 serde_json::to_string(&ElicitationAction::Cancel).unwrap(),
754 "\"cancel\""
755 );
756 }
757
758 #[test]
759 fn test_form_elicit_params() {
760 let schema = ElicitationSchema::new().add_string_property(
761 "name".to_string(),
762 true,
763 Some("User name".to_string()),
764 );
765
766 let params = ElicitRequestParams::form(
767 "Please provide your name".to_string(),
768 schema,
769 Some(30000),
770 Some(true),
771 );
772
773 assert_eq!(params.message(), "Please provide your name");
774
775 let json = serde_json::to_string(¶ms).unwrap();
777 assert!(json.contains("Please provide your name"));
778 assert!(json.contains("requestedSchema"));
779 }
780
781 #[test]
782 #[cfg(feature = "mcp-url-elicitation")]
783 fn test_url_elicit_params() {
784 use url::Url;
785
786 let url = Url::parse("https://example.com/oauth/authorize").unwrap();
787 let params = ElicitRequestParams::url(
788 "test-id-123".to_string(),
789 "Please authorize the connection".to_string(),
790 url,
791 );
792
793 assert_eq!(params.message(), "Please authorize the connection");
794
795 let json = serde_json::to_string(¶ms).unwrap();
797 assert!(json.contains("test-id-123"));
798 assert!(json.contains("https://example.com/oauth/authorize"));
799 assert!(json.contains("\"mode\":\"url\""));
800 }
801
802 #[test]
803 #[cfg(feature = "mcp-url-elicitation")]
804 fn test_elicit_mode_serialization() {
805 assert_eq!(
806 serde_json::to_string(&ElicitMode::Form).unwrap(),
807 "\"form\""
808 );
809 assert_eq!(serde_json::to_string(&ElicitMode::Url).unwrap(), "\"url\"");
810 }
811
812 #[test]
813 #[cfg(feature = "mcp-url-elicitation")]
814 fn test_completion_notification() {
815 let params = ElicitationCompleteParams {
816 elicitation_id: "550e8400-e29b-41d4-a716-446655440000".to_string(),
817 };
818
819 let json = serde_json::to_string(¶ms).unwrap();
820 assert!(json.contains("550e8400-e29b-41d4-a716-446655440000"));
821 assert!(json.contains("elicitationId"));
822 }
823
824 #[test]
825 fn test_elicit_result_form_mode() {
826 let mut content = std::collections::HashMap::new();
827 content.insert("name".to_string(), serde_json::json!("Alice"));
828
829 let result = ElicitResult {
830 action: ElicitationAction::Accept,
831 content: Some(content),
832 _meta: None,
833 };
834
835 let json = serde_json::to_string(&result).unwrap();
836 assert!(json.contains("\"action\":\"accept\""));
837 assert!(json.contains("\"name\":\"Alice\""));
838 }
839
840 #[test]
841 fn test_elicit_result_url_mode() {
842 let result = ElicitResult {
844 action: ElicitationAction::Accept,
845 content: None,
846 _meta: None,
847 };
848
849 let json = serde_json::to_string(&result).unwrap();
850 assert!(json.contains("\"action\":\"accept\""));
851 assert!(!json.contains("content"));
852 }
853
854 #[test]
857 #[cfg(feature = "mcp-enum-improvements")]
858 fn test_titled_single_select_enum_schema() {
859 use super::{EnumOption, TitledSingleSelectEnumSchema};
860
861 let schema = TitledSingleSelectEnumSchema {
862 schema_type: "string".to_string(),
863 one_of: vec![
864 EnumOption {
865 const_value: "#FF0000".to_string(),
866 title: "Red".to_string(),
867 },
868 EnumOption {
869 const_value: "#00FF00".to_string(),
870 title: "Green".to_string(),
871 },
872 EnumOption {
873 const_value: "#0000FF".to_string(),
874 title: "Blue".to_string(),
875 },
876 ],
877 title: Some("Color Selection".to_string()),
878 description: Some("Choose your favorite color".to_string()),
879 default: Some("#FF0000".to_string()),
880 };
881
882 let json = serde_json::to_string(&schema).unwrap();
883 assert!(json.contains("\"type\":\"string\""));
884 assert!(json.contains("\"oneOf\""));
885 assert!(json.contains("\"const\":\"#FF0000\""));
886 assert!(json.contains("\"title\":\"Red\""));
887 assert!(json.contains("\"default\":\"#FF0000\""));
888
889 let deserialized: TitledSingleSelectEnumSchema = serde_json::from_str(&json).unwrap();
891 assert_eq!(deserialized.one_of.len(), 3);
892 assert_eq!(deserialized.one_of[0].const_value, "#FF0000");
893 assert_eq!(deserialized.one_of[0].title, "Red");
894 }
895
896 #[test]
897 #[cfg(feature = "mcp-enum-improvements")]
898 fn test_untitled_single_select_enum_schema() {
899 use super::UntitledSingleSelectEnumSchema;
900
901 let schema = UntitledSingleSelectEnumSchema {
902 schema_type: "string".to_string(),
903 enum_values: vec!["red".to_string(), "green".to_string(), "blue".to_string()],
904 title: Some("Color Selection".to_string()),
905 description: Some("Choose a color".to_string()),
906 default: Some("red".to_string()),
907 };
908
909 let json = serde_json::to_string(&schema).unwrap();
910 assert!(json.contains("\"type\":\"string\""));
911 assert!(json.contains("\"enum\""));
912 assert!(json.contains("\"red\""));
913 assert!(json.contains("\"green\""));
914 assert!(json.contains("\"blue\""));
915 assert!(!json.contains("oneOf"));
916
917 let deserialized: UntitledSingleSelectEnumSchema = serde_json::from_str(&json).unwrap();
919 assert_eq!(deserialized.enum_values.len(), 3);
920 assert_eq!(deserialized.enum_values[0], "red");
921 }
922
923 #[test]
924 #[cfg(feature = "mcp-enum-improvements")]
925 fn test_titled_multi_select_enum_schema() {
926 use super::{EnumOption, MultiSelectItems, TitledMultiSelectEnumSchema};
927
928 let schema = TitledMultiSelectEnumSchema {
929 schema_type: "array".to_string(),
930 min_items: Some(1),
931 max_items: Some(2),
932 items: MultiSelectItems {
933 any_of: vec![
934 EnumOption {
935 const_value: "#FF0000".to_string(),
936 title: "Red".to_string(),
937 },
938 EnumOption {
939 const_value: "#00FF00".to_string(),
940 title: "Green".to_string(),
941 },
942 ],
943 },
944 title: Some("Color Selection".to_string()),
945 description: Some("Choose up to 2 colors".to_string()),
946 default: Some(vec!["#FF0000".to_string()]),
947 };
948
949 let json = serde_json::to_string(&schema).unwrap();
950 assert!(json.contains("\"type\":\"array\""));
951 assert!(json.contains("\"minItems\":1"));
952 assert!(json.contains("\"maxItems\":2"));
953 assert!(json.contains("\"anyOf\""));
954 assert!(json.contains("\"const\":\"#FF0000\""));
955
956 let deserialized: TitledMultiSelectEnumSchema = serde_json::from_str(&json).unwrap();
958 assert_eq!(deserialized.items.any_of.len(), 2);
959 assert_eq!(deserialized.min_items, Some(1));
960 assert_eq!(deserialized.max_items, Some(2));
961 }
962
963 #[test]
964 #[cfg(feature = "mcp-enum-improvements")]
965 fn test_untitled_multi_select_enum_schema() {
966 use super::{UntitledMultiSelectEnumSchema, UntitledMultiSelectItems};
967
968 let schema = UntitledMultiSelectEnumSchema {
969 schema_type: "array".to_string(),
970 min_items: Some(1),
971 max_items: None,
972 items: UntitledMultiSelectItems {
973 schema_type: "string".to_string(),
974 enum_values: vec!["red".to_string(), "green".to_string(), "blue".to_string()],
975 },
976 title: Some("Color Selection".to_string()),
977 description: Some("Choose colors".to_string()),
978 default: Some(vec!["red".to_string(), "green".to_string()]),
979 };
980
981 let json = serde_json::to_string(&schema).unwrap();
982 assert!(json.contains("\"type\":\"array\""));
983 assert!(json.contains("\"minItems\":1"));
984 assert!(json.contains("\"items\""));
985 assert!(json.contains("\"enum\""));
986 assert!(!json.contains("anyOf"));
987
988 let deserialized: UntitledMultiSelectEnumSchema = serde_json::from_str(&json).unwrap();
990 assert_eq!(deserialized.items.enum_values.len(), 3);
991 assert_eq!(deserialized.default.as_ref().unwrap().len(), 2);
992 }
993
994 #[test]
995 #[cfg(feature = "mcp-enum-improvements")]
996 fn test_legacy_titled_enum_schema() {
997 use super::LegacyTitledEnumSchema;
998
999 let schema = LegacyTitledEnumSchema {
1000 schema_type: "string".to_string(),
1001 enum_values: vec!["#FF0000".to_string(), "#00FF00".to_string()],
1002 enum_names: vec!["Red".to_string(), "Green".to_string()],
1003 title: Some("Color Selection".to_string()),
1004 description: Some("Choose a color (legacy)".to_string()),
1005 default: Some("#FF0000".to_string()),
1006 };
1007
1008 let json = serde_json::to_string(&schema).unwrap();
1009 assert!(json.contains("\"type\":\"string\""));
1010 assert!(json.contains("\"enum\""));
1011 assert!(json.contains("\"enumNames\""));
1012 assert!(json.contains("\"Red\""));
1013 assert!(!json.contains("oneOf"));
1014
1015 let deserialized: LegacyTitledEnumSchema = serde_json::from_str(&json).unwrap();
1017 assert_eq!(deserialized.enum_values.len(), 2);
1018 assert_eq!(deserialized.enum_names.len(), 2);
1019 assert_eq!(deserialized.enum_names[0], "Red");
1020 }
1021
1022 #[test]
1023 #[cfg(feature = "mcp-enum-improvements")]
1024 fn test_enum_schema_union_type() {
1025 use super::EnumSchema;
1026
1027 let titled_json = r#"{
1029 "type": "string",
1030 "oneOf": [
1031 {"const": "red", "title": "Red"},
1032 {"const": "green", "title": "Green"}
1033 ]
1034 }"#;
1035
1036 let schema: EnumSchema = serde_json::from_str(titled_json).unwrap();
1037 match schema {
1038 EnumSchema::TitledSingleSelect(s) => {
1039 assert_eq!(s.one_of.len(), 2);
1040 assert_eq!(s.one_of[0].const_value, "red");
1041 }
1042 _ => panic!("Expected TitledSingleSelect variant"),
1043 }
1044
1045 let untitled_json = r#"{
1047 "type": "string",
1048 "enum": ["red", "green", "blue"]
1049 }"#;
1050
1051 let schema: EnumSchema = serde_json::from_str(untitled_json).unwrap();
1052 match schema {
1053 EnumSchema::UntitledSingleSelect(s) => {
1054 assert_eq!(s.enum_values.len(), 3);
1055 }
1056 _ => panic!("Expected UntitledSingleSelect variant"),
1057 }
1058 }
1059
1060 #[test]
1061 #[cfg(feature = "mcp-enum-improvements")]
1062 fn test_enum_option_serialization() {
1063 use super::EnumOption;
1064
1065 let option = EnumOption {
1066 const_value: "#FF0000".to_string(),
1067 title: "Red".to_string(),
1068 };
1069
1070 let json = serde_json::to_string(&option).unwrap();
1071 assert!(json.contains("\"const\":\"#FF0000\""));
1072 assert!(json.contains("\"title\":\"Red\""));
1073 assert!(!json.contains("const_value")); let deserialized: EnumOption = serde_json::from_str(&json).unwrap();
1077 assert_eq!(deserialized.const_value, "#FF0000");
1078 assert_eq!(deserialized.title, "Red");
1079 }
1080}