Skip to main content

rmcp/model/
capabilities.rs

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
12/// MCP extension capabilities map.
13///
14/// Keys are extension identifiers in the format `{vendor-prefix}/{extension-name}`
15/// (e.g., `io.modelcontextprotocol/ui`, `io.modelcontextprotocol/oauth-client-credentials`).
16/// Values are per-extension settings objects. An empty object indicates support with no settings.
17///
18/// # Example
19///
20/// ```rust
21/// use rmcp::model::ExtensionCapabilities;
22/// use serde_json::json;
23///
24/// let mut extensions = ExtensionCapabilities::new();
25/// extensions.insert(
26///     "io.modelcontextprotocol/ui".to_string(),
27///     serde_json::from_value(json!({
28///         "mimeTypes": ["text/html;profile=mcp-app"]
29///     })).unwrap()
30/// );
31/// ```
32pub 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/// Roots capability. Deprecated by SEP-2577; remains functional and will be
64/// removed in a future release.
65/// See <https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577>.
66#[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/// Task capabilities shared by client and server.
76#[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/// Request types that support task-augmented execution.
90#[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/// Sampling task capability. Deprecated by SEP-2577; remains functional and
104/// will be removed in a future release.
105/// See <https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577>.
106#[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    /// Default client tasks capability with sampling and elicitation support.
135    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    /// Default server tasks capability with tools/call support.
152    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/// Capability for handling elicitation requests from servers.
200/// Elicitation allows servers to request interactive input from users during tool execution.
201/// This capability indicates that a client can handle elicitation requests and present
202/// appropriate UI to users for collecting the requested information.
203///
204/// Capability for form mode elicitation.
205#[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    /// Whether the client supports JSON Schema validation for elicitation responses.
211    /// When true, the client will validate user input against the requested_schema
212    /// before sending the response back to the server.
213    #[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/// Capability for URL mode elicitation.
229#[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/// Elicitation allows servers to request interactive input from users during tool execution.
241/// This capability indicates that a client can handle elicitation requests and present
242/// appropriate UI to users for collecting the requested information.
243#[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    /// Whether client supports form-based elicitation.
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub form: Option<FormElicitationCapability>,
251    /// Whether client supports URL-based elicitation.
252    #[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/// Sampling capability with optional sub-capabilities (SEP-1577).
273///
274/// Deprecated by SEP-2577; remains functional and will be removed in a future
275/// release.
276/// See <https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577>.
277#[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    /// Support for `tools` and `toolChoice` parameters
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub tools: Option<JsonObject>,
285    /// Support for `includeContext` (soft-deprecated)
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub context: Option<JsonObject>,
288}
289
290///
291/// # Builder
292/// ```rust
293/// # use rmcp::model::ClientCapabilities;
294/// let cap = ClientCapabilities::builder()
295///     .enable_experimental()
296///     .build();
297/// ```
298#[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    /// Optional MCP extensions that the client supports (SEP-1724).
305    /// Keys are extension identifiers (e.g., `"io.modelcontextprotocol/ui"`),
306    /// values are per-extension settings objects. An empty object indicates
307    /// support with no settings.
308    #[serde(skip_serializing_if = "Option::is_none")]
309    pub extensions: Option<ExtensionCapabilities>,
310    /// Capability for filesystem roots (deprecated by SEP-2577).
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub roots: Option<RootsCapabilities>,
313    /// Capability for LLM sampling requests (SEP-1577, deprecated by SEP-2577).
314    #[serde(skip_serializing_if = "Option::is_none")]
315    pub sampling: Option<SamplingCapability>,
316    /// Capability to handle elicitation requests from servers for interactive user input
317    #[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///
324/// ## Builder
325/// ```rust
326/// # use rmcp::model::ServerCapabilities;
327/// let cap = ServerCapabilities::builder()
328///     .enable_experimental()
329///     .enable_prompts()
330///     .enable_resources()
331///     .enable_tools()
332///     .enable_tool_list_changed()
333///     .build();
334/// ```
335#[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    /// Optional MCP extensions that the server supports (SEP-1724).
343    /// Keys are extension identifiers (e.g., `"io.modelcontextprotocol/apps"`),
344    /// values are per-extension settings objects. An empty object indicates
345    /// support with no settings.
346    #[serde(skip_serializing_if = "Option::is_none")]
347    pub extensions: Option<ExtensionCapabilities>,
348    /// Capability for server log message notifications (deprecated by SEP-2577).
349    #[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            // do we really need to disable some thing in builder?
448            // impl<
449            //     $(const [<$ff:upper>]: bool,)*
450            //     $(const [<$ft:upper>]: bool,)*
451            // > [<$Target Builder>]<[<$Target BuilderState>]<
452            //     $([<$ff:upper>],)*
453            //     true,
454            //     $([<$ft:upper>],)*
455            // >> {
456            //     pub fn [<disable_ $fn>](self) -> [<$Target Builder>]<[<$Target BuilderState>]<
457            //         $([<$ff:upper>],)*
458            //         false,
459            //         $([<$ft:upper>],)*
460            //     >> {
461            //         [<$Target Builder>] {
462            //             $( $ff: self.$ff, )*
463            //             $fn: None,
464            //             $( $ft: self.$ft, )*
465            //             state: PhantomData
466            //         }
467            //     }
468            // }
469        }
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    /// Enable tool calling in sampling requests
596    #[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    /// Enable context inclusion in sampling (soft-deprecated)
608    #[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    /// Enable JSON Schema validation for elicitation responses in form mode.
625    /// When enabled, the client will validate user input against the requested_schema
626    /// before sending responses back to the server.
627    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        // Test deserializing from the MCP spec format
684        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        // Verify structure
706        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        // Verify serialization matches expected format
713        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        // Verify structure
731        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        // Verify serialization matches expected format
738        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        // Test building ClientCapabilities with extensions (MCP Apps support)
748        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        // Verify serialization matches MCP Apps spec format
763        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        // Test building ServerCapabilities with extensions
774        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        // Verify serialization
786        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        // Test deserializing capabilities with extensions from JSON
794        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        // Test that empty extension settings work (indicates support with no settings)
814        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}