1use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, HashSet};
8
9use crate::types::{ClientCapabilities, ServerCapabilities};
10
11#[derive(Debug, Clone)]
31pub struct CapabilityMatcher {
32 compatibility_rules: HashMap<String, CompatibilityRule>,
34 defaults: HashMap<String, bool>,
36}
37
38#[derive(Debug, Clone)]
40pub enum CompatibilityRule {
41 RequireBoth,
43 RequireClient,
45 RequireServer,
47 Optional,
49 Custom(fn(&ClientCapabilities, &ServerCapabilities) -> bool),
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct CapabilitySet {
72 pub enabled_features: HashSet<String>,
74 pub client_capabilities: ClientCapabilities,
76 pub server_capabilities: ServerCapabilities,
78 pub metadata: HashMap<String, serde_json::Value>,
80}
81
82#[derive(Debug, Clone)]
84pub struct CapabilityNegotiator {
85 matcher: CapabilityMatcher,
87 strict_mode: bool,
89}
90
91impl Default for CapabilityMatcher {
92 fn default() -> Self {
93 Self::new()
94 }
95}
96
97impl CapabilityMatcher {
98 pub fn new() -> Self {
100 let mut matcher = Self {
101 compatibility_rules: HashMap::new(),
102 defaults: HashMap::new(),
103 };
104
105 matcher.add_rule("tools", CompatibilityRule::RequireServer);
107 matcher.add_rule("prompts", CompatibilityRule::RequireServer);
108 matcher.add_rule("resources", CompatibilityRule::RequireServer);
109 matcher.add_rule("logging", CompatibilityRule::RequireServer);
110 matcher.add_rule("sampling", CompatibilityRule::RequireClient);
111 matcher.add_rule("roots", CompatibilityRule::RequireClient);
112 matcher.add_rule("progress", CompatibilityRule::Optional);
113
114 matcher.set_default("progress", true);
116
117 matcher
118 }
119
120 pub fn add_rule(&mut self, feature: &str, rule: CompatibilityRule) {
122 self.compatibility_rules.insert(feature.to_string(), rule);
123 }
124
125 pub fn set_default(&mut self, feature: &str, enabled: bool) {
127 self.defaults.insert(feature.to_string(), enabled);
128 }
129
130 pub fn is_compatible(
132 &self,
133 feature: &str,
134 client: &ClientCapabilities,
135 server: &ServerCapabilities,
136 ) -> bool {
137 self.compatibility_rules.get(feature).map_or_else(
138 || {
139 Self::client_has_feature(feature, client)
141 || Self::server_has_feature(feature, server)
142 },
143 |rule| match rule {
144 CompatibilityRule::RequireBoth => {
145 Self::client_has_feature(feature, client)
146 && Self::server_has_feature(feature, server)
147 }
148 CompatibilityRule::RequireClient => Self::client_has_feature(feature, client),
149 CompatibilityRule::RequireServer => Self::server_has_feature(feature, server),
150 CompatibilityRule::Optional => true,
151 CompatibilityRule::Custom(func) => func(client, server),
152 },
153 )
154 }
155
156 fn client_has_feature(feature: &str, client: &ClientCapabilities) -> bool {
158 match feature {
159 "sampling" => client.sampling.is_some(),
160 "roots" => client.roots.is_some(),
161 _ => {
162 client
164 .experimental
165 .as_ref()
166 .is_some_and(|experimental| experimental.contains_key(feature))
167 }
168 }
169 }
170
171 fn server_has_feature(feature: &str, server: &ServerCapabilities) -> bool {
173 match feature {
174 "tools" => server.tools.is_some(),
175 "prompts" => server.prompts.is_some(),
176 "resources" => server.resources.is_some(),
177 "logging" => server.logging.is_some(),
178 _ => {
179 server
181 .experimental
182 .as_ref()
183 .is_some_and(|experimental| experimental.contains_key(feature))
184 }
185 }
186 }
187
188 fn get_all_features(
190 &self,
191 client: &ClientCapabilities,
192 server: &ServerCapabilities,
193 ) -> HashSet<String> {
194 let mut features = HashSet::new();
195
196 if client.sampling.is_some() {
198 features.insert("sampling".to_string());
199 }
200 if client.roots.is_some() {
201 features.insert("roots".to_string());
202 }
203
204 if server.tools.is_some() {
206 features.insert("tools".to_string());
207 }
208 if server.prompts.is_some() {
209 features.insert("prompts".to_string());
210 }
211 if server.resources.is_some() {
212 features.insert("resources".to_string());
213 }
214 if server.logging.is_some() {
215 features.insert("logging".to_string());
216 }
217
218 if let Some(experimental) = &client.experimental {
220 features.extend(experimental.keys().cloned());
221 }
222 if let Some(experimental) = &server.experimental {
223 features.extend(experimental.keys().cloned());
224 }
225
226 features.extend(self.defaults.keys().cloned());
228
229 features
230 }
231
232 pub fn negotiate(
239 &self,
240 client: &ClientCapabilities,
241 server: &ServerCapabilities,
242 ) -> Result<CapabilitySet, CapabilityError> {
243 let all_features = self.get_all_features(client, server);
244 let mut enabled_features = HashSet::new();
245 let mut incompatible_features = Vec::new();
246
247 for feature in &all_features {
248 if self.is_compatible(feature, client, server) {
249 enabled_features.insert(feature.clone());
250 } else {
251 incompatible_features.push(feature.clone());
252 }
253 }
254
255 if !incompatible_features.is_empty() {
256 return Err(CapabilityError::IncompatibleFeatures(incompatible_features));
257 }
258
259 for (feature, enabled) in &self.defaults {
261 if *enabled && !enabled_features.contains(feature) && all_features.contains(feature) {
262 enabled_features.insert(feature.clone());
263 }
264 }
265
266 Ok(CapabilitySet {
267 enabled_features,
268 client_capabilities: client.clone(),
269 server_capabilities: server.clone(),
270 metadata: HashMap::new(),
271 })
272 }
273}
274
275impl CapabilityNegotiator {
276 pub const fn new(matcher: CapabilityMatcher) -> Self {
278 Self {
279 matcher,
280 strict_mode: false,
281 }
282 }
283
284 pub const fn with_strict_mode(mut self) -> Self {
286 self.strict_mode = true;
287 self
288 }
289
290 pub fn negotiate(
298 &self,
299 client: &ClientCapabilities,
300 server: &ServerCapabilities,
301 ) -> Result<CapabilitySet, CapabilityError> {
302 match self.matcher.negotiate(client, server) {
303 Ok(capability_set) => Ok(capability_set),
304 Err(CapabilityError::IncompatibleFeatures(features)) if !self.strict_mode => {
305 tracing::warn!(
307 "Some features are incompatible and will be disabled: {:?}",
308 features
309 );
310
311 let all_features = self.matcher.get_all_features(client, server);
313 let mut enabled_features = HashSet::new();
314
315 for feature in &all_features {
316 if self.matcher.is_compatible(feature, client, server) {
317 enabled_features.insert(feature.clone());
318 }
319 }
320
321 Ok(CapabilitySet {
322 enabled_features,
323 client_capabilities: client.clone(),
324 server_capabilities: server.clone(),
325 metadata: HashMap::new(),
326 })
327 }
328 Err(err) => Err(err),
329 }
330 }
331
332 pub fn is_feature_enabled(capability_set: &CapabilitySet, feature: &str) -> bool {
334 capability_set.enabled_features.contains(feature)
335 }
336
337 pub fn get_enabled_features(capability_set: &CapabilitySet) -> Vec<String> {
339 let mut features: Vec<String> = capability_set.enabled_features.iter().cloned().collect();
340 features.sort();
341 features
342 }
343}
344
345impl Default for CapabilityNegotiator {
346 fn default() -> Self {
347 Self::new(CapabilityMatcher::new())
348 }
349}
350
351impl CapabilitySet {
352 pub fn empty() -> Self {
354 Self {
355 enabled_features: HashSet::new(),
356 client_capabilities: ClientCapabilities::default(),
357 server_capabilities: ServerCapabilities::default(),
358 metadata: HashMap::new(),
359 }
360 }
361
362 pub fn has_feature(&self, feature: &str) -> bool {
364 self.enabled_features.contains(feature)
365 }
366
367 pub fn enable_feature(&mut self, feature: String) {
369 self.enabled_features.insert(feature);
370 }
371
372 pub fn disable_feature(&mut self, feature: &str) {
374 self.enabled_features.remove(feature);
375 }
376
377 pub fn feature_count(&self) -> usize {
379 self.enabled_features.len()
380 }
381
382 pub fn add_metadata(&mut self, key: String, value: serde_json::Value) {
384 self.metadata.insert(key, value);
385 }
386
387 pub fn get_metadata(&self, key: &str) -> Option<&serde_json::Value> {
389 self.metadata.get(key)
390 }
391
392 pub fn summary(&self) -> CapabilitySummary {
394 CapabilitySummary {
395 total_features: self.enabled_features.len(),
396 client_features: self.count_client_features(),
397 server_features: self.count_server_features(),
398 enabled_features: self.enabled_features.iter().cloned().collect(),
399 }
400 }
401
402 fn count_client_features(&self) -> usize {
403 let mut count = 0;
404 if self.client_capabilities.sampling.is_some() {
405 count += 1;
406 }
407 if self.client_capabilities.roots.is_some() {
408 count += 1;
409 }
410 if let Some(experimental) = &self.client_capabilities.experimental {
411 count += experimental.len();
412 }
413 count
414 }
415
416 fn count_server_features(&self) -> usize {
417 let mut count = 0;
418 if self.server_capabilities.tools.is_some() {
419 count += 1;
420 }
421 if self.server_capabilities.prompts.is_some() {
422 count += 1;
423 }
424 if self.server_capabilities.resources.is_some() {
425 count += 1;
426 }
427 if self.server_capabilities.logging.is_some() {
428 count += 1;
429 }
430 if let Some(experimental) = &self.server_capabilities.experimental {
431 count += experimental.len();
432 }
433 count
434 }
435}
436
437#[derive(Debug, Clone, thiserror::Error)]
439pub enum CapabilityError {
440 #[error("Incompatible features: {0:?}")]
442 IncompatibleFeatures(Vec<String>),
443 #[error("Required feature missing: {0}")]
445 RequiredFeatureMissing(String),
446 #[error("Protocol version mismatch: client={client}, server={server}")]
448 VersionMismatch {
449 client: String,
451 server: String,
453 },
454 #[error("Capability negotiation failed: {0}")]
456 NegotiationFailed(String),
457}
458
459#[derive(Debug, Clone, Serialize, Deserialize)]
461pub struct CapabilitySummary {
462 pub total_features: usize,
464 pub client_features: usize,
466 pub server_features: usize,
468 pub enabled_features: Vec<String>,
470}
471
472pub mod utils {
474 use super::*;
475
476 pub fn minimal_client_capabilities() -> ClientCapabilities {
478 ClientCapabilities::default()
479 }
480
481 pub fn minimal_server_capabilities() -> ServerCapabilities {
483 ServerCapabilities::default()
484 }
485
486 pub fn full_client_capabilities() -> ClientCapabilities {
488 ClientCapabilities {
489 sampling: Some(Default::default()),
490 roots: Some(Default::default()),
491 elicitation: Some(Default::default()),
492 experimental: None,
493 }
494 }
495
496 pub fn full_server_capabilities() -> ServerCapabilities {
498 ServerCapabilities {
499 tools: Some(Default::default()),
500 prompts: Some(Default::default()),
501 resources: Some(Default::default()),
502 completions: Some(Default::default()),
503 logging: Some(Default::default()),
504 experimental: None,
505 }
506 }
507
508 pub fn are_compatible(client: &ClientCapabilities, server: &ServerCapabilities) -> bool {
510 let matcher = CapabilityMatcher::new();
511 matcher.negotiate(client, server).is_ok()
512 }
513}
514
515#[cfg(test)]
516mod tests {
517 use super::*;
518 use crate::types::*;
519
520 #[test]
521 fn test_capability_matcher() {
522 let matcher = CapabilityMatcher::new();
523
524 let client = ClientCapabilities {
525 sampling: Some(SamplingCapabilities),
526 roots: None,
527 elicitation: None,
528 experimental: None,
529 };
530
531 let server = ServerCapabilities {
532 tools: Some(ToolsCapabilities::default()),
533 prompts: None,
534 resources: None,
535 logging: None,
536 completions: None,
537 experimental: None,
538 };
539
540 assert!(matcher.is_compatible("sampling", &client, &server));
541 assert!(matcher.is_compatible("tools", &client, &server));
542 assert!(!matcher.is_compatible("roots", &client, &server));
543 }
544
545 #[test]
546 fn test_capability_negotiation() {
547 let negotiator = CapabilityNegotiator::default();
548
549 let client = utils::full_client_capabilities();
550 let server = utils::full_server_capabilities();
551
552 let result = negotiator.negotiate(&client, &server);
553 assert!(result.is_ok());
554
555 let capability_set = result.unwrap();
556 assert!(capability_set.has_feature("sampling"));
557 assert!(capability_set.has_feature("tools"));
558 assert!(capability_set.has_feature("roots"));
559 }
560
561 #[test]
562 fn test_strict_mode() {
563 let negotiator = CapabilityNegotiator::default().with_strict_mode();
564
565 let client = ClientCapabilities::default();
566 let server = ServerCapabilities::default();
567
568 let result = negotiator.negotiate(&client, &server);
569 assert!(result.is_ok()); }
571
572 #[test]
573 fn test_capability_summary() {
574 let mut capability_set = CapabilitySet::empty();
575 capability_set.enable_feature("tools".to_string());
576 capability_set.enable_feature("prompts".to_string());
577
578 let summary = capability_set.summary();
579 assert_eq!(summary.total_features, 2);
580 assert!(summary.enabled_features.contains(&"tools".to_string()));
581 }
582}
583
584pub mod builders {
594 use crate::types::{
595 ClientCapabilities, CompletionCapabilities, ElicitationCapabilities, LoggingCapabilities,
596 PromptsCapabilities, ResourcesCapabilities, RootsCapabilities, SamplingCapabilities,
597 ServerCapabilities, ToolsCapabilities,
598 };
599 use serde_json;
600 use std::collections::HashMap;
601 use std::marker::PhantomData;
602
603 #[derive(Debug, Clone)]
617 pub struct ServerCapabilitiesBuilderState<
618 const EXPERIMENTAL: bool = false,
619 const LOGGING: bool = false,
620 const COMPLETIONS: bool = false,
621 const PROMPTS: bool = false,
622 const RESOURCES: bool = false,
623 const TOOLS: bool = false,
624 >;
625
626 #[derive(Debug, Clone)]
632 pub struct ServerCapabilitiesBuilder<S = ServerCapabilitiesBuilderState> {
633 experimental: Option<HashMap<String, serde_json::Value>>,
634 logging: Option<LoggingCapabilities>,
635 completions: Option<CompletionCapabilities>,
636 prompts: Option<PromptsCapabilities>,
637 resources: Option<ResourcesCapabilities>,
638 tools: Option<ToolsCapabilities>,
639
640 negotiator: Option<super::CapabilityNegotiator>,
642 strict_validation: bool,
643
644 _state: PhantomData<S>,
645 }
646
647 impl ServerCapabilities {
648 pub fn builder() -> ServerCapabilitiesBuilder {
653 ServerCapabilitiesBuilder::new()
654 }
655 }
656
657 impl Default for ServerCapabilitiesBuilder {
658 fn default() -> Self {
659 Self::new()
660 }
661 }
662
663 impl ServerCapabilitiesBuilder {
664 pub fn new() -> Self {
666 Self {
667 experimental: None,
668 logging: None,
669 completions: None,
670 prompts: None,
671 resources: None,
672 tools: None,
673 negotiator: None,
674 strict_validation: false,
675 _state: PhantomData,
676 }
677 }
678 }
679
680 impl<S> ServerCapabilitiesBuilder<S> {
682 pub fn build(self) -> ServerCapabilities {
687 ServerCapabilities {
688 experimental: self.experimental,
689 logging: self.logging,
690 completions: self.completions,
691 prompts: self.prompts,
692 resources: self.resources,
693 tools: self.tools,
694 }
695 }
696
697 pub fn with_strict_validation(mut self) -> Self {
702 self.strict_validation = true;
703 self
704 }
705
706 pub fn with_negotiator(mut self, negotiator: super::CapabilityNegotiator) -> Self {
711 self.negotiator = Some(negotiator);
712 self
713 }
714
715 pub fn validate(&self) -> Result<(), String> {
720 if self.strict_validation {
721 if self.tools.is_none() && self.prompts.is_none() && self.resources.is_none() {
723 return Err("Server must provide at least one capability (tools, prompts, or resources)".to_string());
724 }
725
726 if let Some(ref experimental) = self.experimental {
728 for (key, value) in experimental {
729 if key.starts_with("turbomcp_") {
730 match key.as_str() {
732 "turbomcp_simd_level" => {
733 if !value.is_string() {
734 return Err(
735 "turbomcp_simd_level must be a string".to_string()
736 );
737 }
738 let level = value.as_str().unwrap_or("");
739 if !["none", "sse2", "sse4", "avx2", "avx512"].contains(&level)
740 {
741 return Err(format!("Invalid SIMD level: {}", level));
742 }
743 }
744 "turbomcp_enterprise_security" => {
745 if !value.is_boolean() {
746 return Err(
747 "turbomcp_enterprise_security must be a boolean"
748 .to_string(),
749 );
750 }
751 }
752 _ => {
753 }
755 }
756 }
757 }
758 }
759 }
760 Ok(())
761 }
762
763 pub fn summary(&self) -> String {
767 let mut capabilities = Vec::new();
768 if self.experimental.is_some() {
769 capabilities.push("experimental");
770 }
771 if self.logging.is_some() {
772 capabilities.push("logging");
773 }
774 if self.completions.is_some() {
775 capabilities.push("completions");
776 }
777 if self.prompts.is_some() {
778 capabilities.push("prompts");
779 }
780 if self.resources.is_some() {
781 capabilities.push("resources");
782 }
783 if self.tools.is_some() {
784 capabilities.push("tools");
785 }
786
787 if capabilities.is_empty() {
788 "No capabilities enabled".to_string()
789 } else {
790 format!("Enabled capabilities: {}", capabilities.join(", "))
791 }
792 }
793 }
794
795 impl<const L: bool, const C: bool, const P: bool, const R: bool, const T: bool>
801 ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<false, L, C, P, R, T>>
802 {
803 pub fn enable_experimental(
808 self,
809 ) -> ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<true, L, C, P, R, T>>
810 {
811 ServerCapabilitiesBuilder {
812 experimental: Some(HashMap::new()),
813 logging: self.logging,
814 completions: self.completions,
815 prompts: self.prompts,
816 resources: self.resources,
817 tools: self.tools,
818 negotiator: self.negotiator,
819 strict_validation: self.strict_validation,
820 _state: PhantomData,
821 }
822 }
823
824 pub fn enable_experimental_with(
826 self,
827 experimental: HashMap<String, serde_json::Value>,
828 ) -> ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<true, L, C, P, R, T>>
829 {
830 ServerCapabilitiesBuilder {
831 experimental: Some(experimental),
832 logging: self.logging,
833 completions: self.completions,
834 prompts: self.prompts,
835 resources: self.resources,
836 tools: self.tools,
837 negotiator: self.negotiator,
838 strict_validation: self.strict_validation,
839 _state: PhantomData,
840 }
841 }
842 }
843
844 impl<const E: bool, const C: bool, const P: bool, const R: bool, const T: bool>
846 ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, false, C, P, R, T>>
847 {
848 pub fn enable_logging(
850 self,
851 ) -> ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, true, C, P, R, T>>
852 {
853 ServerCapabilitiesBuilder {
854 experimental: self.experimental,
855 logging: Some(LoggingCapabilities),
856 completions: self.completions,
857 prompts: self.prompts,
858 resources: self.resources,
859 tools: self.tools,
860 negotiator: self.negotiator,
861 strict_validation: self.strict_validation,
862 _state: PhantomData,
863 }
864 }
865 }
866
867 impl<const E: bool, const L: bool, const P: bool, const R: bool, const T: bool>
869 ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, false, P, R, T>>
870 {
871 pub fn enable_completions(
873 self,
874 ) -> ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, true, P, R, T>>
875 {
876 ServerCapabilitiesBuilder {
877 experimental: self.experimental,
878 logging: self.logging,
879 completions: Some(CompletionCapabilities),
880 prompts: self.prompts,
881 resources: self.resources,
882 tools: self.tools,
883 negotiator: self.negotiator,
884 strict_validation: self.strict_validation,
885 _state: PhantomData,
886 }
887 }
888 }
889
890 impl<const E: bool, const L: bool, const C: bool, const R: bool, const T: bool>
892 ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, false, R, T>>
893 {
894 pub fn enable_prompts(
896 self,
897 ) -> ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, true, R, T>>
898 {
899 ServerCapabilitiesBuilder {
900 experimental: self.experimental,
901 logging: self.logging,
902 completions: self.completions,
903 prompts: Some(PromptsCapabilities { list_changed: None }),
904 resources: self.resources,
905 tools: self.tools,
906 negotiator: self.negotiator,
907 strict_validation: self.strict_validation,
908 _state: PhantomData,
909 }
910 }
911 }
912
913 impl<const E: bool, const L: bool, const C: bool, const P: bool, const T: bool>
915 ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, P, false, T>>
916 {
917 pub fn enable_resources(
919 self,
920 ) -> ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, P, true, T>>
921 {
922 ServerCapabilitiesBuilder {
923 experimental: self.experimental,
924 logging: self.logging,
925 completions: self.completions,
926 prompts: self.prompts,
927 resources: Some(ResourcesCapabilities {
928 subscribe: None,
929 list_changed: None,
930 }),
931 tools: self.tools,
932 negotiator: self.negotiator,
933 strict_validation: self.strict_validation,
934 _state: PhantomData,
935 }
936 }
937 }
938
939 impl<const E: bool, const L: bool, const C: bool, const P: bool, const R: bool>
941 ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, P, R, false>>
942 {
943 pub fn enable_tools(
945 self,
946 ) -> ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, P, R, true>>
947 {
948 ServerCapabilitiesBuilder {
949 experimental: self.experimental,
950 logging: self.logging,
951 completions: self.completions,
952 prompts: self.prompts,
953 resources: self.resources,
954 tools: Some(ToolsCapabilities { list_changed: None }),
955 negotiator: self.negotiator,
956 strict_validation: self.strict_validation,
957 _state: PhantomData,
958 }
959 }
960 }
961
962 impl<const E: bool, const L: bool, const C: bool, const P: bool, const R: bool>
968 ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, P, R, true>>
969 {
970 pub fn enable_tool_list_changed(mut self) -> Self {
975 if let Some(ref mut tools) = self.tools {
976 tools.list_changed = Some(true);
977 }
978 self
979 }
980 }
981
982 impl<const E: bool, const L: bool, const C: bool, const R: bool, const T: bool>
984 ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, true, R, T>>
985 {
986 pub fn enable_prompts_list_changed(mut self) -> Self {
988 if let Some(ref mut prompts) = self.prompts {
989 prompts.list_changed = Some(true);
990 }
991 self
992 }
993 }
994
995 impl<const E: bool, const L: bool, const C: bool, const P: bool, const T: bool>
997 ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, P, true, T>>
998 {
999 pub fn enable_resources_list_changed(mut self) -> Self {
1001 if let Some(ref mut resources) = self.resources {
1002 resources.list_changed = Some(true);
1003 }
1004 self
1005 }
1006
1007 pub fn enable_resources_subscribe(mut self) -> Self {
1009 if let Some(ref mut resources) = self.resources {
1010 resources.subscribe = Some(true);
1011 }
1012 self
1013 }
1014 }
1015
1016 impl<const L: bool, const C: bool, const P: bool, const R: bool, const T: bool>
1018 ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<true, L, C, P, R, T>>
1019 {
1020 pub fn add_experimental_capability<K, V>(mut self, key: K, value: V) -> Self
1024 where
1025 K: Into<String>,
1026 V: Into<serde_json::Value>,
1027 {
1028 if let Some(ref mut experimental) = self.experimental {
1029 experimental.insert(key.into(), value.into());
1030 }
1031 self
1032 }
1033
1034 pub fn with_simd_optimization(mut self, level: &str) -> Self {
1038 if let Some(ref mut experimental) = self.experimental {
1039 experimental.insert(
1040 "turbomcp_simd_level".to_string(),
1041 serde_json::Value::String(level.to_string()),
1042 );
1043 }
1044 self
1045 }
1046
1047 pub fn with_enterprise_security(mut self, enabled: bool) -> Self {
1051 if let Some(ref mut experimental) = self.experimental {
1052 experimental.insert(
1053 "turbomcp_enterprise_security".to_string(),
1054 serde_json::Value::Bool(enabled),
1055 );
1056 }
1057 self
1058 }
1059 }
1060
1061 #[derive(Debug, Clone)]
1073 pub struct ClientCapabilitiesBuilderState<
1074 const EXPERIMENTAL: bool = false,
1075 const ROOTS: bool = false,
1076 const SAMPLING: bool = false,
1077 const ELICITATION: bool = false,
1078 >;
1079
1080 #[derive(Debug, Clone)]
1086 pub struct ClientCapabilitiesBuilder<S = ClientCapabilitiesBuilderState> {
1087 experimental: Option<HashMap<String, serde_json::Value>>,
1088 roots: Option<RootsCapabilities>,
1089 sampling: Option<SamplingCapabilities>,
1090 elicitation: Option<ElicitationCapabilities>,
1091
1092 negotiator: Option<super::CapabilityNegotiator>,
1094 strict_validation: bool,
1095
1096 _state: PhantomData<S>,
1097 }
1098
1099 impl ClientCapabilities {
1100 pub fn builder()
1107 -> ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<true, true, true, true>>
1108 {
1109 ClientCapabilitiesBuilder::new()
1110 }
1111 }
1112
1113 impl Default for ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<true, true, true, true>> {
1114 fn default() -> Self {
1115 Self::new()
1116 }
1117 }
1118
1119 impl ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<true, true, true, true>> {
1120 pub fn new() -> Self {
1130 Self {
1131 experimental: Some(HashMap::new()),
1132 roots: Some(RootsCapabilities::default()),
1133 sampling: Some(SamplingCapabilities),
1134 elicitation: Some(ElicitationCapabilities::default()),
1135 negotiator: None,
1136 strict_validation: false,
1137 _state: PhantomData,
1138 }
1139 }
1140 }
1141
1142 impl ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<false, false, false, false>> {
1143 pub fn minimal() -> Self {
1156 Self {
1157 experimental: None,
1158 roots: None,
1159 sampling: None,
1160 elicitation: None,
1161 negotiator: None,
1162 strict_validation: false,
1163 _state: PhantomData,
1164 }
1165 }
1166 }
1167
1168 impl<S> ClientCapabilitiesBuilder<S> {
1170 pub fn build(self) -> ClientCapabilities {
1175 ClientCapabilities {
1176 experimental: self.experimental,
1177 roots: self.roots,
1178 sampling: self.sampling,
1179 elicitation: self.elicitation,
1180 }
1181 }
1182
1183 pub fn with_strict_validation(mut self) -> Self {
1188 self.strict_validation = true;
1189 self
1190 }
1191
1192 pub fn with_negotiator(mut self, negotiator: super::CapabilityNegotiator) -> Self {
1197 self.negotiator = Some(negotiator);
1198 self
1199 }
1200
1201 pub fn validate(&self) -> Result<(), String> {
1206 if self.strict_validation {
1207 if let Some(ref experimental) = self.experimental {
1209 for key in experimental.keys() {
1210 if key.starts_with("turbomcp_") {
1211 }
1216 }
1217 }
1218 }
1219 Ok(())
1220 }
1221
1222 pub fn summary(&self) -> String {
1226 let mut capabilities = Vec::new();
1227 if self.experimental.is_some() {
1228 capabilities.push("experimental");
1229 }
1230 if self.roots.is_some() {
1231 capabilities.push("roots");
1232 }
1233 if self.sampling.is_some() {
1234 capabilities.push("sampling");
1235 }
1236 if self.elicitation.is_some() {
1237 capabilities.push("elicitation");
1238 }
1239
1240 if capabilities.is_empty() {
1241 "No capabilities enabled".to_string()
1242 } else {
1243 format!("Enabled capabilities: {}", capabilities.join(", "))
1244 }
1245 }
1246 }
1247
1248 impl<const R: bool, const S: bool, const E: bool>
1254 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<false, R, S, E>>
1255 {
1256 pub fn enable_experimental(
1261 self,
1262 ) -> ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<true, R, S, E>> {
1263 ClientCapabilitiesBuilder {
1264 experimental: Some(HashMap::new()),
1265 roots: self.roots,
1266 sampling: self.sampling,
1267 elicitation: self.elicitation,
1268 negotiator: self.negotiator,
1269 strict_validation: self.strict_validation,
1270 _state: PhantomData,
1271 }
1272 }
1273
1274 pub fn enable_experimental_with(
1276 self,
1277 experimental: HashMap<String, serde_json::Value>,
1278 ) -> ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<true, R, S, E>> {
1279 ClientCapabilitiesBuilder {
1280 experimental: Some(experimental),
1281 roots: self.roots,
1282 sampling: self.sampling,
1283 elicitation: self.elicitation,
1284 negotiator: self.negotiator,
1285 strict_validation: self.strict_validation,
1286 _state: PhantomData,
1287 }
1288 }
1289 }
1290
1291 impl<const X: bool, const S: bool, const E: bool>
1293 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<X, false, S, E>>
1294 {
1295 pub fn enable_roots(
1297 self,
1298 ) -> ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<X, true, S, E>> {
1299 ClientCapabilitiesBuilder {
1300 experimental: self.experimental,
1301 roots: Some(RootsCapabilities { list_changed: None }),
1302 sampling: self.sampling,
1303 elicitation: self.elicitation,
1304 negotiator: self.negotiator,
1305 strict_validation: self.strict_validation,
1306 _state: PhantomData,
1307 }
1308 }
1309 }
1310
1311 impl<const X: bool, const R: bool, const E: bool>
1313 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<X, R, false, E>>
1314 {
1315 pub fn enable_sampling(
1317 self,
1318 ) -> ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<X, R, true, E>> {
1319 ClientCapabilitiesBuilder {
1320 experimental: self.experimental,
1321 roots: self.roots,
1322 sampling: Some(SamplingCapabilities),
1323 elicitation: self.elicitation,
1324 negotiator: self.negotiator,
1325 strict_validation: self.strict_validation,
1326 _state: PhantomData,
1327 }
1328 }
1329 }
1330
1331 impl<const X: bool, const R: bool, const S: bool>
1333 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<X, R, S, false>>
1334 {
1335 pub fn enable_elicitation(
1337 self,
1338 ) -> ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<X, R, S, true>> {
1339 ClientCapabilitiesBuilder {
1340 experimental: self.experimental,
1341 roots: self.roots,
1342 sampling: self.sampling,
1343 elicitation: Some(ElicitationCapabilities::default()),
1344 negotiator: self.negotiator,
1345 strict_validation: self.strict_validation,
1346 _state: PhantomData,
1347 }
1348 }
1349
1350 pub fn enable_elicitation_with_schema_validation(
1352 self,
1353 ) -> ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<X, R, S, true>> {
1354 ClientCapabilitiesBuilder {
1355 experimental: self.experimental,
1356 roots: self.roots,
1357 sampling: self.sampling,
1358 elicitation: Some(ElicitationCapabilities::default().with_schema_validation()),
1359 negotiator: self.negotiator,
1360 strict_validation: self.strict_validation,
1361 _state: PhantomData,
1362 }
1363 }
1364 }
1365
1366 impl<const X: bool, const S: bool, const E: bool>
1372 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<X, true, S, E>>
1373 {
1374 pub fn enable_roots_list_changed(mut self) -> Self {
1379 if let Some(ref mut roots) = self.roots {
1380 roots.list_changed = Some(true);
1381 }
1382 self
1383 }
1384 }
1385
1386 impl<const R: bool, const S: bool, const E: bool>
1392 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<true, R, S, E>>
1393 {
1394 pub fn without_experimental(
1399 self,
1400 ) -> ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<false, R, S, E>> {
1401 ClientCapabilitiesBuilder {
1402 experimental: None,
1403 roots: self.roots,
1404 sampling: self.sampling,
1405 elicitation: self.elicitation,
1406 negotiator: self.negotiator,
1407 strict_validation: self.strict_validation,
1408 _state: PhantomData,
1409 }
1410 }
1411 }
1412
1413 impl<const X: bool, const S: bool, const E: bool>
1415 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<X, true, S, E>>
1416 {
1417 pub fn without_roots(
1422 self,
1423 ) -> ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<X, false, S, E>> {
1424 ClientCapabilitiesBuilder {
1425 experimental: self.experimental,
1426 roots: None,
1427 sampling: self.sampling,
1428 elicitation: self.elicitation,
1429 negotiator: self.negotiator,
1430 strict_validation: self.strict_validation,
1431 _state: PhantomData,
1432 }
1433 }
1434 }
1435
1436 impl<const X: bool, const R: bool, const E: bool>
1438 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<X, R, true, E>>
1439 {
1440 pub fn without_sampling(
1445 self,
1446 ) -> ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<X, R, false, E>> {
1447 ClientCapabilitiesBuilder {
1448 experimental: self.experimental,
1449 roots: self.roots,
1450 sampling: None,
1451 elicitation: self.elicitation,
1452 negotiator: self.negotiator,
1453 strict_validation: self.strict_validation,
1454 _state: PhantomData,
1455 }
1456 }
1457 }
1458
1459 impl<const X: bool, const R: bool, const S: bool>
1461 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<X, R, S, true>>
1462 {
1463 pub fn without_elicitation(
1468 self,
1469 ) -> ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<X, R, S, false>> {
1470 ClientCapabilitiesBuilder {
1471 experimental: self.experimental,
1472 roots: self.roots,
1473 sampling: self.sampling,
1474 elicitation: None,
1475 negotiator: self.negotiator,
1476 strict_validation: self.strict_validation,
1477 _state: PhantomData,
1478 }
1479 }
1480 }
1481
1482 impl<const R: bool, const S: bool, const E: bool>
1484 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<true, R, S, E>>
1485 {
1486 pub fn add_experimental_capability<K, V>(mut self, key: K, value: V) -> Self
1490 where
1491 K: Into<String>,
1492 V: Into<serde_json::Value>,
1493 {
1494 if let Some(ref mut experimental) = self.experimental {
1495 experimental.insert(key.into(), value.into());
1496 }
1497 self
1498 }
1499 }
1500
1501 #[cfg(test)]
1502 mod type_state_tests {
1503 use super::*;
1504
1505 #[test]
1506 fn test_server_capabilities_builder_type_state() {
1507 let builder = ServerCapabilities::builder();
1509 assert!(format!("{:?}", builder).contains("ServerCapabilitiesBuilder"));
1510
1511 let builder_with_tools = builder.enable_tools();
1513
1514 let _final_builder = builder_with_tools.enable_tool_list_changed();
1516
1517 let full_capabilities = ServerCapabilitiesBuilder::new()
1519 .enable_experimental()
1520 .enable_logging()
1521 .enable_completions()
1522 .enable_prompts()
1523 .enable_resources()
1524 .enable_tools()
1525 .enable_tool_list_changed()
1526 .enable_prompts_list_changed()
1527 .enable_resources_list_changed()
1528 .enable_resources_subscribe()
1529 .build();
1530
1531 assert!(full_capabilities.experimental.is_some());
1532 assert!(full_capabilities.logging.is_some());
1533 assert!(full_capabilities.completions.is_some());
1534 assert!(full_capabilities.prompts.is_some());
1535 assert!(full_capabilities.resources.is_some());
1536 assert!(full_capabilities.tools.is_some());
1537
1538 if let Some(ref tools) = full_capabilities.tools {
1540 assert_eq!(tools.list_changed, Some(true));
1541 }
1542
1543 if let Some(ref resources) = full_capabilities.resources {
1544 assert_eq!(resources.list_changed, Some(true));
1545 assert_eq!(resources.subscribe, Some(true));
1546 }
1547 }
1548
1549 #[test]
1550 fn test_client_capabilities_builder_type_state() {
1551 let builder = ClientCapabilities::builder();
1553 assert!(format!("{:?}", builder).contains("ClientCapabilitiesBuilder"));
1554
1555 let builder_without_roots = builder.without_roots();
1557
1558 let builder_with_roots = builder_without_roots.enable_roots();
1560
1561 let _final_builder = builder_with_roots.enable_roots_list_changed();
1563
1564 let full_capabilities = ClientCapabilitiesBuilder::new()
1566 .enable_roots_list_changed()
1567 .build();
1568
1569 assert!(full_capabilities.experimental.is_some());
1570 assert!(full_capabilities.roots.is_some());
1571 assert!(full_capabilities.sampling.is_some());
1572 assert!(full_capabilities.elicitation.is_some());
1573
1574 if let Some(ref roots) = full_capabilities.roots {
1576 assert_eq!(roots.list_changed, Some(true));
1577 }
1578 }
1579
1580 #[test]
1581 fn test_opt_out_model() {
1582 let caps = ClientCapabilitiesBuilder::new().build();
1584 assert!(caps.experimental.is_some());
1585 assert!(caps.roots.is_some());
1586 assert!(caps.sampling.is_some());
1587 assert!(caps.elicitation.is_some());
1588
1589 let caps_without_elicitation = ClientCapabilitiesBuilder::new()
1591 .without_elicitation()
1592 .build();
1593
1594 assert!(caps_without_elicitation.experimental.is_some());
1595 assert!(caps_without_elicitation.roots.is_some());
1596 assert!(caps_without_elicitation.sampling.is_some());
1597 assert!(caps_without_elicitation.elicitation.is_none());
1598
1599 let minimal_caps = ClientCapabilitiesBuilder::new()
1601 .without_experimental()
1602 .without_roots()
1603 .without_elicitation()
1604 .build();
1605
1606 assert!(minimal_caps.experimental.is_none());
1607 assert!(minimal_caps.roots.is_none());
1608 assert!(minimal_caps.sampling.is_some());
1609 assert!(minimal_caps.elicitation.is_none());
1610 }
1611
1612 #[test]
1613 fn test_opt_in_with_minimal() {
1614 let caps = ClientCapabilitiesBuilder::minimal().build();
1616 assert!(caps.experimental.is_none());
1617 assert!(caps.roots.is_none());
1618 assert!(caps.sampling.is_none());
1619 assert!(caps.elicitation.is_none());
1620
1621 let caps_with_sampling = ClientCapabilitiesBuilder::minimal()
1623 .enable_sampling()
1624 .build();
1625
1626 assert!(caps_with_sampling.experimental.is_none());
1627 assert!(caps_with_sampling.roots.is_none());
1628 assert!(caps_with_sampling.sampling.is_some());
1629 assert!(caps_with_sampling.elicitation.is_none());
1630
1631 let full_caps = ClientCapabilitiesBuilder::minimal()
1633 .enable_experimental()
1634 .enable_roots()
1635 .enable_sampling()
1636 .enable_elicitation()
1637 .build();
1638
1639 assert!(full_caps.experimental.is_some());
1640 assert!(full_caps.roots.is_some());
1641 assert!(full_caps.sampling.is_some());
1642 assert!(full_caps.elicitation.is_some());
1643 }
1644
1645 #[test]
1646 fn test_turbomcp_extensions() {
1647 let server_caps = ServerCapabilities::builder()
1649 .enable_experimental()
1650 .with_simd_optimization("avx2")
1651 .with_enterprise_security(true)
1652 .build();
1653
1654 if let Some(ref experimental) = server_caps.experimental {
1655 assert!(experimental.contains_key("turbomcp_simd_level"));
1656 assert!(experimental.contains_key("turbomcp_enterprise_security"));
1657 assert_eq!(
1658 experimental.get("turbomcp_simd_level").unwrap().as_str(),
1659 Some("avx2")
1660 );
1661 assert_eq!(
1662 experimental
1663 .get("turbomcp_enterprise_security")
1664 .unwrap()
1665 .as_bool(),
1666 Some(true)
1667 );
1668 } else {
1669 panic!("Expected experimental capabilities to be set");
1670 }
1671
1672 let client_caps = ClientCapabilities::builder()
1674 .add_experimental_capability("custom_feature", true)
1675 .build();
1676
1677 if let Some(ref experimental) = client_caps.experimental {
1678 assert!(experimental.contains_key("custom_feature"));
1679 } else {
1680 panic!("Expected experimental capabilities to be set");
1681 }
1682 }
1683
1684 #[test]
1685 fn test_minimal_constructor() {
1686 let minimal_client_caps = ClientCapabilitiesBuilder::minimal().build();
1688 assert!(minimal_client_caps.experimental.is_none());
1689 assert!(minimal_client_caps.roots.is_none());
1690 assert!(minimal_client_caps.sampling.is_none());
1691 assert!(minimal_client_caps.elicitation.is_none());
1692
1693 let sampling_only = ClientCapabilitiesBuilder::minimal()
1695 .enable_sampling()
1696 .build();
1697 assert!(sampling_only.experimental.is_none());
1698 assert!(sampling_only.roots.is_none());
1699 assert!(sampling_only.sampling.is_some());
1700 assert!(sampling_only.elicitation.is_none());
1701
1702 let sampling_focused_client = ClientCapabilitiesBuilder::minimal()
1704 .enable_experimental()
1705 .enable_sampling()
1706 .build();
1707 assert!(sampling_focused_client.experimental.is_some());
1708 assert!(sampling_focused_client.roots.is_none());
1709 assert!(sampling_focused_client.sampling.is_some());
1710 assert!(sampling_focused_client.elicitation.is_none());
1711 }
1712
1713 #[test]
1714 fn test_builder_default_implementations() {
1715 let default_server_builder = ServerCapabilitiesBuilder::default();
1717 let server_caps = default_server_builder.build();
1718 assert!(server_caps.tools.is_none()); let default_client_builder = ClientCapabilitiesBuilder::default();
1721 let client_caps = default_client_builder.build();
1722 assert!(client_caps.experimental.is_some());
1724 assert!(client_caps.roots.is_some());
1725 assert!(client_caps.sampling.is_some());
1726 assert!(client_caps.elicitation.is_some());
1727 }
1728
1729 #[test]
1730 fn test_builder_chaining() {
1731 let server_caps = ServerCapabilities::builder()
1733 .enable_experimental()
1734 .enable_tools()
1735 .enable_prompts()
1736 .enable_resources()
1737 .enable_tool_list_changed()
1738 .enable_prompts_list_changed()
1739 .enable_resources_list_changed()
1740 .enable_resources_subscribe()
1741 .add_experimental_capability("custom_feature", true)
1742 .build();
1743
1744 assert!(server_caps.experimental.is_some());
1745 assert!(server_caps.tools.is_some());
1746 assert!(server_caps.prompts.is_some());
1747 assert!(server_caps.resources.is_some());
1748
1749 if let Some(ref experimental) = server_caps.experimental {
1751 assert!(experimental.contains_key("custom_feature"));
1752 }
1753 }
1754
1755 #[test]
1756 fn test_with_negotiator_integration() {
1757 let negotiator = super::super::CapabilityNegotiator::default();
1759
1760 let server_caps = ServerCapabilities::builder()
1761 .enable_tools()
1762 .with_negotiator(negotiator.clone())
1763 .with_strict_validation()
1764 .build();
1765
1766 assert!(server_caps.tools.is_some());
1767 }
1770
1771 #[test]
1772 fn test_builder_validation_methods() {
1773 let server_builder = ServerCapabilities::builder()
1775 .enable_experimental()
1776 .enable_tools()
1777 .with_simd_optimization("avx2")
1778 .with_enterprise_security(true)
1779 .with_strict_validation();
1780
1781 assert!(server_builder.validate().is_ok());
1783
1784 let summary = server_builder.summary();
1786 assert!(summary.contains("experimental"));
1787 assert!(summary.contains("tools"));
1788
1789 let client_builder = ClientCapabilities::builder()
1791 .add_experimental_capability("custom_feature", true)
1792 .with_strict_validation();
1793
1794 assert!(client_builder.validate().is_ok());
1796
1797 let summary = client_builder.summary();
1799 assert!(summary.contains("experimental"));
1800 assert!(summary.contains("sampling"));
1801 }
1802
1803 #[test]
1804 fn test_builder_validation_errors() {
1805 let server_builder = ServerCapabilities::builder()
1807 .enable_experimental()
1808 .with_strict_validation();
1809
1810 assert!(server_builder.validate().is_err());
1812 let error = server_builder.validate().unwrap_err();
1813 assert!(error.contains("at least one capability"));
1814
1815 let invalid_server_builder = ServerCapabilities::builder()
1817 .enable_experimental()
1818 .enable_tools()
1819 .add_experimental_capability("turbomcp_simd_level", "invalid_level")
1820 .with_strict_validation();
1821
1822 assert!(invalid_server_builder.validate().is_err());
1823 let error = invalid_server_builder.validate().unwrap_err();
1824 assert!(error.contains("Invalid SIMD level"));
1825
1826 let client_builder = ClientCapabilities::builder()
1828 .add_experimental_capability("custom_feature", true)
1829 .with_strict_validation();
1830
1831 assert!(client_builder.validate().is_ok());
1832 }
1833
1834 #[test]
1835 fn test_builder_clone_support() {
1836 let original_server_builder = ServerCapabilities::builder()
1838 .enable_tools()
1839 .enable_prompts();
1840
1841 let cloned_server_builder = original_server_builder.clone();
1842
1843 let original_caps = original_server_builder.build();
1845 let cloned_caps = cloned_server_builder.build();
1846
1847 assert_eq!(original_caps.tools.is_some(), cloned_caps.tools.is_some());
1848 assert_eq!(
1849 original_caps.prompts.is_some(),
1850 cloned_caps.prompts.is_some()
1851 );
1852
1853 let original_client_builder = ClientCapabilities::builder()
1855 .without_experimental()
1856 .without_roots();
1857
1858 let cloned_client_builder = original_client_builder.clone();
1859
1860 let original_caps = original_client_builder.build();
1861 let cloned_caps = cloned_client_builder.build();
1862
1863 assert_eq!(
1864 original_caps.experimental.is_some(),
1865 cloned_caps.experimental.is_some()
1866 );
1867 assert_eq!(original_caps.roots.is_some(), cloned_caps.roots.is_some());
1868 assert_eq!(
1869 original_caps.sampling.is_some(),
1870 cloned_caps.sampling.is_some()
1871 );
1872 assert_eq!(
1873 original_caps.elicitation.is_some(),
1874 cloned_caps.elicitation.is_some()
1875 );
1876 }
1877 }
1878}