mcpkit_core/
capability.rs

1//! Capability flags for MCP clients and servers.
2//!
3//! Capabilities are negotiated during the initialization handshake.
4//! They determine what features are available in the session.
5
6use serde::{Deserialize, Serialize};
7
8/// Server capabilities advertised during initialization.
9#[derive(Debug, Clone, Default, Serialize, Deserialize)]
10pub struct ServerCapabilities {
11    /// Tool capabilities.
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub tools: Option<ToolCapability>,
14    /// Resource capabilities.
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub resources: Option<ResourceCapability>,
17    /// Prompt capabilities.
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub prompts: Option<PromptCapability>,
20    /// Task capabilities.
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub tasks: Option<TaskCapability>,
23    /// Logging capabilities.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub logging: Option<LoggingCapability>,
26    /// Completion capabilities.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub completions: Option<CompletionCapability>,
29    /// Experimental capabilities.
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub experimental: Option<serde_json::Value>,
32}
33
34impl ServerCapabilities {
35    /// Create empty capabilities.
36    #[must_use]
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    /// Enable tool support.
42    #[must_use]
43    pub fn with_tools(mut self) -> Self {
44        self.tools = Some(ToolCapability::default());
45        self
46    }
47
48    /// Enable tool support with change notifications.
49    #[must_use]
50    pub fn with_tools_and_changes(mut self) -> Self {
51        self.tools = Some(ToolCapability {
52            list_changed: Some(true),
53        });
54        self
55    }
56
57    /// Enable resource support.
58    #[must_use]
59    pub fn with_resources(mut self) -> Self {
60        self.resources = Some(ResourceCapability::default());
61        self
62    }
63
64    /// Enable resource support with subscriptions.
65    #[must_use]
66    pub fn with_resources_and_subscriptions(mut self) -> Self {
67        self.resources = Some(ResourceCapability {
68            subscribe: Some(true),
69            list_changed: Some(true),
70        });
71        self
72    }
73
74    /// Enable prompt support.
75    #[must_use]
76    pub fn with_prompts(mut self) -> Self {
77        self.prompts = Some(PromptCapability::default());
78        self
79    }
80
81    /// Enable task support.
82    #[must_use]
83    pub fn with_tasks(mut self) -> Self {
84        self.tasks = Some(TaskCapability::default());
85        self
86    }
87
88    /// Enable logging support.
89    #[must_use]
90    pub fn with_logging(mut self) -> Self {
91        self.logging = Some(LoggingCapability {});
92        self
93    }
94
95    /// Enable completion support.
96    #[must_use]
97    pub fn with_completions(mut self) -> Self {
98        self.completions = Some(CompletionCapability {});
99        self
100    }
101
102    /// Check if tools are supported.
103    #[must_use]
104    pub fn has_tools(&self) -> bool {
105        self.tools.is_some()
106    }
107
108    /// Check if resources are supported.
109    #[must_use]
110    pub fn has_resources(&self) -> bool {
111        self.resources.is_some()
112    }
113
114    /// Check if prompts are supported.
115    #[must_use]
116    pub fn has_prompts(&self) -> bool {
117        self.prompts.is_some()
118    }
119
120    /// Check if tasks are supported.
121    #[must_use]
122    pub fn has_tasks(&self) -> bool {
123        self.tasks.is_some()
124    }
125}
126
127/// Client capabilities advertised during initialization.
128#[derive(Debug, Clone, Default, Serialize, Deserialize)]
129pub struct ClientCapabilities {
130    /// Roots (file system access) capabilities.
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub roots: Option<RootsCapability>,
133    /// Sampling capabilities.
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub sampling: Option<SamplingCapability>,
136    /// Elicitation capabilities.
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub elicitation: Option<ElicitationCapability>,
139    /// Experimental capabilities.
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub experimental: Option<serde_json::Value>,
142}
143
144impl ClientCapabilities {
145    /// Create empty capabilities.
146    #[must_use]
147    pub fn new() -> Self {
148        Self::default()
149    }
150
151    /// Enable roots support.
152    #[must_use]
153    pub fn with_roots(mut self) -> Self {
154        self.roots = Some(RootsCapability::default());
155        self
156    }
157
158    /// Enable roots support with change notifications.
159    #[must_use]
160    pub fn with_roots_and_changes(mut self) -> Self {
161        self.roots = Some(RootsCapability {
162            list_changed: Some(true),
163        });
164        self
165    }
166
167    /// Enable sampling support.
168    #[must_use]
169    pub fn with_sampling(mut self) -> Self {
170        self.sampling = Some(SamplingCapability {});
171        self
172    }
173
174    /// Enable elicitation support.
175    #[must_use]
176    pub fn with_elicitation(mut self) -> Self {
177        self.elicitation = Some(ElicitationCapability {});
178        self
179    }
180
181    /// Check if roots are supported.
182    #[must_use]
183    pub fn has_roots(&self) -> bool {
184        self.roots.is_some()
185    }
186
187    /// Check if sampling is supported.
188    #[must_use]
189    pub fn has_sampling(&self) -> bool {
190        self.sampling.is_some()
191    }
192
193    /// Check if elicitation is supported.
194    #[must_use]
195    pub fn has_elicitation(&self) -> bool {
196        self.elicitation.is_some()
197    }
198}
199
200/// Tool capability flags.
201#[derive(Debug, Clone, Default, Serialize, Deserialize)]
202pub struct ToolCapability {
203    /// If true, the server will send tool list changed notifications.
204    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
205    pub list_changed: Option<bool>,
206}
207
208/// Resource capability flags.
209#[derive(Debug, Clone, Default, Serialize, Deserialize)]
210pub struct ResourceCapability {
211    /// If true, the server supports resource subscriptions.
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub subscribe: Option<bool>,
214    /// If true, the server will send resource list changed notifications.
215    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
216    pub list_changed: Option<bool>,
217}
218
219/// Prompt capability flags.
220#[derive(Debug, Clone, Default, Serialize, Deserialize)]
221pub struct PromptCapability {
222    /// If true, the server will send prompt list changed notifications.
223    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
224    pub list_changed: Option<bool>,
225}
226
227/// Task capability flags.
228#[derive(Debug, Clone, Default, Serialize, Deserialize)]
229pub struct TaskCapability {
230    /// If true, the server supports task cancellation.
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub cancellable: Option<bool>,
233}
234
235/// Logging capability flags.
236#[derive(Debug, Clone, Default, Serialize, Deserialize)]
237pub struct LoggingCapability {}
238
239/// Completion capability flags.
240#[derive(Debug, Clone, Default, Serialize, Deserialize)]
241pub struct CompletionCapability {}
242
243/// Roots capability flags.
244#[derive(Debug, Clone, Default, Serialize, Deserialize)]
245pub struct RootsCapability {
246    /// If true, the client will send roots list changed notifications.
247    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
248    pub list_changed: Option<bool>,
249}
250
251/// Sampling capability flags.
252#[derive(Debug, Clone, Default, Serialize, Deserialize)]
253pub struct SamplingCapability {}
254
255/// Elicitation capability flags.
256#[derive(Debug, Clone, Default, Serialize, Deserialize)]
257pub struct ElicitationCapability {}
258
259/// Server information provided during initialization.
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct ServerInfo {
262    /// Server name.
263    pub name: String,
264    /// Server version.
265    pub version: String,
266    /// Protocol version supported.
267    #[serde(rename = "protocolVersion", skip_serializing_if = "Option::is_none")]
268    pub protocol_version: Option<String>,
269}
270
271impl ServerInfo {
272    /// Create new server info.
273    #[must_use]
274    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
275        Self {
276            name: name.into(),
277            version: version.into(),
278            protocol_version: Some(PROTOCOL_VERSION.to_string()),
279        }
280    }
281}
282
283/// Client information provided during initialization.
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct ClientInfo {
286    /// Client name.
287    pub name: String,
288    /// Client version.
289    pub version: String,
290}
291
292impl ClientInfo {
293    /// Create new client info.
294    #[must_use]
295    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
296        Self {
297            name: name.into(),
298            version: version.into(),
299        }
300    }
301}
302
303/// Initialize request parameters.
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct InitializeRequest {
306    /// Protocol version the client supports.
307    #[serde(rename = "protocolVersion")]
308    pub protocol_version: String,
309    /// Client capabilities.
310    pub capabilities: ClientCapabilities,
311    /// Client information.
312    #[serde(rename = "clientInfo")]
313    pub client_info: ClientInfo,
314}
315
316impl InitializeRequest {
317    /// Create a new initialize request.
318    #[must_use]
319    pub fn new(client_info: ClientInfo, capabilities: ClientCapabilities) -> Self {
320        Self {
321            protocol_version: PROTOCOL_VERSION.to_string(),
322            capabilities,
323            client_info,
324        }
325    }
326}
327
328/// Initialize response.
329#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct InitializeResult {
331    /// Protocol version the server supports.
332    #[serde(rename = "protocolVersion")]
333    pub protocol_version: String,
334    /// Server capabilities.
335    pub capabilities: ServerCapabilities,
336    /// Server information.
337    #[serde(rename = "serverInfo")]
338    pub server_info: ServerInfo,
339    /// Optional instructions for using this server.
340    #[serde(skip_serializing_if = "Option::is_none")]
341    pub instructions: Option<String>,
342}
343
344impl InitializeResult {
345    /// Create a new initialize result.
346    #[must_use]
347    pub fn new(server_info: ServerInfo, capabilities: ServerCapabilities) -> Self {
348        Self {
349            protocol_version: PROTOCOL_VERSION.to_string(),
350            capabilities,
351            server_info,
352            instructions: None,
353        }
354    }
355
356    /// Set instructions.
357    #[must_use]
358    pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
359        self.instructions = Some(instructions.into());
360        self
361    }
362}
363
364/// The latest protocol version supported by this implementation.
365///
366/// This is the preferred version that clients and servers will advertise during initialization.
367pub const PROTOCOL_VERSION: &str = "2025-11-25";
368
369/// All protocol versions supported by this implementation.
370///
371/// The SDK supports multiple protocol versions for backward compatibility:
372/// - `2025-11-25`: Latest version with full feature support
373/// - `2024-11-05`: Original MCP specification, widely deployed
374///
375/// Version negotiation happens during initialization:
376/// 1. Client sends its preferred (latest) version
377/// 2. Server responds with the same version if supported, or its own preferred version
378/// 3. Client must support the server's version or disconnect
379///
380/// # Example
381///
382/// ```
383/// use mcpkit_core::capability::{SUPPORTED_PROTOCOL_VERSIONS, is_version_supported};
384///
385/// assert!(is_version_supported("2025-11-25"));
386/// assert!(is_version_supported("2024-11-05"));
387/// assert!(!is_version_supported("1.0.0"));
388/// ```
389pub const SUPPORTED_PROTOCOL_VERSIONS: &[&str] = &[
390    "2025-11-25", // Latest - full feature support
391    "2024-11-05", // Original MCP spec - widely deployed
392];
393
394/// Check if a protocol version is supported by this implementation.
395///
396/// # Arguments
397///
398/// * `version` - The protocol version string to check
399///
400/// # Returns
401///
402/// `true` if the version is in the list of supported versions, `false` otherwise.
403///
404/// # Example
405///
406/// ```
407/// use mcpkit_core::capability::is_version_supported;
408///
409/// assert!(is_version_supported("2025-11-25"));
410/// assert!(!is_version_supported("0.9.0"));
411/// ```
412#[must_use]
413pub fn is_version_supported(version: &str) -> bool {
414    SUPPORTED_PROTOCOL_VERSIONS.contains(&version)
415}
416
417/// Negotiate a protocol version between client and server.
418///
419/// Per the MCP specification:
420/// - If the requested version is supported, return it
421/// - Otherwise, return the server's preferred (latest) version
422///
423/// The client is then responsible for determining if it can support
424/// the returned version, and disconnecting if not.
425///
426/// # Arguments
427///
428/// * `requested_version` - The version requested by the client
429///
430/// # Returns
431///
432/// The negotiated protocol version string.
433///
434/// # Example
435///
436/// ```
437/// use mcpkit_core::capability::{negotiate_version, PROTOCOL_VERSION};
438///
439/// // Client requests a supported version - gets it back
440/// assert_eq!(negotiate_version("2024-11-05"), "2024-11-05");
441///
442/// // Client requests the latest version - gets it back
443/// assert_eq!(negotiate_version("2025-11-25"), "2025-11-25");
444///
445/// // Client requests unknown version - gets server's preferred version
446/// assert_eq!(negotiate_version("1.0.0"), PROTOCOL_VERSION);
447/// ```
448#[must_use]
449pub fn negotiate_version(requested_version: &str) -> &'static str {
450    if is_version_supported(requested_version) {
451        // Return the requested version if we support it
452        SUPPORTED_PROTOCOL_VERSIONS
453            .iter()
454            .find(|&&v| v == requested_version)
455            .copied()
456            .unwrap_or(PROTOCOL_VERSION)
457    } else {
458        // Return our preferred (latest) version
459        PROTOCOL_VERSION
460    }
461}
462
463/// Protocol version negotiation result.
464///
465/// Used internally to track the outcome of version negotiation.
466#[derive(Debug, Clone, PartialEq, Eq)]
467pub enum VersionNegotiationResult {
468    /// The requested version is supported and will be used.
469    Accepted(String),
470    /// The requested version is not supported; the server offers an alternative.
471    /// Client should check if it supports this alternative version.
472    CounterOffer {
473        /// The version requested by the client.
474        requested: String,
475        /// The version offered by the server.
476        offered: String,
477    },
478}
479
480impl VersionNegotiationResult {
481    /// Get the effective protocol version to use.
482    #[must_use]
483    pub fn version(&self) -> &str {
484        match self {
485            Self::Accepted(v) => v,
486            Self::CounterOffer { offered, .. } => offered,
487        }
488    }
489
490    /// Check if the negotiation was an exact match.
491    #[must_use]
492    pub fn is_exact_match(&self) -> bool {
493        matches!(self, Self::Accepted(_))
494    }
495}
496
497/// Perform version negotiation and return detailed result.
498///
499/// This is useful when you need to know whether the negotiation
500/// resulted in an exact match or a counter-offer.
501///
502/// # Arguments
503///
504/// * `requested_version` - The version requested by the client
505///
506/// # Returns
507///
508/// A [`VersionNegotiationResult`] indicating whether the version was
509/// accepted or a counter-offer was made.
510///
511/// # Example
512///
513/// ```
514/// use mcpkit_core::capability::{negotiate_version_detailed, VersionNegotiationResult};
515///
516/// let result = negotiate_version_detailed("2024-11-05");
517/// assert!(result.is_exact_match());
518///
519/// let result = negotiate_version_detailed("unknown-version");
520/// assert!(!result.is_exact_match());
521/// ```
522#[must_use]
523pub fn negotiate_version_detailed(requested_version: &str) -> VersionNegotiationResult {
524    if is_version_supported(requested_version) {
525        VersionNegotiationResult::Accepted(requested_version.to_string())
526    } else {
527        VersionNegotiationResult::CounterOffer {
528            requested: requested_version.to_string(),
529            offered: PROTOCOL_VERSION.to_string(),
530        }
531    }
532}
533
534/// Initialized notification (sent by client after receiving initialize result).
535#[derive(Debug, Clone, Default, Serialize, Deserialize)]
536pub struct InitializedNotification {}
537
538/// Ping request for keep-alive.
539#[derive(Debug, Clone, Default, Serialize, Deserialize)]
540pub struct PingRequest {}
541
542/// Ping response.
543#[derive(Debug, Clone, Default, Serialize, Deserialize)]
544pub struct PingResult {}
545
546#[cfg(test)]
547mod tests {
548    use super::*;
549
550    #[test]
551    fn test_server_capabilities_builder() {
552        let caps = ServerCapabilities::new()
553            .with_tools()
554            .with_resources_and_subscriptions()
555            .with_prompts()
556            .with_tasks();
557
558        assert!(caps.has_tools());
559        assert!(caps.has_resources());
560        assert!(caps.has_prompts());
561        assert!(caps.has_tasks());
562        assert!(caps.resources.unwrap().subscribe.unwrap());
563    }
564
565    #[test]
566    fn test_client_capabilities_builder() {
567        let caps = ClientCapabilities::new()
568            .with_roots_and_changes()
569            .with_sampling()
570            .with_elicitation();
571
572        assert!(caps.has_roots());
573        assert!(caps.has_sampling());
574        assert!(caps.has_elicitation());
575        assert!(caps.roots.unwrap().list_changed.unwrap());
576    }
577
578    #[test]
579    fn test_initialize_request() {
580        let client = ClientInfo::new("test-client", "1.0.0");
581        let caps = ClientCapabilities::new().with_sampling();
582        let request = InitializeRequest::new(client, caps);
583
584        assert_eq!(request.protocol_version, PROTOCOL_VERSION);
585        assert_eq!(request.client_info.name, "test-client");
586    }
587
588    #[test]
589    fn test_initialize_result() {
590        let server = ServerInfo::new("test-server", "1.0.0");
591        let caps = ServerCapabilities::new().with_tools();
592        let result = InitializeResult::new(server, caps)
593            .instructions("Use this server to do things");
594
595        assert_eq!(result.protocol_version, PROTOCOL_VERSION);
596        assert!(result.instructions.is_some());
597    }
598
599    #[test]
600    fn test_serialization() {
601        let caps = ServerCapabilities::new()
602            .with_tools_and_changes()
603            .with_resources();
604
605        let json = serde_json::to_string(&caps).unwrap();
606        assert!(json.contains("\"tools\""));
607        assert!(json.contains("\"listChanged\":true"));
608    }
609}