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