1use std::collections::BTreeMap;
2#[cfg(any(feature = "server", feature = "macros"))]
3use std::marker::PhantomData;
4
5#[cfg(any(feature = "server", feature = "macros"))]
6use pastey::paste;
7use serde::{Deserialize, Serialize};
8
9use super::JsonObject;
10pub type ExperimentalCapabilities = BTreeMap<String, JsonObject>;
11
12pub type ExtensionCapabilities = BTreeMap<String, JsonObject>;
33
34#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
35#[serde(rename_all = "camelCase")]
36#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
37#[non_exhaustive]
38pub struct PromptsCapability {
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub list_changed: Option<bool>,
41}
42
43#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
44#[serde(rename_all = "camelCase")]
45#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
46#[non_exhaustive]
47pub struct ResourcesCapability {
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub subscribe: Option<bool>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub list_changed: Option<bool>,
52}
53
54#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
55#[serde(rename_all = "camelCase")]
56#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
57#[non_exhaustive]
58pub struct ToolsCapability {
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub list_changed: Option<bool>,
61}
62
63#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
67#[serde(rename_all = "camelCase")]
68#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
69#[non_exhaustive]
70pub struct RootsCapabilities {
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub list_changed: Option<bool>,
73}
74
75#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
77#[serde(rename_all = "camelCase")]
78#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
79#[non_exhaustive]
80pub struct TasksCapability {
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub requests: Option<TaskRequestsCapability>,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub list: Option<JsonObject>,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub cancel: Option<JsonObject>,
87}
88
89#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
91#[serde(rename_all = "camelCase")]
92#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
93#[non_exhaustive]
94pub struct TaskRequestsCapability {
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub sampling: Option<SamplingTaskCapability>,
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub elicitation: Option<ElicitationTaskCapability>,
99 #[serde(skip_serializing_if = "Option::is_none")]
100 pub tools: Option<ToolsTaskCapability>,
101}
102
103#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
107#[serde(rename_all = "camelCase")]
108#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
109#[non_exhaustive]
110pub struct SamplingTaskCapability {
111 #[serde(skip_serializing_if = "Option::is_none")]
112 pub create_message: Option<JsonObject>,
113}
114
115#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
116#[serde(rename_all = "camelCase")]
117#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
118#[non_exhaustive]
119pub struct ElicitationTaskCapability {
120 #[serde(skip_serializing_if = "Option::is_none")]
121 pub create: Option<JsonObject>,
122}
123
124#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
125#[serde(rename_all = "camelCase")]
126#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
127#[non_exhaustive]
128pub struct ToolsTaskCapability {
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub call: Option<JsonObject>,
131}
132
133impl TasksCapability {
134 pub fn client_default() -> Self {
136 Self {
137 list: Some(JsonObject::new()),
138 cancel: Some(JsonObject::new()),
139 requests: Some(TaskRequestsCapability {
140 sampling: Some(SamplingTaskCapability {
141 create_message: Some(JsonObject::new()),
142 }),
143 elicitation: Some(ElicitationTaskCapability {
144 create: Some(JsonObject::new()),
145 }),
146 tools: None,
147 }),
148 }
149 }
150
151 pub fn server_default() -> Self {
153 Self {
154 list: Some(JsonObject::new()),
155 cancel: Some(JsonObject::new()),
156 requests: Some(TaskRequestsCapability {
157 sampling: None,
158 elicitation: None,
159 tools: Some(ToolsTaskCapability {
160 call: Some(JsonObject::new()),
161 }),
162 }),
163 }
164 }
165
166 pub fn supports_list(&self) -> bool {
167 self.list.is_some()
168 }
169
170 pub fn supports_cancel(&self) -> bool {
171 self.cancel.is_some()
172 }
173
174 pub fn supports_tools_call(&self) -> bool {
175 self.requests
176 .as_ref()
177 .and_then(|r| r.tools.as_ref())
178 .and_then(|t| t.call.as_ref())
179 .is_some()
180 }
181
182 pub fn supports_sampling_create_message(&self) -> bool {
183 self.requests
184 .as_ref()
185 .and_then(|r| r.sampling.as_ref())
186 .and_then(|s| s.create_message.as_ref())
187 .is_some()
188 }
189
190 pub fn supports_elicitation_create(&self) -> bool {
191 self.requests
192 .as_ref()
193 .and_then(|r| r.elicitation.as_ref())
194 .and_then(|e| e.create.as_ref())
195 .is_some()
196 }
197}
198
199#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
206#[serde(rename_all = "camelCase")]
207#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
208#[non_exhaustive]
209pub struct FormElicitationCapability {
210 #[serde(skip_serializing_if = "Option::is_none")]
214 pub schema_validation: Option<bool>,
215}
216
217impl FormElicitationCapability {
218 pub fn new() -> Self {
219 Self::default()
220 }
221
222 pub fn with_schema_validation(mut self, enabled: bool) -> Self {
223 self.schema_validation = Some(enabled);
224 self
225 }
226}
227
228#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
230#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
231#[non_exhaustive]
232pub struct UrlElicitationCapability {}
233
234impl UrlElicitationCapability {
235 pub fn new() -> Self {
236 Self::default()
237 }
238}
239
240#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
244#[serde(rename_all = "camelCase")]
245#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
246#[non_exhaustive]
247pub struct ElicitationCapability {
248 #[serde(skip_serializing_if = "Option::is_none")]
250 pub form: Option<FormElicitationCapability>,
251 #[serde(skip_serializing_if = "Option::is_none")]
253 pub url: Option<UrlElicitationCapability>,
254}
255
256impl ElicitationCapability {
257 pub fn new() -> Self {
258 Self::default()
259 }
260
261 pub fn with_form(mut self, form: FormElicitationCapability) -> Self {
262 self.form = Some(form);
263 self
264 }
265
266 pub fn with_url(mut self, url: UrlElicitationCapability) -> Self {
267 self.url = Some(url);
268 self
269 }
270}
271
272#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
278#[serde(rename_all = "camelCase")]
279#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
280#[non_exhaustive]
281pub struct SamplingCapability {
282 #[serde(skip_serializing_if = "Option::is_none")]
284 pub tools: Option<JsonObject>,
285 #[serde(skip_serializing_if = "Option::is_none")]
287 pub context: Option<JsonObject>,
288}
289
290#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
299#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
300#[non_exhaustive]
301pub struct ClientCapabilities {
302 #[serde(skip_serializing_if = "Option::is_none")]
303 pub experimental: Option<ExperimentalCapabilities>,
304 #[serde(skip_serializing_if = "Option::is_none")]
309 pub extensions: Option<ExtensionCapabilities>,
310 #[serde(skip_serializing_if = "Option::is_none")]
312 pub roots: Option<RootsCapabilities>,
313 #[serde(skip_serializing_if = "Option::is_none")]
315 pub sampling: Option<SamplingCapability>,
316 #[serde(skip_serializing_if = "Option::is_none")]
318 pub elicitation: Option<ElicitationCapability>,
319 #[serde(skip_serializing_if = "Option::is_none")]
320 pub tasks: Option<TasksCapability>,
321}
322
323#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
336#[serde(rename_all = "camelCase")]
337#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
338#[non_exhaustive]
339pub struct ServerCapabilities {
340 #[serde(skip_serializing_if = "Option::is_none")]
341 pub experimental: Option<ExperimentalCapabilities>,
342 #[serde(skip_serializing_if = "Option::is_none")]
347 pub extensions: Option<ExtensionCapabilities>,
348 #[serde(skip_serializing_if = "Option::is_none")]
350 pub logging: Option<JsonObject>,
351 #[serde(skip_serializing_if = "Option::is_none")]
352 pub completions: Option<JsonObject>,
353 #[serde(skip_serializing_if = "Option::is_none")]
354 pub prompts: Option<PromptsCapability>,
355 #[serde(skip_serializing_if = "Option::is_none")]
356 pub resources: Option<ResourcesCapability>,
357 #[serde(skip_serializing_if = "Option::is_none")]
358 pub tools: Option<ToolsCapability>,
359 #[serde(skip_serializing_if = "Option::is_none")]
360 pub tasks: Option<TasksCapability>,
361}
362
363#[cfg(any(feature = "server", feature = "macros"))]
364macro_rules! builder {
365 ($Target: ident {$($(#[$fa:meta])* $f: ident: $T: ty),* $(,)?}) => {
366 paste! {
367 #[derive(Default, Clone, Copy, Debug)]
368 #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
369 pub struct [<$Target BuilderState>]<
370 $(const [<$f:upper>]: bool = false,)*
371 >;
372 #[derive(Debug, Default)]
373 #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
374 pub struct [<$Target Builder>]<S = [<$Target BuilderState>]> {
375 $(pub $f: Option<$T>,)*
376 pub state: PhantomData<S>
377 }
378 impl $Target {
379 #[doc = "Create a new [`" $Target "`] builder."]
380 pub fn builder() -> [<$Target Builder>] {
381 <[<$Target Builder>]>::default()
382 }
383 }
384 impl<S> [<$Target Builder>]<S> {
385 pub fn build(self) -> $Target {
386 $Target {
387 $( $f: self.$f, )*
388 }
389 }
390 }
391 impl<S> From<[<$Target Builder>]<S>> for $Target {
392 fn from(builder: [<$Target Builder>]<S>) -> Self {
393 builder.build()
394 }
395 }
396 }
397 builder!($Target @toggle $($(#[$fa])* $f: $T,)*);
398
399 };
400 ($Target: ident @toggle $(#[$fa0:meta])* $f0: ident: $T0: ty, $($(#[$fa:meta])* $f: ident: $T: ty,)*) => {
401 builder!($Target @toggle [][$(#[$fa0])* $f0: $T0][$($(#[$fa])* $f: $T,)*]);
402 };
403 ($Target: ident @toggle [$($ff: ident: $Tf: ty,)*][$(#[$fna:meta])* $fn: ident: $TN: ty][$(#[$fn1a:meta])* $fn_1: ident: $Tn_1: ty, $($(#[$fta:meta])* $ft: ident: $Tt: ty,)*]) => {
404 builder!($Target @impl_toggle [$($ff: $Tf,)*][$(#[$fna])* $fn: $TN][$fn_1: $Tn_1, $($ft:$Tt,)*]);
405 builder!($Target @toggle [$($ff: $Tf,)* $fn: $TN,][$(#[$fn1a])* $fn_1: $Tn_1][$($(#[$fta])* $ft: $Tt,)*]);
406 };
407 ($Target: ident @toggle [$($ff: ident: $Tf: ty,)*][$(#[$fna:meta])* $fn: ident: $TN: ty][]) => {
408 builder!($Target @impl_toggle [$($ff: $Tf,)*][$(#[$fna])* $fn: $TN][]);
409 };
410 ($Target: ident @impl_toggle [$($ff: ident: $Tf: ty,)*][$(#[$fna:meta])* $fn: ident: $TN: ty][$($ft: ident: $Tt: ty,)*]) => {
411 paste! {
412 impl<
413 $(const [<$ff:upper>]: bool,)*
414 $(const [<$ft:upper>]: bool,)*
415 > [<$Target Builder>]<[<$Target BuilderState>]<
416 $([<$ff:upper>],)*
417 false,
418 $([<$ft:upper>],)*
419 >> {
420 $(#[$fna])*
421 pub fn [<enable_ $fn>](self) -> [<$Target Builder>]<[<$Target BuilderState>]<
422 $([<$ff:upper>],)*
423 true,
424 $([<$ft:upper>],)*
425 >> {
426 [<$Target Builder>] {
427 $( $ff: self.$ff, )*
428 $fn: Some($TN::default()),
429 $( $ft: self.$ft, )*
430 state: PhantomData
431 }
432 }
433 $(#[$fna])*
434 pub fn [<enable_ $fn _with>](self, $fn: $TN) -> [<$Target Builder>]<[<$Target BuilderState>]<
435 $([<$ff:upper>],)*
436 true,
437 $([<$ft:upper>],)*
438 >> {
439 [<$Target Builder>] {
440 $( $ff: self.$ff, )*
441 $fn: Some($fn),
442 $( $ft: self.$ft, )*
443 state: PhantomData
444 }
445 }
446 }
447 }
470 }
471}
472
473#[cfg(any(feature = "server", feature = "macros"))]
474builder! {
475 ServerCapabilities {
476 experimental: ExperimentalCapabilities,
477 extensions: ExtensionCapabilities,
478 #[deprecated(
479 since = "1.8.0",
480 note = "Logging is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577"
481 )]
482 logging: JsonObject,
483 completions: JsonObject,
484 prompts: PromptsCapability,
485 resources: ResourcesCapability,
486 tools: ToolsCapability,
487 tasks: TasksCapability
488 }
489}
490
491#[cfg(any(feature = "server", feature = "macros"))]
492impl<
493 const E: bool,
494 const EXT: bool,
495 const L: bool,
496 const C: bool,
497 const P: bool,
498 const R: bool,
499 const TASKS: bool,
500> ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, EXT, L, C, P, R, true, TASKS>>
501{
502 pub fn enable_tool_list_changed(mut self) -> Self {
503 if let Some(c) = self.tools.as_mut() {
504 c.list_changed = Some(true);
505 }
506 self
507 }
508}
509
510#[cfg(any(feature = "server", feature = "macros"))]
511impl<
512 const E: bool,
513 const EXT: bool,
514 const L: bool,
515 const C: bool,
516 const R: bool,
517 const T: bool,
518 const TASKS: bool,
519> ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, EXT, L, C, true, R, T, TASKS>>
520{
521 pub fn enable_prompts_list_changed(mut self) -> Self {
522 if let Some(c) = self.prompts.as_mut() {
523 c.list_changed = Some(true);
524 }
525 self
526 }
527}
528
529#[cfg(any(feature = "server", feature = "macros"))]
530impl<
531 const E: bool,
532 const EXT: bool,
533 const L: bool,
534 const C: bool,
535 const P: bool,
536 const T: bool,
537 const TASKS: bool,
538> ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, EXT, L, C, P, true, T, TASKS>>
539{
540 pub fn enable_resources_list_changed(mut self) -> Self {
541 if let Some(c) = self.resources.as_mut() {
542 c.list_changed = Some(true);
543 }
544 self
545 }
546
547 pub fn enable_resources_subscribe(mut self) -> Self {
548 if let Some(c) = self.resources.as_mut() {
549 c.subscribe = Some(true);
550 }
551 self
552 }
553}
554
555#[cfg(any(feature = "server", feature = "macros"))]
556builder! {
557 ClientCapabilities{
558 experimental: ExperimentalCapabilities,
559 extensions: ExtensionCapabilities,
560 #[deprecated(
561 since = "1.8.0",
562 note = "Roots is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577"
563 )]
564 roots: RootsCapabilities,
565 #[deprecated(
566 since = "1.8.0",
567 note = "Sampling is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577"
568 )]
569 sampling: SamplingCapability,
570 elicitation: ElicitationCapability,
571 tasks: TasksCapability,
572 }
573}
574
575#[cfg(any(feature = "server", feature = "macros"))]
576impl<const E: bool, const EXT: bool, const S: bool, const EL: bool, const TASKS: bool>
577 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<E, EXT, true, S, EL, TASKS>>
578{
579 #[deprecated(
580 since = "1.8.0",
581 note = "Roots is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577"
582 )]
583 pub fn enable_roots_list_changed(mut self) -> Self {
584 if let Some(c) = self.roots.as_mut() {
585 c.list_changed = Some(true);
586 }
587 self
588 }
589}
590
591#[cfg(any(feature = "server", feature = "macros"))]
592impl<const E: bool, const EXT: bool, const R: bool, const EL: bool, const TASKS: bool>
593 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<E, EXT, R, true, EL, TASKS>>
594{
595 #[deprecated(
597 since = "1.8.0",
598 note = "Sampling is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577"
599 )]
600 pub fn enable_sampling_tools(mut self) -> Self {
601 if let Some(c) = self.sampling.as_mut() {
602 c.tools = Some(JsonObject::default());
603 }
604 self
605 }
606
607 #[deprecated(
609 since = "1.8.0",
610 note = "Sampling is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577"
611 )]
612 pub fn enable_sampling_context(mut self) -> Self {
613 if let Some(c) = self.sampling.as_mut() {
614 c.context = Some(JsonObject::default());
615 }
616 self
617 }
618}
619
620#[cfg(all(feature = "elicitation", any(feature = "server", feature = "macros")))]
621impl<const E: bool, const EXT: bool, const R: bool, const S: bool, const TASKS: bool>
622 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<E, EXT, R, S, true, TASKS>>
623{
624 pub fn enable_elicitation_schema_validation(mut self) -> Self {
628 if let Some(c) = self.elicitation.as_mut() {
629 c.form = Some(FormElicitationCapability {
630 schema_validation: Some(true),
631 });
632 }
633 self
634 }
635}
636
637#[cfg(test)]
638#[cfg(any(feature = "server", feature = "macros"))]
639mod test {
640 use super::*;
641 #[test]
642 #[allow(deprecated)]
643 fn test_builder() {
644 let builder = <ServerCapabilitiesBuilder>::default()
645 .enable_logging()
646 .enable_experimental()
647 .enable_prompts()
648 .enable_resources()
649 .enable_tools()
650 .enable_tool_list_changed();
651 assert_eq!(builder.logging, Some(JsonObject::default()));
652 assert_eq!(builder.prompts, Some(PromptsCapability::default()));
653 assert_eq!(builder.resources, Some(ResourcesCapability::default()));
654 assert_eq!(
655 builder.tools,
656 Some(ToolsCapability {
657 list_changed: Some(true),
658 })
659 );
660 assert_eq!(
661 builder.experimental,
662 Some(ExperimentalCapabilities::default())
663 );
664 let client_builder = <ClientCapabilitiesBuilder>::default()
665 .enable_experimental()
666 .enable_roots()
667 .enable_roots_list_changed()
668 .enable_sampling();
669 assert_eq!(
670 client_builder.experimental,
671 Some(ExperimentalCapabilities::default())
672 );
673 assert_eq!(
674 client_builder.roots,
675 Some(RootsCapabilities {
676 list_changed: Some(true),
677 })
678 );
679 }
680
681 #[test]
682 fn test_task_capabilities_deserialization() {
683 let json = serde_json::json!({
685 "list": {},
686 "cancel": {},
687 "requests": {
688 "tools": { "call": {} }
689 }
690 });
691
692 let tasks: TasksCapability = serde_json::from_value(json).unwrap();
693 assert!(tasks.list.is_some());
694 assert!(tasks.cancel.is_some());
695 assert!(tasks.requests.is_some());
696 let requests = tasks.requests.unwrap();
697 assert!(requests.tools.is_some());
698 assert!(requests.tools.unwrap().call.is_some());
699 }
700
701 #[test]
702 fn test_tasks_capability_client_default() {
703 let tasks = TasksCapability::client_default();
704
705 assert!(tasks.supports_list());
707 assert!(tasks.supports_cancel());
708 assert!(tasks.supports_sampling_create_message());
709 assert!(tasks.supports_elicitation_create());
710 assert!(!tasks.supports_tools_call());
711
712 let json = serde_json::to_value(&tasks).unwrap();
714 assert_eq!(json["list"], serde_json::json!({}));
715 assert_eq!(json["cancel"], serde_json::json!({}));
716 assert_eq!(
717 json["requests"]["sampling"]["createMessage"],
718 serde_json::json!({})
719 );
720 assert_eq!(
721 json["requests"]["elicitation"]["create"],
722 serde_json::json!({})
723 );
724 }
725
726 #[test]
727 fn test_tasks_capability_server_default() {
728 let tasks = TasksCapability::server_default();
729
730 assert!(tasks.supports_list());
732 assert!(tasks.supports_cancel());
733 assert!(tasks.supports_tools_call());
734 assert!(!tasks.supports_sampling_create_message());
735 assert!(!tasks.supports_elicitation_create());
736
737 let json = serde_json::to_value(&tasks).unwrap();
739 assert_eq!(json["list"], serde_json::json!({}));
740 assert_eq!(json["cancel"], serde_json::json!({}));
741 assert_eq!(json["requests"]["tools"]["call"], serde_json::json!({}));
742 }
743
744 #[test]
745 #[allow(deprecated)]
746 fn test_client_extensions_capability() {
747 let mut extensions = ExtensionCapabilities::new();
749 extensions.insert(
750 "io.modelcontextprotocol/ui".to_string(),
751 serde_json::from_value(serde_json::json!({
752 "mimeTypes": ["text/html;profile=mcp-app"]
753 }))
754 .unwrap(),
755 );
756
757 let capabilities = ClientCapabilities::builder()
758 .enable_extensions_with(extensions)
759 .enable_sampling()
760 .build();
761
762 let json = serde_json::to_value(&capabilities).unwrap();
764 assert_eq!(
765 json["extensions"]["io.modelcontextprotocol/ui"]["mimeTypes"],
766 serde_json::json!(["text/html;profile=mcp-app"])
767 );
768 assert!(json["sampling"].is_object());
769 }
770
771 #[test]
772 fn test_server_extensions_capability() {
773 let mut extensions = ExtensionCapabilities::new();
775 extensions.insert(
776 "io.modelcontextprotocol/apps".to_string(),
777 serde_json::from_value(serde_json::json!({})).unwrap(),
778 );
779
780 let capabilities = ServerCapabilities::builder()
781 .enable_extensions_with(extensions)
782 .enable_tools()
783 .build();
784
785 let json = serde_json::to_value(&capabilities).unwrap();
787 assert!(json["extensions"]["io.modelcontextprotocol/apps"].is_object());
788 assert!(json["tools"].is_object());
789 }
790
791 #[test]
792 fn test_extensions_deserialization() {
793 let json = serde_json::json!({
795 "extensions": {
796 "io.modelcontextprotocol/ui": {
797 "mimeTypes": ["text/html;profile=mcp-app"]
798 }
799 },
800 "sampling": {}
801 });
802
803 let capabilities: ClientCapabilities = serde_json::from_value(json).unwrap();
804 assert!(capabilities.extensions.is_some());
805 let extensions = capabilities.extensions.unwrap();
806 assert!(extensions.contains_key("io.modelcontextprotocol/ui"));
807 let ui_ext = extensions.get("io.modelcontextprotocol/ui").unwrap();
808 assert!(ui_ext.contains_key("mimeTypes"));
809 }
810
811 #[test]
812 fn test_extensions_empty_settings() {
813 let mut extensions = ExtensionCapabilities::new();
815 extensions.insert(
816 "io.modelcontextprotocol/oauth-client-credentials".to_string(),
817 JsonObject::new(),
818 );
819
820 let capabilities = ClientCapabilities::builder()
821 .enable_extensions_with(extensions)
822 .build();
823
824 let json = serde_json::to_value(&capabilities).unwrap();
825 assert_eq!(
826 json["extensions"]["io.modelcontextprotocol/oauth-client-credentials"],
827 serde_json::json!({})
828 );
829 }
830}