ultrafast_mcp_core/protocol/
capabilities.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, Default)]
5pub struct ClientCapabilities {
6 #[serde(skip_serializing_if = "Option::is_none")]
8 pub roots: Option<RootsCapability>,
9
10 #[serde(skip_serializing_if = "Option::is_none")]
12 pub sampling: Option<SamplingCapability>,
13
14 #[serde(skip_serializing_if = "Option::is_none")]
16 pub elicitation: Option<ElicitationCapability>,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize, Default)]
21pub struct ServerCapabilities {
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub tools: Option<ToolsCapability>,
25
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub resources: Option<ResourcesCapability>,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub prompts: Option<PromptsCapability>,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub logging: Option<LoggingCapability>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub completion: Option<CompletionCapability>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct RootsCapability {
46 #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
48 pub list_changed: Option<bool>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct SamplingCapability {
54 }
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ElicitationCapability {
60 }
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct ToolsCapability {
66 #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
68 pub list_changed: Option<bool>,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, Default)]
73pub struct ResourcesCapability {
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub subscribe: Option<bool>,
77
78 #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
80 pub list_changed: Option<bool>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct PromptsCapability {
86 #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
88 pub list_changed: Option<bool>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct LoggingCapability {
94 }
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct CompletionCapability {
100 }
102
103impl ServerCapabilities {
104 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 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 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
156pub fn validate_compatibility(
158 client_caps: &ClientCapabilities,
159 server_caps: &ServerCapabilities,
160) -> Result<(), String> {
161 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 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 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 assert!(validate_compatibility(&client_caps, &server_caps).is_ok());
212
213 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 assert!(validate_compatibility(&client_caps, &server_caps).is_err());
229 }
230}