ultrafast_mcp_core/protocol/
capabilities.rs

1use serde::{Deserialize, Serialize};
2
3/// Client capabilities that can be negotiated during initialization
4#[derive(Debug, Clone, Serialize, Deserialize, Default)]
5pub struct ClientCapabilities {
6    /// Filesystem roots capability
7    #[serde(skip_serializing_if = "Option::is_none")]
8    pub roots: Option<RootsCapability>,
9
10    /// LLM sampling capability
11    #[serde(skip_serializing_if = "Option::is_none")]
12    pub sampling: Option<SamplingCapability>,
13
14    /// User input elicitation capability
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub elicitation: Option<ElicitationCapability>,
17}
18
19/// Server capabilities that can be advertised during initialization
20#[derive(Debug, Clone, Serialize, Deserialize, Default)]
21pub struct ServerCapabilities {
22    /// Tools capability
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub tools: Option<ToolsCapability>,
25
26    /// Resources capability
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub resources: Option<ResourcesCapability>,
29
30    /// Prompts capability
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub prompts: Option<PromptsCapability>,
33
34    /// Logging capability
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub logging: Option<LoggingCapability>,
37
38    /// Completion capability
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub completion: Option<CompletionCapability>,
41}
42
43/// Roots capability for filesystem boundary management
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct RootsCapability {
46    /// Whether the client supports list_changed notifications
47    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
48    pub list_changed: Option<bool>,
49}
50
51/// Sampling capability for LLM completions
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct SamplingCapability {
54    // Sampling capability has no specific parameters in MCP 2025-06-18
55}
56
57/// Elicitation capability for user input collection
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ElicitationCapability {
60    // Elicitation capability has no specific parameters in MCP 2025-06-18
61}
62
63/// Tools capability
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct ToolsCapability {
66    /// Whether the server supports list_changed notifications
67    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
68    pub list_changed: Option<bool>,
69}
70
71/// Resources capability
72#[derive(Debug, Clone, Serialize, Deserialize, Default)]
73pub struct ResourcesCapability {
74    /// Whether the server supports resource subscriptions
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub subscribe: Option<bool>,
77
78    /// Whether the server supports list_changed notifications
79    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
80    pub list_changed: Option<bool>,
81}
82
83/// Prompts capability
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct PromptsCapability {
86    /// Whether the server supports list_changed notifications
87    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
88    pub list_changed: Option<bool>,
89}
90
91/// Logging capability
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct LoggingCapability {
94    // Logging capability has no specific parameters in MCP 2025-06-18
95}
96
97/// Completion capability for argument autocompletion
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct CompletionCapability {
100    // Completion capability has no specific parameters in MCP 2025-06-18
101}
102
103impl ServerCapabilities {
104    /// Check if server supports a specific capability
105    pub fn supports_capability(&self, capability: &str) -> bool {
106        match capability {
107            "tools" => self.tools.is_some(),
108            "resources" => self.resources.is_some(),
109            "prompts" => self.prompts.is_some(),
110            "logging" => self.logging.is_some(),
111            "completion" => self.completion.is_some(),
112            _ => false,
113        }
114    }
115
116    /// Check if server supports a specific feature within a capability
117    pub fn supports_feature(&self, capability: &str, feature: &str) -> bool {
118        match (capability, feature) {
119            ("tools", "list_changed") => self
120                .tools
121                .as_ref()
122                .and_then(|t| t.list_changed)
123                .unwrap_or(false),
124            ("resources", "subscribe") => self
125                .resources
126                .as_ref()
127                .and_then(|r| r.subscribe)
128                .unwrap_or(false),
129            ("resources", "list_changed") => self
130                .resources
131                .as_ref()
132                .and_then(|r| r.list_changed)
133                .unwrap_or(false),
134            ("prompts", "list_changed") => self
135                .prompts
136                .as_ref()
137                .and_then(|p| p.list_changed)
138                .unwrap_or(false),
139            _ => false,
140        }
141    }
142}
143
144impl ClientCapabilities {
145    /// Check if client supports a specific capability
146    pub fn supports_capability(&self, capability: &str) -> bool {
147        match capability {
148            "roots" => self.roots.is_some(),
149            "sampling" => self.sampling.is_some(),
150            "elicitation" => self.elicitation.is_some(),
151            _ => false,
152        }
153    }
154}
155
156/// Validate compatibility between client and server capabilities
157pub fn validate_compatibility(
158    client_caps: &ClientCapabilities,
159    server_caps: &ServerCapabilities,
160) -> Result<(), String> {
161    // Check if client wants sampling but server doesn't support tools
162    if client_caps.sampling.is_some() && server_caps.tools.is_none() {
163        return Err(
164            "Client supports sampling but server does not provide tools capability".to_string(),
165        );
166    }
167
168    // Check if we have at least one compatible capability
169    if !server_caps.supports_capability("tools")
170        && !server_caps.supports_capability("resources")
171        && !server_caps.supports_capability("prompts")
172    {
173        return Err("No compatible capabilities found between client and server".to_string());
174    }
175
176    Ok(())
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[test]
184    fn test_capability_negotiation() {
185        let server_caps = ServerCapabilities {
186            tools: Some(ToolsCapability {
187                list_changed: Some(true),
188            }),
189            ..Default::default()
190        };
191        let client_caps = ClientCapabilities {
192            sampling: Some(SamplingCapability {}),
193            ..Default::default()
194        };
195
196        // Client can use sampling if server supports tools
197        assert!(validate_compatibility(&client_caps, &server_caps).is_ok());
198    }
199
200    #[test]
201    fn test_compatibility_validation() {
202        let server_caps = ServerCapabilities {
203            tools: Some(ToolsCapability {
204                list_changed: Some(true),
205            }),
206            ..Default::default()
207        };
208        let client_caps = ClientCapabilities::default();
209
210        // Valid compatibility
211        assert!(validate_compatibility(&client_caps, &server_caps).is_ok());
212
213        // Invalid: client wants sampling but server has no tools
214        let client_with_sampling = ClientCapabilities {
215            sampling: Some(SamplingCapability {}),
216            ..Default::default()
217        };
218        let server_without_tools = ServerCapabilities::default();
219        assert!(validate_compatibility(&client_with_sampling, &server_without_tools).is_err());
220    }
221
222    #[test]
223    fn test_no_overlap_error() {
224        let server_caps = ServerCapabilities::default();
225        let client_caps = ClientCapabilities::default();
226
227        // No compatible capabilities
228        assert!(validate_compatibility(&client_caps, &server_caps).is_err());
229    }
230}