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))]
37pub struct PromptsCapability {
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub list_changed: Option<bool>,
40}
41
42#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
43#[serde(rename_all = "camelCase")]
44#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
45pub struct ResourcesCapability {
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub subscribe: Option<bool>,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub list_changed: Option<bool>,
50}
51
52#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
53#[serde(rename_all = "camelCase")]
54#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
55pub struct ToolsCapability {
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub list_changed: Option<bool>,
58}
59
60#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
61#[serde(rename_all = "camelCase")]
62#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
63pub struct RootsCapabilities {
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub list_changed: Option<bool>,
66}
67
68#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
70#[serde(rename_all = "camelCase")]
71#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
72pub struct TasksCapability {
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub requests: Option<TaskRequestsCapability>,
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub list: Option<JsonObject>,
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub cancel: Option<JsonObject>,
79}
80
81#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
83#[serde(rename_all = "camelCase")]
84#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
85pub struct TaskRequestsCapability {
86 #[serde(skip_serializing_if = "Option::is_none")]
87 pub sampling: Option<SamplingTaskCapability>,
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub elicitation: Option<ElicitationTaskCapability>,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub tools: Option<ToolsTaskCapability>,
92}
93
94#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
95#[serde(rename_all = "camelCase")]
96#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
97pub struct SamplingTaskCapability {
98 #[serde(skip_serializing_if = "Option::is_none")]
99 pub create_message: Option<JsonObject>,
100}
101
102#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
103#[serde(rename_all = "camelCase")]
104#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
105pub struct ElicitationTaskCapability {
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub create: Option<JsonObject>,
108}
109
110#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
111#[serde(rename_all = "camelCase")]
112#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
113pub struct ToolsTaskCapability {
114 #[serde(skip_serializing_if = "Option::is_none")]
115 pub call: Option<JsonObject>,
116}
117
118impl TasksCapability {
119 pub fn client_default() -> Self {
121 Self {
122 list: Some(JsonObject::new()),
123 cancel: Some(JsonObject::new()),
124 requests: Some(TaskRequestsCapability {
125 sampling: Some(SamplingTaskCapability {
126 create_message: Some(JsonObject::new()),
127 }),
128 elicitation: Some(ElicitationTaskCapability {
129 create: Some(JsonObject::new()),
130 }),
131 tools: None,
132 }),
133 }
134 }
135
136 pub fn server_default() -> Self {
138 Self {
139 list: Some(JsonObject::new()),
140 cancel: Some(JsonObject::new()),
141 requests: Some(TaskRequestsCapability {
142 sampling: None,
143 elicitation: None,
144 tools: Some(ToolsTaskCapability {
145 call: Some(JsonObject::new()),
146 }),
147 }),
148 }
149 }
150
151 pub fn supports_list(&self) -> bool {
152 self.list.is_some()
153 }
154
155 pub fn supports_cancel(&self) -> bool {
156 self.cancel.is_some()
157 }
158
159 pub fn supports_tools_call(&self) -> bool {
160 self.requests
161 .as_ref()
162 .and_then(|r| r.tools.as_ref())
163 .and_then(|t| t.call.as_ref())
164 .is_some()
165 }
166
167 pub fn supports_sampling_create_message(&self) -> bool {
168 self.requests
169 .as_ref()
170 .and_then(|r| r.sampling.as_ref())
171 .and_then(|s| s.create_message.as_ref())
172 .is_some()
173 }
174
175 pub fn supports_elicitation_create(&self) -> bool {
176 self.requests
177 .as_ref()
178 .and_then(|r| r.elicitation.as_ref())
179 .and_then(|e| e.create.as_ref())
180 .is_some()
181 }
182}
183
184#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
191#[serde(rename_all = "camelCase")]
192#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
193pub struct FormElicitationCapability {
194 #[serde(skip_serializing_if = "Option::is_none")]
198 pub schema_validation: Option<bool>,
199}
200
201#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
203#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
204pub struct UrlElicitationCapability {}
205
206#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
210#[serde(rename_all = "camelCase")]
211#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
212pub struct ElicitationCapability {
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub form: Option<FormElicitationCapability>,
216 #[serde(skip_serializing_if = "Option::is_none")]
218 pub url: Option<UrlElicitationCapability>,
219}
220
221#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
223#[serde(rename_all = "camelCase")]
224#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
225pub struct SamplingCapability {
226 #[serde(skip_serializing_if = "Option::is_none")]
228 pub tools: Option<JsonObject>,
229 #[serde(skip_serializing_if = "Option::is_none")]
231 pub context: Option<JsonObject>,
232}
233
234#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
245#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
246#[non_exhaustive]
247pub struct ClientCapabilities {
248 #[serde(skip_serializing_if = "Option::is_none")]
249 pub experimental: Option<ExperimentalCapabilities>,
250 #[serde(skip_serializing_if = "Option::is_none")]
255 pub extensions: Option<ExtensionCapabilities>,
256 #[serde(skip_serializing_if = "Option::is_none")]
257 pub roots: Option<RootsCapabilities>,
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub sampling: Option<SamplingCapability>,
261 #[serde(skip_serializing_if = "Option::is_none")]
263 pub elicitation: Option<ElicitationCapability>,
264 #[serde(skip_serializing_if = "Option::is_none")]
265 pub tasks: Option<TasksCapability>,
266}
267
268#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
282#[serde(rename_all = "camelCase")]
283#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
284#[non_exhaustive]
285pub struct ServerCapabilities {
286 #[serde(skip_serializing_if = "Option::is_none")]
287 pub experimental: Option<ExperimentalCapabilities>,
288 #[serde(skip_serializing_if = "Option::is_none")]
293 pub extensions: Option<ExtensionCapabilities>,
294 #[serde(skip_serializing_if = "Option::is_none")]
295 pub logging: Option<JsonObject>,
296 #[serde(skip_serializing_if = "Option::is_none")]
297 pub completions: Option<JsonObject>,
298 #[serde(skip_serializing_if = "Option::is_none")]
299 pub prompts: Option<PromptsCapability>,
300 #[serde(skip_serializing_if = "Option::is_none")]
301 pub resources: Option<ResourcesCapability>,
302 #[serde(skip_serializing_if = "Option::is_none")]
303 pub tools: Option<ToolsCapability>,
304 #[serde(skip_serializing_if = "Option::is_none")]
305 pub tasks: Option<TasksCapability>,
306}
307
308#[cfg(any(feature = "server", feature = "macros"))]
309macro_rules! builder {
310 ($Target: ident {$($f: ident: $T: ty),* $(,)?}) => {
311 paste! {
312 #[derive(Default, Clone, Copy, Debug)]
313 pub struct [<$Target BuilderState>]<
314 $(const [<$f:upper>]: bool = false,)*
315 >;
316 #[derive(Debug, Default)]
317 pub struct [<$Target Builder>]<S = [<$Target BuilderState>]> {
318 $(pub $f: Option<$T>,)*
319 pub state: PhantomData<S>
320 }
321 impl $Target {
322 #[doc = "Create a new [`" $Target "`] builder."]
323 pub fn builder() -> [<$Target Builder>] {
324 <[<$Target Builder>]>::default()
325 }
326 }
327 impl<S> [<$Target Builder>]<S> {
328 pub fn build(self) -> $Target {
329 $Target {
330 $( $f: self.$f, )*
331 }
332 }
333 }
334 impl<S> From<[<$Target Builder>]<S>> for $Target {
335 fn from(builder: [<$Target Builder>]<S>) -> Self {
336 builder.build()
337 }
338 }
339 }
340 builder!($Target @toggle $($f: $T,) *);
341
342 };
343 ($Target: ident @toggle $f0: ident: $T0: ty, $($f: ident: $T: ty,)*) => {
344 builder!($Target @toggle [][$f0: $T0][$($f: $T,)*]);
345 };
346 ($Target: ident @toggle [$($ff: ident: $Tf: ty,)*][$fn: ident: $TN: ty][$fn_1: ident: $Tn_1: ty, $($ft: ident: $Tt: ty,)*]) => {
347 builder!($Target @impl_toggle [$($ff: $Tf,)*][$fn: $TN][$fn_1: $Tn_1, $($ft:$Tt,)*]);
348 builder!($Target @toggle [$($ff: $Tf,)* $fn: $TN,][$fn_1: $Tn_1][$($ft:$Tt,)*]);
349 };
350 ($Target: ident @toggle [$($ff: ident: $Tf: ty,)*][$fn: ident: $TN: ty][]) => {
351 builder!($Target @impl_toggle [$($ff: $Tf,)*][$fn: $TN][]);
352 };
353 ($Target: ident @impl_toggle [$($ff: ident: $Tf: ty,)*][$fn: ident: $TN: ty][$($ft: ident: $Tt: ty,)*]) => {
354 paste! {
355 impl<
356 $(const [<$ff:upper>]: bool,)*
357 $(const [<$ft:upper>]: bool,)*
358 > [<$Target Builder>]<[<$Target BuilderState>]<
359 $([<$ff:upper>],)*
360 false,
361 $([<$ft:upper>],)*
362 >> {
363 pub fn [<enable_ $fn>](self) -> [<$Target Builder>]<[<$Target BuilderState>]<
364 $([<$ff:upper>],)*
365 true,
366 $([<$ft:upper>],)*
367 >> {
368 [<$Target Builder>] {
369 $( $ff: self.$ff, )*
370 $fn: Some($TN::default()),
371 $( $ft: self.$ft, )*
372 state: PhantomData
373 }
374 }
375 pub fn [<enable_ $fn _with>](self, $fn: $TN) -> [<$Target Builder>]<[<$Target BuilderState>]<
376 $([<$ff:upper>],)*
377 true,
378 $([<$ft:upper>],)*
379 >> {
380 [<$Target Builder>] {
381 $( $ff: self.$ff, )*
382 $fn: Some($fn),
383 $( $ft: self.$ft, )*
384 state: PhantomData
385 }
386 }
387 }
388 }
411 }
412}
413
414#[cfg(any(feature = "server", feature = "macros"))]
415builder! {
416 ServerCapabilities {
417 experimental: ExperimentalCapabilities,
418 extensions: ExtensionCapabilities,
419 logging: JsonObject,
420 completions: JsonObject,
421 prompts: PromptsCapability,
422 resources: ResourcesCapability,
423 tools: ToolsCapability,
424 tasks: TasksCapability
425 }
426}
427
428#[cfg(any(feature = "server", feature = "macros"))]
429impl<
430 const E: bool,
431 const EXT: bool,
432 const L: bool,
433 const C: bool,
434 const P: bool,
435 const R: bool,
436 const TASKS: bool,
437> ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, EXT, L, C, P, R, true, TASKS>>
438{
439 pub fn enable_tool_list_changed(mut self) -> Self {
440 if let Some(c) = self.tools.as_mut() {
441 c.list_changed = Some(true);
442 }
443 self
444 }
445}
446
447#[cfg(any(feature = "server", feature = "macros"))]
448impl<
449 const E: bool,
450 const EXT: bool,
451 const L: bool,
452 const C: bool,
453 const R: bool,
454 const T: bool,
455 const TASKS: bool,
456> ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, EXT, L, C, true, R, T, TASKS>>
457{
458 pub fn enable_prompts_list_changed(mut self) -> Self {
459 if let Some(c) = self.prompts.as_mut() {
460 c.list_changed = Some(true);
461 }
462 self
463 }
464}
465
466#[cfg(any(feature = "server", feature = "macros"))]
467impl<
468 const E: bool,
469 const EXT: bool,
470 const L: bool,
471 const C: bool,
472 const P: bool,
473 const T: bool,
474 const TASKS: bool,
475> ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, EXT, L, C, P, true, T, TASKS>>
476{
477 pub fn enable_resources_list_changed(mut self) -> Self {
478 if let Some(c) = self.resources.as_mut() {
479 c.list_changed = Some(true);
480 }
481 self
482 }
483
484 pub fn enable_resources_subscribe(mut self) -> Self {
485 if let Some(c) = self.resources.as_mut() {
486 c.subscribe = Some(true);
487 }
488 self
489 }
490}
491
492#[cfg(any(feature = "server", feature = "macros"))]
493builder! {
494 ClientCapabilities{
495 experimental: ExperimentalCapabilities,
496 extensions: ExtensionCapabilities,
497 roots: RootsCapabilities,
498 sampling: SamplingCapability,
499 elicitation: ElicitationCapability,
500 tasks: TasksCapability,
501 }
502}
503
504#[cfg(any(feature = "server", feature = "macros"))]
505impl<const E: bool, const EXT: bool, const S: bool, const EL: bool, const TASKS: bool>
506 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<E, EXT, true, S, EL, TASKS>>
507{
508 pub fn enable_roots_list_changed(mut self) -> Self {
509 if let Some(c) = self.roots.as_mut() {
510 c.list_changed = Some(true);
511 }
512 self
513 }
514}
515
516#[cfg(any(feature = "server", feature = "macros"))]
517impl<const E: bool, const EXT: bool, const R: bool, const EL: bool, const TASKS: bool>
518 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<E, EXT, R, true, EL, TASKS>>
519{
520 pub fn enable_sampling_tools(mut self) -> Self {
522 if let Some(c) = self.sampling.as_mut() {
523 c.tools = Some(JsonObject::default());
524 }
525 self
526 }
527
528 pub fn enable_sampling_context(mut self) -> Self {
530 if let Some(c) = self.sampling.as_mut() {
531 c.context = Some(JsonObject::default());
532 }
533 self
534 }
535}
536
537#[cfg(all(feature = "elicitation", any(feature = "server", feature = "macros")))]
538impl<const E: bool, const EXT: bool, const R: bool, const S: bool, const TASKS: bool>
539 ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<E, EXT, R, S, true, TASKS>>
540{
541 pub fn enable_elicitation_schema_validation(mut self) -> Self {
545 if let Some(c) = self.elicitation.as_mut() {
546 c.form = Some(FormElicitationCapability {
547 schema_validation: Some(true),
548 });
549 }
550 self
551 }
552}
553
554#[cfg(test)]
555#[cfg(any(feature = "server", feature = "macros"))]
556mod test {
557 use super::*;
558 #[test]
559 fn test_builder() {
560 let builder = <ServerCapabilitiesBuilder>::default()
561 .enable_logging()
562 .enable_experimental()
563 .enable_prompts()
564 .enable_resources()
565 .enable_tools()
566 .enable_tool_list_changed();
567 assert_eq!(builder.logging, Some(JsonObject::default()));
568 assert_eq!(builder.prompts, Some(PromptsCapability::default()));
569 assert_eq!(builder.resources, Some(ResourcesCapability::default()));
570 assert_eq!(
571 builder.tools,
572 Some(ToolsCapability {
573 list_changed: Some(true),
574 })
575 );
576 assert_eq!(
577 builder.experimental,
578 Some(ExperimentalCapabilities::default())
579 );
580 let client_builder = <ClientCapabilitiesBuilder>::default()
581 .enable_experimental()
582 .enable_roots()
583 .enable_roots_list_changed()
584 .enable_sampling();
585 assert_eq!(
586 client_builder.experimental,
587 Some(ExperimentalCapabilities::default())
588 );
589 assert_eq!(
590 client_builder.roots,
591 Some(RootsCapabilities {
592 list_changed: Some(true),
593 })
594 );
595 }
596
597 #[test]
598 fn test_task_capabilities_deserialization() {
599 let json = serde_json::json!({
601 "list": {},
602 "cancel": {},
603 "requests": {
604 "tools": { "call": {} }
605 }
606 });
607
608 let tasks: TasksCapability = serde_json::from_value(json).unwrap();
609 assert!(tasks.list.is_some());
610 assert!(tasks.cancel.is_some());
611 assert!(tasks.requests.is_some());
612 let requests = tasks.requests.unwrap();
613 assert!(requests.tools.is_some());
614 assert!(requests.tools.unwrap().call.is_some());
615 }
616
617 #[test]
618 fn test_tasks_capability_client_default() {
619 let tasks = TasksCapability::client_default();
620
621 assert!(tasks.supports_list());
623 assert!(tasks.supports_cancel());
624 assert!(tasks.supports_sampling_create_message());
625 assert!(tasks.supports_elicitation_create());
626 assert!(!tasks.supports_tools_call());
627
628 let json = serde_json::to_value(&tasks).unwrap();
630 assert_eq!(json["list"], serde_json::json!({}));
631 assert_eq!(json["cancel"], serde_json::json!({}));
632 assert_eq!(
633 json["requests"]["sampling"]["createMessage"],
634 serde_json::json!({})
635 );
636 assert_eq!(
637 json["requests"]["elicitation"]["create"],
638 serde_json::json!({})
639 );
640 }
641
642 #[test]
643 fn test_tasks_capability_server_default() {
644 let tasks = TasksCapability::server_default();
645
646 assert!(tasks.supports_list());
648 assert!(tasks.supports_cancel());
649 assert!(tasks.supports_tools_call());
650 assert!(!tasks.supports_sampling_create_message());
651 assert!(!tasks.supports_elicitation_create());
652
653 let json = serde_json::to_value(&tasks).unwrap();
655 assert_eq!(json["list"], serde_json::json!({}));
656 assert_eq!(json["cancel"], serde_json::json!({}));
657 assert_eq!(json["requests"]["tools"]["call"], serde_json::json!({}));
658 }
659
660 #[test]
661 fn test_client_extensions_capability() {
662 let mut extensions = ExtensionCapabilities::new();
664 extensions.insert(
665 "io.modelcontextprotocol/ui".to_string(),
666 serde_json::from_value(serde_json::json!({
667 "mimeTypes": ["text/html;profile=mcp-app"]
668 }))
669 .unwrap(),
670 );
671
672 let capabilities = ClientCapabilities::builder()
673 .enable_extensions_with(extensions)
674 .enable_sampling()
675 .build();
676
677 let json = serde_json::to_value(&capabilities).unwrap();
679 assert_eq!(
680 json["extensions"]["io.modelcontextprotocol/ui"]["mimeTypes"],
681 serde_json::json!(["text/html;profile=mcp-app"])
682 );
683 assert!(json["sampling"].is_object());
684 }
685
686 #[test]
687 fn test_server_extensions_capability() {
688 let mut extensions = ExtensionCapabilities::new();
690 extensions.insert(
691 "io.modelcontextprotocol/apps".to_string(),
692 serde_json::from_value(serde_json::json!({})).unwrap(),
693 );
694
695 let capabilities = ServerCapabilities::builder()
696 .enable_extensions_with(extensions)
697 .enable_tools()
698 .build();
699
700 let json = serde_json::to_value(&capabilities).unwrap();
702 assert!(json["extensions"]["io.modelcontextprotocol/apps"].is_object());
703 assert!(json["tools"].is_object());
704 }
705
706 #[test]
707 fn test_extensions_deserialization() {
708 let json = serde_json::json!({
710 "extensions": {
711 "io.modelcontextprotocol/ui": {
712 "mimeTypes": ["text/html;profile=mcp-app"]
713 }
714 },
715 "sampling": {}
716 });
717
718 let capabilities: ClientCapabilities = serde_json::from_value(json).unwrap();
719 assert!(capabilities.extensions.is_some());
720 let extensions = capabilities.extensions.unwrap();
721 assert!(extensions.contains_key("io.modelcontextprotocol/ui"));
722 let ui_ext = extensions.get("io.modelcontextprotocol/ui").unwrap();
723 assert!(ui_ext.contains_key("mimeTypes"));
724 }
725
726 #[test]
727 fn test_extensions_empty_settings() {
728 let mut extensions = ExtensionCapabilities::new();
730 extensions.insert(
731 "io.modelcontextprotocol/oauth-client-credentials".to_string(),
732 JsonObject::new(),
733 );
734
735 let capabilities = ClientCapabilities::builder()
736 .enable_extensions_with(extensions)
737 .build();
738
739 let json = serde_json::to_value(&capabilities).unwrap();
740 assert_eq!(
741 json["extensions"]["io.modelcontextprotocol/oauth-client-credentials"],
742 serde_json::json!({})
743 );
744 }
745}