1use crate::extension::ExtensionRegistry;
7use crate::types::Icon;
8use crate::types::meta::Meta;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Default, Serialize, Deserialize)]
13pub struct ServerCapabilities {
14 #[serde(skip_serializing_if = "Option::is_none")]
16 pub tools: Option<ToolCapability>,
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub resources: Option<ResourceCapability>,
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub prompts: Option<PromptCapability>,
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub tasks: Option<TasksCapability>,
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub logging: Option<LoggingCapability>,
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub completions: Option<CompletionCapability>,
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub experimental: Option<serde_json::Value>,
35}
36
37impl ServerCapabilities {
38 #[must_use]
40 pub fn new() -> Self {
41 Self::default()
42 }
43
44 #[must_use]
46 pub fn with_tools(mut self) -> Self {
47 self.tools = Some(ToolCapability::default());
48 self
49 }
50
51 #[must_use]
53 pub const fn with_tools_and_changes(mut self) -> Self {
54 self.tools = Some(ToolCapability {
55 list_changed: Some(true),
56 });
57 self
58 }
59
60 #[must_use]
62 pub fn with_resources(mut self) -> Self {
63 self.resources = Some(ResourceCapability::default());
64 self
65 }
66
67 #[must_use]
69 pub const fn with_resources_and_subscriptions(mut self) -> Self {
70 self.resources = Some(ResourceCapability {
71 subscribe: Some(true),
72 list_changed: Some(true),
73 });
74 self
75 }
76
77 #[must_use]
79 pub fn with_prompts(mut self) -> Self {
80 self.prompts = Some(PromptCapability::default());
81 self
82 }
83
84 #[must_use]
91 pub fn with_tasks(mut self) -> Self {
92 let tasks = self.tasks.get_or_insert_with(TasksCapability::default);
93 tasks.list = Some(serde_json::json!({}));
94 tasks.cancel = Some(serde_json::json!({}));
95 self
96 }
97
98 #[must_use]
101 pub fn with_task_tools(mut self) -> Self {
102 self.tasks
103 .get_or_insert_with(TasksCapability::default)
104 .requests
105 .get_or_insert_with(TaskRequestsCapability::default)
106 .tools
107 .get_or_insert_with(ToolsTaskCapability::default)
108 .call = Some(serde_json::json!({}));
109 self
110 }
111
112 #[must_use]
114 pub const fn with_logging(mut self) -> Self {
115 self.logging = Some(LoggingCapability {});
116 self
117 }
118
119 #[must_use]
121 pub const fn with_completions(mut self) -> Self {
122 self.completions = Some(CompletionCapability {});
123 self
124 }
125
126 #[must_use]
128 pub const fn has_tools(&self) -> bool {
129 self.tools.is_some()
130 }
131
132 #[must_use]
134 pub const fn has_resources(&self) -> bool {
135 self.resources.is_some()
136 }
137
138 #[must_use]
140 pub const fn has_prompts(&self) -> bool {
141 self.prompts.is_some()
142 }
143
144 #[must_use]
146 pub const fn has_tasks(&self) -> bool {
147 self.tasks.is_some()
148 }
149
150 #[must_use]
152 pub const fn has_completions(&self) -> bool {
153 self.completions.is_some()
154 }
155
156 #[must_use]
158 pub const fn has_logging(&self) -> bool {
159 self.logging.is_some()
160 }
161
162 #[must_use]
164 pub fn has_resource_subscribe(&self) -> bool {
165 self.resources
166 .as_ref()
167 .and_then(|r| r.subscribe)
168 .unwrap_or(false)
169 }
170
171 #[must_use]
195 pub fn with_extensions(mut self, registry: ExtensionRegistry) -> Self {
196 if !registry.is_empty() {
197 self.experimental = Some(registry.to_experimental());
198 }
199 self
200 }
201
202 #[must_use]
208 pub fn has_extension(&self, name: &str) -> bool {
209 self.experimental
210 .as_ref()
211 .and_then(ExtensionRegistry::from_experimental)
212 .is_some_and(|registry| registry.has(name))
213 }
214
215 #[must_use]
219 pub fn extensions(&self) -> Option<ExtensionRegistry> {
220 self.experimental
221 .as_ref()
222 .and_then(ExtensionRegistry::from_experimental)
223 }
224}
225
226#[derive(Debug, Clone, Default, Serialize, Deserialize)]
228pub struct ClientCapabilities {
229 #[serde(skip_serializing_if = "Option::is_none")]
231 pub roots: Option<RootsCapability>,
232 #[serde(skip_serializing_if = "Option::is_none")]
234 pub sampling: Option<SamplingCapability>,
235 #[serde(skip_serializing_if = "Option::is_none")]
237 pub elicitation: Option<ElicitationCapability>,
238 #[serde(skip_serializing_if = "Option::is_none")]
241 pub tasks: Option<TasksCapability>,
242 #[serde(skip_serializing_if = "Option::is_none")]
244 pub experimental: Option<serde_json::Value>,
245}
246
247impl ClientCapabilities {
248 #[must_use]
250 pub fn new() -> Self {
251 Self::default()
252 }
253
254 #[must_use]
256 pub fn with_roots(mut self) -> Self {
257 self.roots = Some(RootsCapability::default());
258 self
259 }
260
261 #[must_use]
263 pub const fn with_roots_and_changes(mut self) -> Self {
264 self.roots = Some(RootsCapability {
265 list_changed: Some(true),
266 });
267 self
268 }
269
270 #[must_use]
275 pub fn with_sampling(mut self) -> Self {
276 self.sampling = Some(SamplingCapability::default());
277 self
278 }
279
280 #[must_use]
282 pub fn with_sampling_tools(mut self) -> Self {
283 let sampling = self
284 .sampling
285 .get_or_insert_with(SamplingCapability::default);
286 sampling.tools = Some(serde_json::json!({}));
287 self
288 }
289
290 #[must_use]
293 pub fn with_sampling_context(mut self) -> Self {
294 let sampling = self
295 .sampling
296 .get_or_insert_with(SamplingCapability::default);
297 sampling.context = Some(serde_json::json!({}));
298 self
299 }
300
301 #[must_use]
307 pub fn with_elicitation(mut self) -> Self {
308 self.elicitation = Some(ElicitationCapability {
309 form: None,
310 url: None,
311 });
312 self
313 }
314
315 #[must_use]
317 pub fn with_form_elicitation(mut self) -> Self {
318 self.elicitation
319 .get_or_insert_with(ElicitationCapability::default)
320 .form = Some(serde_json::json!({}));
321 self
322 }
323
324 #[must_use]
326 pub fn with_url_elicitation(mut self) -> Self {
327 self.elicitation
328 .get_or_insert_with(ElicitationCapability::default)
329 .url = Some(serde_json::json!({}));
330 self
331 }
332
333 #[must_use]
339 pub fn with_tasks(mut self) -> Self {
340 let tasks = self.tasks.get_or_insert_with(TasksCapability::default);
341 tasks.list = Some(serde_json::json!({}));
342 tasks.cancel = Some(serde_json::json!({}));
343 self
344 }
345
346 #[must_use]
352 pub fn with_task_sampling(mut self) -> Self {
353 self.tasks
354 .get_or_insert_with(TasksCapability::default)
355 .requests
356 .get_or_insert_with(TaskRequestsCapability::default)
357 .sampling
358 .get_or_insert_with(SamplingTaskCapability::default)
359 .create_message = Some(serde_json::json!({}));
360 self
361 }
362
363 #[must_use]
365 pub fn has_task_sampling(&self) -> bool {
366 self.tasks
367 .as_ref()
368 .and_then(|t| t.requests.as_ref())
369 .and_then(|r| r.sampling.as_ref())
370 .is_some_and(|s| s.create_message.is_some())
371 }
372
373 #[must_use]
378 pub fn has_form_elicitation(&self) -> bool {
379 self.elicitation
380 .as_ref()
381 .is_some_and(ElicitationCapability::has_form)
382 }
383
384 #[must_use]
386 pub fn has_url_elicitation(&self) -> bool {
387 self.elicitation
388 .as_ref()
389 .is_some_and(ElicitationCapability::has_url)
390 }
391
392 #[must_use]
394 pub const fn has_roots(&self) -> bool {
395 self.roots.is_some()
396 }
397
398 #[must_use]
400 pub const fn has_sampling(&self) -> bool {
401 self.sampling.is_some()
402 }
403
404 #[must_use]
407 pub const fn has_sampling_tools(&self) -> bool {
408 matches!(&self.sampling, Some(s) if s.tools.is_some())
409 }
410
411 #[must_use]
414 pub const fn has_sampling_context(&self) -> bool {
415 matches!(&self.sampling, Some(s) if s.context.is_some())
416 }
417
418 #[must_use]
420 pub const fn has_elicitation(&self) -> bool {
421 self.elicitation.is_some()
422 }
423
424 #[must_use]
428 pub fn with_extensions(mut self, registry: ExtensionRegistry) -> Self {
429 if !registry.is_empty() {
430 self.experimental = Some(registry.to_experimental());
431 }
432 self
433 }
434
435 #[must_use]
437 pub fn has_extension(&self, name: &str) -> bool {
438 self.experimental
439 .as_ref()
440 .and_then(ExtensionRegistry::from_experimental)
441 .is_some_and(|registry| registry.has(name))
442 }
443
444 #[must_use]
446 pub fn extensions(&self) -> Option<ExtensionRegistry> {
447 self.experimental
448 .as_ref()
449 .and_then(ExtensionRegistry::from_experimental)
450 }
451}
452
453#[derive(Debug, Clone, Default, Serialize, Deserialize)]
455pub struct ToolCapability {
456 #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
458 pub list_changed: Option<bool>,
459}
460
461#[derive(Debug, Clone, Default, Serialize, Deserialize)]
463pub struct ResourceCapability {
464 #[serde(skip_serializing_if = "Option::is_none")]
466 pub subscribe: Option<bool>,
467 #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
469 pub list_changed: Option<bool>,
470}
471
472#[derive(Debug, Clone, Default, Serialize, Deserialize)]
474pub struct PromptCapability {
475 #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
477 pub list_changed: Option<bool>,
478}
479
480#[derive(Debug, Clone, Default, Serialize, Deserialize)]
487pub struct TasksCapability {
488 #[serde(skip_serializing_if = "Option::is_none")]
490 pub list: Option<serde_json::Value>,
491 #[serde(skip_serializing_if = "Option::is_none")]
493 pub cancel: Option<serde_json::Value>,
494 #[serde(skip_serializing_if = "Option::is_none")]
496 pub requests: Option<TaskRequestsCapability>,
497}
498
499#[derive(Debug, Clone, Default, Serialize, Deserialize)]
502pub struct TaskRequestsCapability {
503 #[serde(skip_serializing_if = "Option::is_none")]
505 pub sampling: Option<SamplingTaskCapability>,
506 #[serde(skip_serializing_if = "Option::is_none")]
508 pub elicitation: Option<ElicitationTaskCapability>,
509 #[serde(skip_serializing_if = "Option::is_none")]
511 pub tools: Option<ToolsTaskCapability>,
512}
513
514#[derive(Debug, Clone, Default, Serialize, Deserialize)]
516pub struct SamplingTaskCapability {
517 #[serde(rename = "createMessage", skip_serializing_if = "Option::is_none")]
519 pub create_message: Option<serde_json::Value>,
520}
521
522#[derive(Debug, Clone, Default, Serialize, Deserialize)]
524pub struct ElicitationTaskCapability {
525 #[serde(skip_serializing_if = "Option::is_none")]
527 pub create: Option<serde_json::Value>,
528}
529
530#[derive(Debug, Clone, Default, Serialize, Deserialize)]
532pub struct ToolsTaskCapability {
533 #[serde(skip_serializing_if = "Option::is_none")]
535 pub call: Option<serde_json::Value>,
536}
537
538#[derive(Debug, Clone, Default, Serialize, Deserialize)]
540pub struct LoggingCapability {}
541
542#[derive(Debug, Clone, Default, Serialize, Deserialize)]
544pub struct CompletionCapability {}
545
546#[derive(Debug, Clone, Default, Serialize, Deserialize)]
548pub struct RootsCapability {
549 #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
551 pub list_changed: Option<bool>,
552}
553
554#[derive(Debug, Clone, Default, Serialize, Deserialize)]
556pub struct SamplingCapability {
557 #[serde(skip_serializing_if = "Option::is_none")]
559 pub context: Option<serde_json::Value>,
560 #[serde(skip_serializing_if = "Option::is_none")]
562 pub tools: Option<serde_json::Value>,
563}
564
565#[derive(Debug, Clone, Default, Serialize, Deserialize)]
567pub struct ElicitationCapability {
568 #[serde(skip_serializing_if = "Option::is_none")]
571 pub form: Option<serde_json::Value>,
572 #[serde(skip_serializing_if = "Option::is_none")]
574 pub url: Option<serde_json::Value>,
575}
576
577impl ElicitationCapability {
578 #[must_use]
581 pub const fn has_form(&self) -> bool {
582 self.form.is_some() || self.url.is_none()
583 }
584
585 #[must_use]
587 pub const fn has_url(&self) -> bool {
588 self.url.is_some()
589 }
590}
591
592#[derive(Debug, Clone, Serialize, Deserialize)]
594pub struct ServerInfo {
595 pub name: String,
597 #[serde(skip_serializing_if = "Option::is_none")]
599 pub title: Option<String>,
600 pub version: String,
602 #[serde(rename = "protocolVersion", skip_serializing_if = "Option::is_none")]
604 pub protocol_version: Option<String>,
605 #[serde(skip_serializing_if = "Option::is_none")]
607 pub description: Option<String>,
608 #[serde(rename = "websiteUrl", skip_serializing_if = "Option::is_none")]
610 pub website_url: Option<String>,
611 #[serde(skip_serializing_if = "Option::is_none")]
613 pub icons: Option<Vec<Icon>>,
614}
615
616impl ServerInfo {
617 #[must_use]
619 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
620 Self {
621 name: name.into(),
622 title: None,
623 version: version.into(),
624 protocol_version: Some(PROTOCOL_VERSION.to_string()),
625 description: None,
626 website_url: None,
627 icons: None,
628 }
629 }
630
631 #[must_use]
633 pub fn description(mut self, description: impl Into<String>) -> Self {
634 self.description = Some(description.into());
635 self
636 }
637
638 #[must_use]
640 pub fn website_url(mut self, website_url: impl Into<String>) -> Self {
641 self.website_url = Some(website_url.into());
642 self
643 }
644
645 #[must_use]
647 pub fn title(mut self, title: impl Into<String>) -> Self {
648 self.title = Some(title.into());
649 self
650 }
651
652 #[must_use]
654 pub fn icon(mut self, icon: Icon) -> Self {
655 self.icons.get_or_insert_with(Vec::new).push(icon);
656 self
657 }
658
659 #[must_use]
661 pub fn icons(mut self, icons: impl IntoIterator<Item = Icon>) -> Self {
662 self.icons = Some(icons.into_iter().collect());
663 self
664 }
665}
666
667#[derive(Debug, Clone, Serialize, Deserialize)]
669pub struct ClientInfo {
670 pub name: String,
672 #[serde(skip_serializing_if = "Option::is_none")]
674 pub title: Option<String>,
675 pub version: String,
677 #[serde(skip_serializing_if = "Option::is_none")]
679 pub description: Option<String>,
680 #[serde(rename = "websiteUrl", skip_serializing_if = "Option::is_none")]
682 pub website_url: Option<String>,
683 #[serde(skip_serializing_if = "Option::is_none")]
685 pub icons: Option<Vec<Icon>>,
686}
687
688impl ClientInfo {
689 #[must_use]
691 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
692 Self {
693 name: name.into(),
694 title: None,
695 version: version.into(),
696 description: None,
697 website_url: None,
698 icons: None,
699 }
700 }
701
702 #[must_use]
704 pub fn description(mut self, description: impl Into<String>) -> Self {
705 self.description = Some(description.into());
706 self
707 }
708
709 #[must_use]
711 pub fn website_url(mut self, website_url: impl Into<String>) -> Self {
712 self.website_url = Some(website_url.into());
713 self
714 }
715
716 #[must_use]
718 pub fn title(mut self, title: impl Into<String>) -> Self {
719 self.title = Some(title.into());
720 self
721 }
722
723 #[must_use]
725 pub fn icon(mut self, icon: Icon) -> Self {
726 self.icons.get_or_insert_with(Vec::new).push(icon);
727 self
728 }
729
730 #[must_use]
732 pub fn icons(mut self, icons: impl IntoIterator<Item = Icon>) -> Self {
733 self.icons = Some(icons.into_iter().collect());
734 self
735 }
736}
737
738#[derive(Debug, Clone, Serialize, Deserialize)]
740pub struct InitializeRequest {
741 #[serde(rename = "protocolVersion")]
743 pub protocol_version: String,
744 pub capabilities: ClientCapabilities,
746 #[serde(rename = "clientInfo")]
748 pub client_info: ClientInfo,
749 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
751 pub meta: Option<Meta>,
752}
753
754impl InitializeRequest {
755 #[must_use]
757 pub fn new(client_info: ClientInfo, capabilities: ClientCapabilities) -> Self {
758 Self {
759 protocol_version: PROTOCOL_VERSION.to_string(),
760 capabilities,
761 client_info,
762 meta: None,
763 }
764 }
765}
766
767#[derive(Debug, Clone, Serialize, Deserialize)]
769pub struct InitializeResult {
770 #[serde(rename = "protocolVersion")]
772 pub protocol_version: String,
773 pub capabilities: ServerCapabilities,
775 #[serde(rename = "serverInfo")]
777 pub server_info: ServerInfo,
778 #[serde(skip_serializing_if = "Option::is_none")]
780 pub instructions: Option<String>,
781 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
783 pub meta: Option<Meta>,
784}
785
786impl InitializeResult {
787 #[must_use]
789 pub fn new(server_info: ServerInfo, capabilities: ServerCapabilities) -> Self {
790 Self {
791 protocol_version: PROTOCOL_VERSION.to_string(),
792 capabilities,
793 server_info,
794 instructions: None,
795 meta: None,
796 }
797 }
798
799 #[must_use]
801 pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
802 self.instructions = Some(instructions.into());
803 self
804 }
805}
806
807pub const PROTOCOL_VERSION: &str = "2025-11-25";
811
812pub const SUPPORTED_PROTOCOL_VERSIONS: &[&str] = &[
839 "2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05", ];
844
845#[must_use]
864pub fn is_version_supported(version: &str) -> bool {
865 SUPPORTED_PROTOCOL_VERSIONS.contains(&version)
866}
867
868#[must_use]
900pub fn negotiate_version(requested_version: &str) -> &'static str {
901 if is_version_supported(requested_version) {
902 SUPPORTED_PROTOCOL_VERSIONS
904 .iter()
905 .find(|&&v| v == requested_version)
906 .copied()
907 .unwrap_or(PROTOCOL_VERSION)
908 } else {
909 PROTOCOL_VERSION
911 }
912}
913
914#[derive(Debug, Clone, PartialEq, Eq)]
918pub enum VersionNegotiationResult {
919 Accepted(String),
921 CounterOffer {
924 requested: String,
926 offered: String,
928 },
929}
930
931impl VersionNegotiationResult {
932 #[must_use]
934 pub fn version(&self) -> &str {
935 match self {
936 Self::Accepted(v) => v,
937 Self::CounterOffer { offered, .. } => offered,
938 }
939 }
940
941 #[must_use]
943 pub const fn is_exact_match(&self) -> bool {
944 matches!(self, Self::Accepted(_))
945 }
946}
947
948#[must_use]
974pub fn negotiate_version_detailed(requested_version: &str) -> VersionNegotiationResult {
975 if is_version_supported(requested_version) {
976 VersionNegotiationResult::Accepted(requested_version.to_string())
977 } else {
978 VersionNegotiationResult::CounterOffer {
979 requested: requested_version.to_string(),
980 offered: PROTOCOL_VERSION.to_string(),
981 }
982 }
983}
984
985#[derive(Debug, Clone, Default, Serialize, Deserialize)]
987pub struct InitializedNotification {
988 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
990 pub meta: Option<Meta>,
991}
992
993#[derive(Debug, Clone, Default, Serialize, Deserialize)]
995pub struct PingRequest {
996 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
998 pub meta: Option<Meta>,
999}
1000
1001#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1003pub struct PingResult {
1004 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1006 pub meta: Option<Meta>,
1007}
1008
1009#[cfg(test)]
1010mod tests {
1011 #[test]
1012 fn elicitation_form_url_capability_semantics() {
1013 use super::ClientCapabilities;
1014 let form = ClientCapabilities::default().with_elicitation();
1016 assert!(form.has_elicitation());
1017 assert!(form.has_form_elicitation());
1018 assert!(!form.has_url_elicitation());
1019 assert_eq!(
1020 serde_json::to_value(&form.elicitation).unwrap(),
1021 serde_json::json!({}),
1022 "empty elicitation must still serialize as {{}} for compatibility"
1023 );
1024
1025 let url = ClientCapabilities::default().with_url_elicitation();
1027 assert!(url.has_url_elicitation());
1028 assert!(!url.has_form_elicitation());
1029
1030 let both = ClientCapabilities::default()
1032 .with_form_elicitation()
1033 .with_url_elicitation();
1034 assert!(both.has_form_elicitation());
1035 assert!(both.has_url_elicitation());
1036 }
1037
1038 use super::*;
1039
1040 #[test]
1041 fn test_server_capabilities_builder() -> Result<(), Box<dyn std::error::Error>> {
1042 let caps = ServerCapabilities::new()
1043 .with_tools()
1044 .with_resources_and_subscriptions()
1045 .with_prompts()
1046 .with_tasks();
1047
1048 assert!(caps.has_tools());
1049 assert!(caps.has_resources());
1050 assert!(caps.has_prompts());
1051 assert!(caps.has_tasks());
1052 assert!(
1053 caps.resources
1054 .ok_or("Expected resources")?
1055 .subscribe
1056 .ok_or("Expected subscribe")?
1057 );
1058 Ok(())
1059 }
1060
1061 #[test]
1062 fn test_client_capabilities_builder() -> Result<(), Box<dyn std::error::Error>> {
1063 let caps = ClientCapabilities::new()
1064 .with_roots_and_changes()
1065 .with_sampling()
1066 .with_elicitation();
1067
1068 assert!(caps.has_roots());
1069 assert!(caps.has_sampling());
1070 assert!(caps.has_elicitation());
1071 assert!(
1072 caps.roots
1073 .ok_or("Expected roots")?
1074 .list_changed
1075 .ok_or("Expected list_changed")?
1076 );
1077 Ok(())
1078 }
1079
1080 #[test]
1081 fn test_initialize_request() {
1082 let client = ClientInfo::new("test-client", "1.0.0");
1083 let caps = ClientCapabilities::new().with_sampling();
1084 let request = InitializeRequest::new(client, caps);
1085
1086 assert_eq!(request.protocol_version, PROTOCOL_VERSION);
1087 assert_eq!(request.client_info.name, "test-client");
1088 }
1089
1090 #[test]
1091 fn test_initialize_result() {
1092 let server = ServerInfo::new("test-server", "1.0.0");
1093 let caps = ServerCapabilities::new().with_tools();
1094 let result =
1095 InitializeResult::new(server, caps).instructions("Use this server to do things");
1096
1097 assert_eq!(result.protocol_version, PROTOCOL_VERSION);
1098 assert!(result.instructions.is_some());
1099 }
1100
1101 #[test]
1102 fn test_serialization() -> Result<(), Box<dyn std::error::Error>> {
1103 let caps = ServerCapabilities::new()
1104 .with_tools_and_changes()
1105 .with_resources();
1106
1107 let json = serde_json::to_string(&caps)?;
1108 assert!(json.contains("\"tools\""));
1109 assert!(json.contains("\"listChanged\":true"));
1110 Ok(())
1111 }
1112
1113 #[test]
1114 fn implementation_description_and_website_url_round_trip()
1115 -> Result<(), Box<dyn std::error::Error>> {
1116 let s = ServerInfo::new("s", "1.0")
1117 .description("does things")
1118 .website_url("https://example.com");
1119 let json = serde_json::to_value(&s)?;
1120 assert_eq!(json["description"], "does things");
1121 assert_eq!(json["websiteUrl"], "https://example.com");
1122
1123 let c: ClientInfo = serde_json::from_value(serde_json::json!({
1124 "name": "c", "version": "2.0",
1125 "description": "a client", "websiteUrl": "https://client.example"
1126 }))?;
1127 assert_eq!(c.description.as_deref(), Some("a client"));
1128 assert_eq!(c.website_url.as_deref(), Some("https://client.example"));
1129 Ok(())
1130 }
1131}