pmcp/types/
capabilities.rs

1//! Capability definitions for MCP clients and servers.
2//!
3//! This module defines the capability structures that clients and servers
4//! use to advertise their supported features.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// Client capabilities advertised during initialization.
10///
11/// # Examples
12///
13/// ```rust
14/// use pmcp::types::ClientCapabilities;
15///
16/// let capabilities = ClientCapabilities {
17///     experimental: Some([("custom-feature".to_string(), serde_json::json!(true))]
18///         .into_iter()
19///         .collect()),
20///     ..Default::default()
21/// };
22/// ```
23#[derive(Debug, Clone, Default, Serialize, Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub struct ClientCapabilities {
26    /// Tool calling capabilities
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub tools: Option<ToolCapabilities>,
29
30    /// Prompt capabilities
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub prompts: Option<PromptCapabilities>,
33
34    /// Resource capabilities
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub resources: Option<ResourceCapabilities>,
37
38    /// Logging capabilities
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub logging: Option<LoggingCapabilities>,
41
42    /// Sampling capabilities (for LLM providers)
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub sampling: Option<SamplingCapabilities>,
45
46    /// Roots capabilities
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub roots: Option<RootsCapabilities>,
49
50    /// Experimental capabilities
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub experimental: Option<HashMap<String, serde_json::Value>>,
53}
54
55/// Server capabilities advertised during initialization.
56#[derive(Debug, Clone, Default, Serialize, Deserialize)]
57#[serde(rename_all = "camelCase")]
58pub struct ServerCapabilities {
59    /// Tool providing capabilities
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub tools: Option<ToolCapabilities>,
62
63    /// Prompt providing capabilities
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub prompts: Option<PromptCapabilities>,
66
67    /// Resource providing capabilities
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub resources: Option<ResourceCapabilities>,
70
71    /// Logging capabilities
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub logging: Option<LoggingCapabilities>,
74
75    /// Completion capabilities
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub completions: Option<CompletionCapabilities>,
78
79    /// Sampling capabilities (for LLM providers)
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub sampling: Option<SamplingCapabilities>,
82
83    /// Experimental capabilities
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub experimental: Option<HashMap<String, serde_json::Value>>,
86}
87
88/// Tool-related capabilities.
89#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
90#[serde(rename_all = "camelCase")]
91pub struct ToolCapabilities {
92    /// Whether list changes are supported
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub list_changed: Option<bool>,
95}
96
97/// Prompt-related capabilities.
98#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase")]
100pub struct PromptCapabilities {
101    /// Whether list changes are supported
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub list_changed: Option<bool>,
104}
105
106/// Resource-related capabilities.
107#[derive(Debug, Clone, Default, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct ResourceCapabilities {
110    /// Whether resource subscriptions are supported
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub subscribe: Option<bool>,
113
114    /// Whether list changes are supported
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub list_changed: Option<bool>,
117}
118
119/// Logging capabilities.
120#[derive(Debug, Clone, Default, Serialize, Deserialize)]
121#[serde(rename_all = "camelCase")]
122pub struct LoggingCapabilities {
123    /// Supported log levels
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub levels: Option<Vec<String>>,
126}
127
128/// Sampling capabilities for LLM operations.
129#[derive(Debug, Clone, Default, Serialize, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct SamplingCapabilities {
132    /// Supported model families/providers
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub models: Option<Vec<String>>,
135}
136
137/// Roots capabilities.
138#[derive(Debug, Clone, Default, Serialize, Deserialize)]
139#[serde(rename_all = "camelCase")]
140pub struct RootsCapabilities {
141    /// Whether list changed notifications are supported
142    #[serde(default)]
143    pub list_changed: bool,
144}
145
146/// Completion capabilities.
147#[derive(Debug, Clone, Default, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct CompletionCapabilities {
150    /// Placeholder for completion capability options
151    #[serde(skip)]
152    _reserved: (),
153}
154
155impl ClientCapabilities {
156    /// Create a minimal set of client capabilities.
157    ///
158    /// # Examples
159    ///
160    /// ```rust
161    /// use pmcp::ClientCapabilities;
162    ///
163    /// // Create minimal capabilities (no features advertised)
164    /// let capabilities = ClientCapabilities::minimal();
165    /// assert!(!capabilities.supports_tools());
166    /// assert!(!capabilities.supports_prompts());
167    /// assert!(!capabilities.supports_resources());
168    /// assert!(!capabilities.supports_sampling());
169    ///
170    /// // Use in client initialization
171    /// # use pmcp::{Client, StdioTransport};
172    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
173    /// let transport = StdioTransport::new();
174    /// let mut client = Client::new(transport);
175    /// let server_info = client.initialize(ClientCapabilities::minimal()).await?;
176    /// # Ok(())
177    /// # }
178    /// ```
179    pub fn minimal() -> Self {
180        Self::default()
181    }
182
183    /// Create a full set of client capabilities.
184    ///
185    /// # Examples
186    ///
187    /// ```rust
188    /// use pmcp::ClientCapabilities;
189    ///
190    /// // Create full capabilities (all features supported)
191    /// let capabilities = ClientCapabilities::full();
192    /// assert!(capabilities.supports_tools());
193    /// assert!(capabilities.supports_prompts());
194    /// assert!(capabilities.supports_resources());
195    /// assert!(capabilities.supports_sampling());
196    ///
197    /// // Inspect specific capabilities
198    /// assert!(capabilities.tools.unwrap().list_changed.unwrap());
199    /// assert!(capabilities.resources.unwrap().subscribe.unwrap());
200    ///
201    /// // Use in client that needs all features
202    /// # use pmcp::{Client, StdioTransport};
203    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
204    /// let transport = StdioTransport::new();
205    /// let mut client = Client::new(transport);
206    /// let server_info = client.initialize(ClientCapabilities::full()).await?;
207    /// # Ok(())
208    /// # }
209    /// ```
210    pub fn full() -> Self {
211        Self {
212            tools: Some(ToolCapabilities {
213                list_changed: Some(true),
214            }),
215            prompts: Some(PromptCapabilities {
216                list_changed: Some(true),
217            }),
218            resources: Some(ResourceCapabilities {
219                subscribe: Some(true),
220                list_changed: Some(true),
221            }),
222            logging: Some(LoggingCapabilities {
223                levels: Some(vec![
224                    "debug".to_string(),
225                    "info".to_string(),
226                    "warning".to_string(),
227                    "error".to_string(),
228                ]),
229            }),
230            sampling: Some(SamplingCapabilities::default()),
231            roots: Some(RootsCapabilities { list_changed: true }),
232            experimental: None,
233        }
234    }
235
236    /// Check if the client supports tools.
237    ///
238    /// # Examples
239    ///
240    /// ```rust
241    /// use pmcp::{ClientCapabilities, types::capabilities::ToolCapabilities};
242    ///
243    /// // Minimal capabilities don't support tools
244    /// let minimal = ClientCapabilities::minimal();
245    /// assert!(!minimal.supports_tools());
246    ///
247    /// // Full capabilities support tools
248    /// let full = ClientCapabilities::full();
249    /// assert!(full.supports_tools());
250    ///
251    /// // Custom capabilities with only tools
252    /// let tools_only = ClientCapabilities {
253    ///     tools: Some(ToolCapabilities {
254    ///         list_changed: Some(true),
255    ///     }),
256    ///     ..Default::default()
257    /// };
258    /// assert!(tools_only.supports_tools());
259    ///
260    /// // Use to conditionally enable features
261    /// fn setup_client(caps: &ClientCapabilities) {
262    ///     if caps.supports_tools() {
263    ///         println!("Client can call tools");
264    ///     }
265    /// }
266    /// ```
267    pub fn supports_tools(&self) -> bool {
268        self.tools.is_some()
269    }
270
271    /// Check if the client supports prompts.
272    ///
273    /// # Examples
274    ///
275    /// ```rust
276    /// use pmcp::{ClientCapabilities, types::capabilities::PromptCapabilities};
277    ///
278    /// // Check prompt support
279    /// let caps = ClientCapabilities::full();
280    /// assert!(caps.supports_prompts());
281    ///
282    /// // Build capabilities with just prompts
283    /// let prompts_only = ClientCapabilities {
284    ///     prompts: Some(PromptCapabilities {
285    ///         list_changed: Some(true),
286    ///     }),
287    ///     ..Default::default()
288    /// };
289    /// assert!(prompts_only.supports_prompts());
290    /// assert!(!prompts_only.supports_tools());
291    ///
292    /// // Conditional logic based on prompt support
293    /// # use pmcp::{Client, StdioTransport};
294    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
295    /// let caps = ClientCapabilities::full();
296    /// if caps.supports_prompts() {
297    ///     // Client can use prompts
298    ///     println!("This client supports prompts");
299    /// }
300    /// # Ok(())
301    /// # }
302    /// ```
303    pub fn supports_prompts(&self) -> bool {
304        self.prompts.is_some()
305    }
306
307    /// Check if the client supports resources.
308    ///
309    /// # Examples
310    ///
311    /// ```rust
312    /// use pmcp::{ClientCapabilities, types::capabilities::ResourceCapabilities};
313    ///
314    /// // Check resource support with subscriptions
315    /// let caps = ClientCapabilities::full();
316    /// assert!(caps.supports_resources());
317    ///
318    /// // Build capabilities with advanced resource features
319    /// let advanced_resources = ClientCapabilities {
320    ///     resources: Some(ResourceCapabilities {
321    ///         subscribe: Some(true),
322    ///         list_changed: Some(true),
323    ///     }),
324    ///     ..Default::default()
325    /// };
326    /// assert!(advanced_resources.supports_resources());
327    ///
328    /// // Check specific resource capabilities
329    /// if let Some(resource_caps) = &advanced_resources.resources {
330    ///     assert!(resource_caps.subscribe.unwrap_or(false));
331    ///     println!("Client can subscribe to resource changes");
332    /// }
333    /// ```
334    pub fn supports_resources(&self) -> bool {
335        self.resources.is_some()
336    }
337
338    /// Check if the client supports sampling.
339    ///
340    /// # Examples
341    ///
342    /// ```rust
343    /// use pmcp::{ClientCapabilities, types::capabilities::SamplingCapabilities};
344    ///
345    /// // Check sampling support for LLM operations
346    /// let caps = ClientCapabilities::full();
347    /// assert!(caps.supports_sampling());
348    ///
349    /// // Build LLM client capabilities
350    /// let llm_client = ClientCapabilities {
351    ///     sampling: Some(SamplingCapabilities {
352    ///         models: Some(vec![
353    ///             "gpt-4".to_string(),
354    ///             "claude-3".to_string(),
355    ///             "llama-2".to_string(),
356    ///         ]),
357    ///     }),
358    ///     ..Default::default()
359    /// };
360    /// assert!(llm_client.supports_sampling());
361    ///
362    /// // List supported models
363    /// if let Some(sampling) = &llm_client.sampling {
364    ///     if let Some(models) = &sampling.models {
365    ///         println!("Supported models: {:?}", models);
366    ///     }
367    /// }
368    /// ```
369    pub fn supports_sampling(&self) -> bool {
370        self.sampling.is_some()
371    }
372}
373
374impl ServerCapabilities {
375    /// Create a minimal set of server capabilities.
376    ///
377    /// # Examples
378    ///
379    /// ```rust
380    /// use pmcp::ServerCapabilities;
381    ///
382    /// // Create minimal server with no advertised features
383    /// let capabilities = ServerCapabilities::minimal();
384    /// assert!(!capabilities.provides_tools());
385    /// assert!(!capabilities.provides_prompts());
386    /// assert!(!capabilities.provides_resources());
387    ///
388    /// // Use in server that implements custom protocol extensions
389    /// # use pmcp::Server;
390    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
391    /// let server = Server::builder()
392    ///     .name("minimal-server")
393    ///     .version("1.0.0")
394    ///     .capabilities(ServerCapabilities::minimal())
395    ///     .build()?;
396    /// # Ok(())
397    /// # }
398    /// ```
399    pub fn minimal() -> Self {
400        Self::default()
401    }
402
403    /// Create capabilities for a tool server.
404    ///
405    /// # Examples
406    ///
407    /// ```rust
408    /// use pmcp::ServerCapabilities;
409    ///
410    /// // Create server that only provides tools
411    /// let capabilities = ServerCapabilities::tools_only();
412    /// assert!(capabilities.provides_tools());
413    /// assert!(!capabilities.provides_prompts());
414    /// assert!(!capabilities.provides_resources());
415    ///
416    /// // Use in a tool-focused server
417    /// # use pmcp::{Server, ToolHandler};
418    /// # use async_trait::async_trait;
419    /// # struct CalculatorTool;
420    /// # #[async_trait]
421    /// # impl ToolHandler for CalculatorTool {
422    /// #     async fn handle(&self, args: serde_json::Value, _extra: pmcp::RequestHandlerExtra) -> Result<serde_json::Value, pmcp::Error> {
423    /// #         Ok(serde_json::json!({"result": 42}))
424    /// #     }
425    /// # }
426    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
427    /// let server = Server::builder()
428    ///     .name("calculator-server")
429    ///     .version("1.0.0")
430    ///     .capabilities(ServerCapabilities::tools_only())
431    ///     .tool("calculate", CalculatorTool)
432    ///     .build()?;
433    /// # Ok(())
434    /// # }
435    /// ```
436    pub fn tools_only() -> Self {
437        Self {
438            tools: Some(ToolCapabilities {
439                list_changed: Some(true),
440            }),
441            ..Default::default()
442        }
443    }
444
445    /// Create capabilities for a prompt server.
446    ///
447    /// # Examples
448    ///
449    /// ```rust
450    /// use pmcp::ServerCapabilities;
451    ///
452    /// // Create server that only provides prompts
453    /// let capabilities = ServerCapabilities::prompts_only();
454    /// assert!(!capabilities.provides_tools());
455    /// assert!(capabilities.provides_prompts());
456    /// assert!(!capabilities.provides_resources());
457    ///
458    /// // Use in a prompt template server
459    /// # use pmcp::{Server, PromptHandler};
460    /// # use async_trait::async_trait;
461    /// # use pmcp::types::protocol::{GetPromptResult, PromptMessage, Role, Content};
462    /// # struct GreetingPrompt;
463    /// # #[async_trait]
464    /// # impl PromptHandler for GreetingPrompt {
465    /// #     async fn handle(&self, args: std::collections::HashMap<String, String>, _extra: pmcp::RequestHandlerExtra) -> Result<GetPromptResult, pmcp::Error> {
466    /// #         Ok(GetPromptResult {
467    /// #             description: Some("Greeting prompt".to_string()),
468    /// #             messages: vec![PromptMessage {
469    /// #                 role: Role::System,
470    /// #                 content: Content::Text { text: "Hello!".to_string() },
471    /// #             }],
472    /// #         })
473    /// #     }
474    /// # }
475    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
476    /// let server = Server::builder()
477    ///     .name("prompt-server")
478    ///     .version("1.0.0")
479    ///     .capabilities(ServerCapabilities::prompts_only())
480    ///     .prompt("greeting", GreetingPrompt)
481    ///     .build()?;
482    /// # Ok(())
483    /// # }
484    /// ```
485    pub fn prompts_only() -> Self {
486        Self {
487            prompts: Some(PromptCapabilities {
488                list_changed: Some(true),
489            }),
490            ..Default::default()
491        }
492    }
493
494    /// Create capabilities for a resource server.
495    ///
496    /// # Examples
497    ///
498    /// ```rust
499    /// use pmcp::ServerCapabilities;
500    ///
501    /// // Create server that only provides resources
502    /// let capabilities = ServerCapabilities::resources_only();
503    /// assert!(!capabilities.provides_tools());
504    /// assert!(!capabilities.provides_prompts());
505    /// assert!(capabilities.provides_resources());
506    ///
507    /// // Check subscription support
508    /// let resource_caps = capabilities.resources.unwrap();
509    /// assert!(resource_caps.subscribe.unwrap());
510    /// assert!(resource_caps.list_changed.unwrap());
511    ///
512    /// // Use in a file system resource server
513    /// # use pmcp::{Server, ResourceHandler};
514    /// # use async_trait::async_trait;
515    /// # use pmcp::types::protocol::{ReadResourceResult, ListResourcesResult, ResourceInfo, Content};
516    /// # struct FileResource;
517    /// # #[async_trait]
518    /// # impl ResourceHandler for FileResource {
519    /// #     async fn read(&self, uri: &str, _extra: pmcp::RequestHandlerExtra) -> Result<ReadResourceResult, pmcp::Error> {
520    /// #         Ok(ReadResourceResult {
521    /// #             contents: vec![Content::Text { text: "File contents".to_string() }],
522    /// #         })
523    /// #     }
524    /// #     async fn list(&self, _path: Option<String>, _extra: pmcp::RequestHandlerExtra) -> Result<ListResourcesResult, pmcp::Error> {
525    /// #         Ok(ListResourcesResult {
526    /// #             resources: vec![],
527    /// #             next_cursor: None,
528    /// #         })
529    /// #     }
530    /// # }
531    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
532    /// let server = Server::builder()
533    ///     .name("filesystem-server")
534    ///     .version("1.0.0")
535    ///     .capabilities(ServerCapabilities::resources_only())
536    ///     .resources(FileResource)
537    ///     .build()?;
538    /// # Ok(())
539    /// # }
540    /// ```
541    pub fn resources_only() -> Self {
542        Self {
543            resources: Some(ResourceCapabilities {
544                subscribe: Some(true),
545                list_changed: Some(true),
546            }),
547            ..Default::default()
548        }
549    }
550
551    /// Check if the server provides tools.
552    ///
553    /// # Examples
554    ///
555    /// ```rust
556    /// use pmcp::ServerCapabilities;
557    ///
558    /// // Check different server configurations
559    /// let tool_server = ServerCapabilities::tools_only();
560    /// assert!(tool_server.provides_tools());
561    ///
562    /// let minimal_server = ServerCapabilities::minimal();
563    /// assert!(!minimal_server.provides_tools());
564    ///
565    /// // Use in server logic
566    /// fn validate_server(caps: &ServerCapabilities) {
567    ///     if caps.provides_tools() {
568    ///         println!("Server can handle tool calls");
569    ///     } else {
570    ///         println!("Server does not provide tools");
571    ///     }
572    /// }
573    ///
574    /// // Combine multiple capabilities
575    /// use pmcp::types::capabilities::{ToolCapabilities, PromptCapabilities};
576    /// let multi_server = ServerCapabilities {
577    ///     tools: Some(ToolCapabilities::default()),
578    ///     prompts: Some(PromptCapabilities::default()),
579    ///     ..Default::default()
580    /// };
581    /// assert!(multi_server.provides_tools());
582    /// assert!(multi_server.provides_prompts());
583    /// ```
584    pub fn provides_tools(&self) -> bool {
585        self.tools.is_some()
586    }
587
588    /// Check if the server provides prompts.
589    ///
590    /// # Examples
591    ///
592    /// ```rust
593    /// use pmcp::ServerCapabilities;
594    ///
595    /// // Check prompt server
596    /// let prompt_server = ServerCapabilities::prompts_only();
597    /// assert!(prompt_server.provides_prompts());
598    /// assert!(!prompt_server.provides_tools());
599    ///
600    /// // Use in client code to check server features
601    /// # use pmcp::{Client, StdioTransport};
602    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
603    /// # let transport = StdioTransport::new();
604    /// # let mut client = Client::new(transport);
605    /// # let server_info = client.initialize(pmcp::ClientCapabilities::default()).await?;
606    /// if server_info.capabilities.provides_prompts() {
607    ///     // Server supports prompts, we can list them
608    ///     let prompts = client.list_prompts(None).await?;
609    ///     println!("Available prompts: {}", prompts.prompts.len());
610    /// }
611    /// # Ok(())
612    /// # }
613    /// ```
614    pub fn provides_prompts(&self) -> bool {
615        self.prompts.is_some()
616    }
617
618    /// Check if the server provides resources.
619    ///
620    /// # Examples
621    ///
622    /// ```rust
623    /// use pmcp::ServerCapabilities;
624    ///
625    /// // Check resource server capabilities
626    /// let resource_server = ServerCapabilities::resources_only();
627    /// assert!(resource_server.provides_resources());
628    ///
629    /// // Check if subscriptions are supported
630    /// if resource_server.provides_resources() {
631    ///     if let Some(res_caps) = &resource_server.resources {
632    ///         if res_caps.subscribe.unwrap_or(false) {
633    ///             println!("Server supports resource subscriptions");
634    ///         }
635    ///     }
636    /// }
637    ///
638    /// // Build a full-featured server
639    /// use pmcp::types::capabilities::*;
640    /// let full_server = ServerCapabilities {
641    ///     tools: Some(ToolCapabilities::default()),
642    ///     prompts: Some(PromptCapabilities::default()),
643    ///     resources: Some(ResourceCapabilities {
644    ///         subscribe: Some(true),
645    ///         list_changed: Some(true),
646    ///     }),
647    ///     ..Default::default()
648    /// };
649    /// assert!(full_server.provides_tools());
650    /// assert!(full_server.provides_prompts());
651    /// assert!(full_server.provides_resources());
652    /// ```
653    pub fn provides_resources(&self) -> bool {
654        self.resources.is_some()
655    }
656}
657
658#[cfg(test)]
659mod tests {
660    use super::*;
661
662    #[test]
663    fn client_capabilities_helpers() {
664        let minimal = ClientCapabilities::minimal();
665        assert!(!minimal.supports_tools());
666        assert!(!minimal.supports_prompts());
667        assert!(!minimal.supports_resources());
668        assert!(!minimal.supports_sampling());
669
670        let full = ClientCapabilities::full();
671        assert!(full.supports_tools());
672        assert!(full.supports_prompts());
673        assert!(full.supports_resources());
674        assert!(full.supports_sampling());
675    }
676
677    #[test]
678    fn server_capabilities_helpers() {
679        let tools_only = ServerCapabilities::tools_only();
680        assert!(tools_only.provides_tools());
681        assert!(!tools_only.provides_prompts());
682        assert!(!tools_only.provides_resources());
683
684        let prompts_only = ServerCapabilities::prompts_only();
685        assert!(!prompts_only.provides_tools());
686        assert!(prompts_only.provides_prompts());
687        assert!(!prompts_only.provides_resources());
688    }
689
690    #[test]
691    fn capabilities_serialization() {
692        let caps = ClientCapabilities {
693            tools: Some(ToolCapabilities {
694                list_changed: Some(true),
695            }),
696            ..Default::default()
697        };
698
699        let json = serde_json::to_value(&caps).unwrap();
700        assert_eq!(json["tools"]["listChanged"], true);
701        assert!(json.get("prompts").is_none());
702    }
703}