mcpkit_core/
protocol_version.rs

1//! Protocol version types and negotiation.
2//!
3//! This module provides a type-safe representation of MCP protocol versions
4//! with capability detection methods for version-specific features.
5//!
6//! # Protocol Version History
7//!
8//! | Version | Date | Key Changes |
9//! |---------|------|-------------|
10//! | 2024-11-05 | Nov 2024 | Initial MCP specification, HTTP+SSE transport |
11//! | 2025-03-26 | Mar 2025 | OAuth 2.1, Streamable HTTP, batching, tool annotations, audio |
12//! | 2025-06-18 | Jun 2025 | Elicitation, structured output, resource links, removed batching |
13//! | 2025-11-25 | Nov 2025 | Tasks, parallel tools, server-side agent loops |
14//!
15//! # Example
16//!
17//! ```rust
18//! use mcpkit_core::protocol_version::ProtocolVersion;
19//!
20//! // Parse version from string
21//! let version: ProtocolVersion = "2025-03-26".parse().unwrap();
22//!
23//! // Check feature support
24//! assert!(version.supports_oauth());
25//! assert!(version.supports_tool_annotations());
26//! assert!(!version.supports_elicitation()); // Added in 2025-06-18
27//!
28//! // Version comparison
29//! assert!(ProtocolVersion::V2025_11_25 > ProtocolVersion::V2024_11_05);
30//! ```
31
32use std::fmt;
33use std::str::FromStr;
34
35use serde::{Deserialize, Serialize};
36
37/// MCP protocol versions in chronological order.
38///
39/// The ordering is: `V2024_11_05 < V2025_03_26 < V2025_06_18 < V2025_11_25`
40///
41/// This enum implements `Ord`, so you can compare versions:
42///
43/// ```rust
44/// use mcpkit_core::protocol_version::ProtocolVersion;
45///
46/// assert!(ProtocolVersion::V2025_11_25 > ProtocolVersion::V2024_11_05);
47/// assert!(ProtocolVersion::V2025_03_26 >= ProtocolVersion::V2024_11_05);
48/// ```
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
50#[serde(into = "String", try_from = "String")]
51pub enum ProtocolVersion {
52    /// Original MCP specification (November 2024).
53    ///
54    /// Features: HTTP+SSE transport, tools, resources, prompts.
55    V2024_11_05,
56
57    /// OAuth 2.1 and Streamable HTTP update (March 2025).
58    ///
59    /// New features:
60    /// - OAuth 2.1 authorization framework
61    /// - Streamable HTTP transport (replaces HTTP+SSE)
62    /// - JSON-RPC batching (removed in 2025-06-18)
63    /// - Tool annotations (readOnly, destructive)
64    /// - Audio content type
65    /// - Completions capability
66    V2025_03_26,
67
68    /// Security and elicitation update (June 2025).
69    ///
70    /// New features:
71    /// - Elicitation (server requesting user input)
72    /// - Structured tool output
73    /// - Resource links in tool results
74    /// - Protected resource metadata
75    /// - `_meta` field in messages
76    /// - `title` field for display names
77    ///
78    /// Breaking changes:
79    /// - Removed JSON-RPC batching
80    /// - MCP-Protocol-Version header required
81    V2025_06_18,
82
83    /// Tasks and parallel tools update (November 2025).
84    ///
85    /// New features:
86    /// - Tasks with async status tracking
87    /// - Parallel tool calls
88    /// - Server-side agent loops
89    /// - Tool calling in sampling requests
90    V2025_11_25,
91}
92
93impl ProtocolVersion {
94    /// The latest supported protocol version.
95    pub const LATEST: Self = Self::V2025_11_25;
96
97    /// The default version used when not specified.
98    pub const DEFAULT: Self = Self::LATEST;
99
100    /// All supported versions in chronological order.
101    pub const ALL: &'static [Self] = &[
102        Self::V2024_11_05,
103        Self::V2025_03_26,
104        Self::V2025_06_18,
105        Self::V2025_11_25,
106    ];
107
108    /// Returns the string representation used in the protocol.
109    ///
110    /// This matches the format expected in MCP messages (e.g., `"2025-11-25"`).
111    #[must_use]
112    pub const fn as_str(&self) -> &'static str {
113        match self {
114            Self::V2024_11_05 => "2024-11-05",
115            Self::V2025_03_26 => "2025-03-26",
116            Self::V2025_06_18 => "2025-06-18",
117            Self::V2025_11_25 => "2025-11-25",
118        }
119    }
120
121    // =========================================================================
122    // Transport Features
123    // =========================================================================
124
125    /// Whether this version uses HTTP+SSE transport (original spec).
126    ///
127    /// Only `V2024_11_05` uses the original HTTP+SSE transport.
128    /// Later versions use Streamable HTTP.
129    #[must_use]
130    pub const fn supports_sse_transport(&self) -> bool {
131        matches!(self, Self::V2024_11_05)
132    }
133
134    /// Whether this version supports Streamable HTTP transport.
135    ///
136    /// Added in 2025-03-26, replacing HTTP+SSE.
137    #[must_use]
138    pub const fn supports_streamable_http(&self) -> bool {
139        matches!(
140            self,
141            Self::V2025_03_26 | Self::V2025_06_18 | Self::V2025_11_25
142        )
143    }
144
145    /// Whether this version supports JSON-RPC batching.
146    ///
147    /// Only available in 2025-03-26. Removed in 2025-06-18.
148    #[must_use]
149    pub const fn supports_batching(&self) -> bool {
150        matches!(self, Self::V2025_03_26)
151    }
152
153    /// Whether the MCP-Protocol-Version header is required in HTTP requests.
154    ///
155    /// Required from 2025-06-18 onwards.
156    #[must_use]
157    pub const fn requires_version_header(&self) -> bool {
158        matches!(self, Self::V2025_06_18 | Self::V2025_11_25)
159    }
160
161    // =========================================================================
162    // Content Types
163    // =========================================================================
164
165    /// Whether this version supports audio content type.
166    ///
167    /// Added in 2025-03-26.
168    #[must_use]
169    pub const fn supports_audio_content(&self) -> bool {
170        matches!(
171            self,
172            Self::V2025_03_26 | Self::V2025_06_18 | Self::V2025_11_25
173        )
174    }
175
176    // =========================================================================
177    // Tool Features
178    // =========================================================================
179
180    /// Whether this version supports tool annotations.
181    ///
182    /// Tool annotations describe behavior like `readOnly`, `destructive`, `idempotent`.
183    /// Added in 2025-03-26.
184    #[must_use]
185    pub const fn supports_tool_annotations(&self) -> bool {
186        matches!(
187            self,
188            Self::V2025_03_26 | Self::V2025_06_18 | Self::V2025_11_25
189        )
190    }
191
192    /// Whether this version supports structured tool output.
193    ///
194    /// Allows tools to return structured data alongside text.
195    /// Added in 2025-06-18.
196    #[must_use]
197    pub const fn supports_structured_tool_output(&self) -> bool {
198        matches!(self, Self::V2025_06_18 | Self::V2025_11_25)
199    }
200
201    /// Whether this version supports resource links in tool results.
202    ///
203    /// Allows tool results to reference resources.
204    /// Added in 2025-06-18.
205    #[must_use]
206    pub const fn supports_resource_links(&self) -> bool {
207        matches!(self, Self::V2025_06_18 | Self::V2025_11_25)
208    }
209
210    /// Whether this version supports parallel tool calls.
211    ///
212    /// Allows multiple tools to be called concurrently.
213    /// Added in 2025-11-25.
214    #[must_use]
215    pub const fn supports_parallel_tools(&self) -> bool {
216        matches!(self, Self::V2025_11_25)
217    }
218
219    // =========================================================================
220    // Authorization Features
221    // =========================================================================
222
223    /// Whether this version supports OAuth 2.1 authorization.
224    ///
225    /// Added in 2025-03-26.
226    #[must_use]
227    pub const fn supports_oauth(&self) -> bool {
228        matches!(
229            self,
230            Self::V2025_03_26 | Self::V2025_06_18 | Self::V2025_11_25
231        )
232    }
233
234    /// Whether this version supports protected resource metadata.
235    ///
236    /// Enables discovery of OAuth authorization servers.
237    /// Added in 2025-06-18.
238    #[must_use]
239    pub const fn supports_protected_resources(&self) -> bool {
240        matches!(self, Self::V2025_06_18 | Self::V2025_11_25)
241    }
242
243    // =========================================================================
244    // Server Features
245    // =========================================================================
246
247    /// Whether this version supports elicitation.
248    ///
249    /// Elicitation allows servers to request additional information from users.
250    /// Added in 2025-06-18.
251    #[must_use]
252    pub const fn supports_elicitation(&self) -> bool {
253        matches!(self, Self::V2025_06_18 | Self::V2025_11_25)
254    }
255
256    /// Whether this version supports tasks with async status tracking.
257    ///
258    /// Tasks allow tracking long-running operations.
259    /// Added in 2025-11-25.
260    #[must_use]
261    pub const fn supports_tasks(&self) -> bool {
262        matches!(self, Self::V2025_11_25)
263    }
264
265    /// Whether this version supports server-side agent loops.
266    ///
267    /// Enables sophisticated multi-step reasoning on the server.
268    /// Added in 2025-11-25.
269    #[must_use]
270    pub const fn supports_agent_loops(&self) -> bool {
271        matches!(self, Self::V2025_11_25)
272    }
273
274    /// Whether this version supports tool calling in sampling requests.
275    ///
276    /// Allows servers to include tool definitions in sampling.
277    /// Added in 2025-11-25.
278    #[must_use]
279    pub const fn supports_sampling_tools(&self) -> bool {
280        matches!(self, Self::V2025_11_25)
281    }
282
283    // =========================================================================
284    // Schema Features
285    // =========================================================================
286
287    /// Whether this version supports the `_meta` field in messages.
288    ///
289    /// Provides metadata for messages.
290    /// Added in 2025-06-18.
291    #[must_use]
292    pub const fn supports_meta_field(&self) -> bool {
293        matches!(self, Self::V2025_06_18 | Self::V2025_11_25)
294    }
295
296    /// Whether this version supports the `title` field for display names.
297    ///
298    /// Separate from `name` which is the programmatic identifier.
299    /// Added in 2025-06-18.
300    #[must_use]
301    pub const fn supports_title_field(&self) -> bool {
302        matches!(self, Self::V2025_06_18 | Self::V2025_11_25)
303    }
304
305    /// Whether this version supports the `context` field in completion requests.
306    ///
307    /// Provides previously-resolved variable values.
308    /// Added in 2025-06-18.
309    #[must_use]
310    pub const fn supports_completion_context(&self) -> bool {
311        matches!(self, Self::V2025_06_18 | Self::V2025_11_25)
312    }
313
314    /// Whether this version supports the `completions` capability.
315    ///
316    /// Explicitly indicates support for argument autocompletion.
317    /// Added in 2025-03-26.
318    #[must_use]
319    pub const fn supports_completions_capability(&self) -> bool {
320        matches!(
321            self,
322            Self::V2025_03_26 | Self::V2025_06_18 | Self::V2025_11_25
323        )
324    }
325
326    // =========================================================================
327    // Version Negotiation
328    // =========================================================================
329
330    /// Negotiate the best protocol version between requested and supported versions.
331    ///
332    /// Returns the highest version that is:
333    /// 1. Less than or equal to the requested version
334    /// 2. In the supported versions list
335    ///
336    /// Returns `None` if no compatible version exists.
337    ///
338    /// # Arguments
339    ///
340    /// * `requested` - The version string requested by the client
341    /// * `supported` - List of versions supported by the server
342    ///
343    /// # Example
344    ///
345    /// ```rust
346    /// use mcpkit_core::protocol_version::ProtocolVersion;
347    ///
348    /// // Server supports all versions, client requests latest
349    /// let negotiated = ProtocolVersion::negotiate(
350    ///     "2025-11-25",
351    ///     ProtocolVersion::ALL
352    /// );
353    /// assert_eq!(negotiated, Some(ProtocolVersion::V2025_11_25));
354    ///
355    /// // Client requests older version
356    /// let negotiated = ProtocolVersion::negotiate(
357    ///     "2024-11-05",
358    ///     ProtocolVersion::ALL
359    /// );
360    /// assert_eq!(negotiated, Some(ProtocolVersion::V2024_11_05));
361    ///
362    /// // Client requests unknown future version
363    /// let negotiated = ProtocolVersion::negotiate(
364    ///     "2026-01-01",
365    ///     ProtocolVersion::ALL
366    /// );
367    /// // Returns latest supported version
368    /// assert_eq!(negotiated, Some(ProtocolVersion::V2025_11_25));
369    /// ```
370    #[must_use]
371    pub fn negotiate(requested: &str, supported: &[Self]) -> Option<Self> {
372        // Try to parse the requested version
373        if let Ok(requested_version) = Self::from_str(requested) {
374            // Find the highest supported version <= requested
375            supported
376                .iter()
377                .filter(|v| **v <= requested_version)
378                .max()
379                .copied()
380        } else {
381            // Unknown version string - return latest supported
382            // This handles future versions gracefully
383            supported.iter().max().copied()
384        }
385    }
386
387    /// Check if this version can communicate with another version.
388    ///
389    /// Newer servers can communicate with older clients using backward compatibility.
390    ///
391    /// # Arguments
392    ///
393    /// * `client_version` - The version the client supports
394    ///
395    /// # Example
396    ///
397    /// ```rust
398    /// use mcpkit_core::protocol_version::ProtocolVersion;
399    ///
400    /// let server = ProtocolVersion::V2025_11_25;
401    ///
402    /// // Server can talk to older clients
403    /// assert!(server.is_compatible_with(ProtocolVersion::V2024_11_05));
404    ///
405    /// // Server can talk to same version
406    /// assert!(server.is_compatible_with(ProtocolVersion::V2025_11_25));
407    /// ```
408    #[must_use]
409    pub const fn is_compatible_with(&self, client_version: Self) -> bool {
410        // Server can communicate with clients at same or older version
411        // Client version must be <= server version
412        (client_version as u8) <= (*self as u8)
413    }
414}
415
416impl Default for ProtocolVersion {
417    fn default() -> Self {
418        Self::DEFAULT
419    }
420}
421
422impl fmt::Display for ProtocolVersion {
423    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424        f.write_str(self.as_str())
425    }
426}
427
428/// Error returned when parsing an unknown protocol version string.
429#[derive(Debug, Clone, PartialEq, Eq)]
430pub struct VersionParseError {
431    /// The unknown version string.
432    pub version: String,
433}
434
435impl fmt::Display for VersionParseError {
436    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437        write!(
438            f,
439            "unknown protocol version '{}', supported versions: {:?}",
440            self.version,
441            ProtocolVersion::ALL
442                .iter()
443                .map(ProtocolVersion::as_str)
444                .collect::<Vec<_>>()
445        )
446    }
447}
448
449impl std::error::Error for VersionParseError {}
450
451impl FromStr for ProtocolVersion {
452    type Err = VersionParseError;
453
454    fn from_str(s: &str) -> Result<Self, Self::Err> {
455        match s {
456            "2024-11-05" => Ok(Self::V2024_11_05),
457            "2025-03-26" => Ok(Self::V2025_03_26),
458            "2025-06-18" => Ok(Self::V2025_06_18),
459            "2025-11-25" => Ok(Self::V2025_11_25),
460            _ => Err(VersionParseError {
461                version: s.to_string(),
462            }),
463        }
464    }
465}
466
467impl From<ProtocolVersion> for String {
468    fn from(version: ProtocolVersion) -> Self {
469        version.as_str().to_string()
470    }
471}
472
473impl TryFrom<String> for ProtocolVersion {
474    type Error = VersionParseError;
475
476    fn try_from(s: String) -> Result<Self, Self::Error> {
477        s.parse()
478    }
479}
480
481impl TryFrom<&str> for ProtocolVersion {
482    type Error = VersionParseError;
483
484    fn try_from(s: &str) -> Result<Self, Self::Error> {
485        s.parse()
486    }
487}
488
489#[cfg(test)]
490mod tests {
491    use super::*;
492
493    #[test]
494    fn test_version_ordering() {
495        assert!(ProtocolVersion::V2024_11_05 < ProtocolVersion::V2025_03_26);
496        assert!(ProtocolVersion::V2025_03_26 < ProtocolVersion::V2025_06_18);
497        assert!(ProtocolVersion::V2025_06_18 < ProtocolVersion::V2025_11_25);
498    }
499
500    #[test]
501    fn test_version_parse() {
502        assert_eq!(
503            "2024-11-05".parse::<ProtocolVersion>().unwrap(),
504            ProtocolVersion::V2024_11_05
505        );
506        assert_eq!(
507            "2025-03-26".parse::<ProtocolVersion>().unwrap(),
508            ProtocolVersion::V2025_03_26
509        );
510        assert_eq!(
511            "2025-06-18".parse::<ProtocolVersion>().unwrap(),
512            ProtocolVersion::V2025_06_18
513        );
514        assert_eq!(
515            "2025-11-25".parse::<ProtocolVersion>().unwrap(),
516            ProtocolVersion::V2025_11_25
517        );
518        assert!("unknown".parse::<ProtocolVersion>().is_err());
519    }
520
521    #[test]
522    fn test_version_display() {
523        assert_eq!(ProtocolVersion::V2024_11_05.to_string(), "2024-11-05");
524        assert_eq!(ProtocolVersion::V2025_11_25.to_string(), "2025-11-25");
525    }
526
527    #[test]
528    fn test_feature_support() {
529        // V2024_11_05 - Original features only
530        let v1 = ProtocolVersion::V2024_11_05;
531        assert!(v1.supports_sse_transport());
532        assert!(!v1.supports_streamable_http());
533        assert!(!v1.supports_oauth());
534        assert!(!v1.supports_elicitation());
535        assert!(!v1.supports_tasks());
536
537        // V2025_03_26 - OAuth, Streamable HTTP, etc.
538        let v2 = ProtocolVersion::V2025_03_26;
539        assert!(!v2.supports_sse_transport());
540        assert!(v2.supports_streamable_http());
541        assert!(v2.supports_oauth());
542        assert!(v2.supports_tool_annotations());
543        assert!(v2.supports_batching());
544        assert!(!v2.supports_elicitation());
545
546        // V2025_06_18 - Elicitation, no batching
547        let v3 = ProtocolVersion::V2025_06_18;
548        assert!(v3.supports_oauth());
549        assert!(!v3.supports_batching());
550        assert!(v3.supports_elicitation());
551        assert!(v3.supports_meta_field());
552        assert!(!v3.supports_tasks());
553
554        // V2025_11_25 - Tasks, parallel tools
555        let v4 = ProtocolVersion::V2025_11_25;
556        assert!(v4.supports_elicitation());
557        assert!(v4.supports_tasks());
558        assert!(v4.supports_parallel_tools());
559        assert!(v4.supports_agent_loops());
560    }
561
562    #[test]
563    fn test_negotiate() {
564        let all = ProtocolVersion::ALL;
565
566        // Exact match
567        assert_eq!(
568            ProtocolVersion::negotiate("2024-11-05", all),
569            Some(ProtocolVersion::V2024_11_05)
570        );
571        assert_eq!(
572            ProtocolVersion::negotiate("2025-11-25", all),
573            Some(ProtocolVersion::V2025_11_25)
574        );
575
576        // Unknown future version - returns latest
577        assert_eq!(
578            ProtocolVersion::negotiate("2026-01-01", all),
579            Some(ProtocolVersion::V2025_11_25)
580        );
581
582        // Server only supports old version
583        let old_only = &[ProtocolVersion::V2024_11_05];
584        assert_eq!(
585            ProtocolVersion::negotiate("2025-11-25", old_only),
586            Some(ProtocolVersion::V2024_11_05)
587        );
588
589        // Empty supported list
590        assert_eq!(ProtocolVersion::negotiate("2025-11-25", &[]), None);
591    }
592
593    #[test]
594    fn test_is_compatible_with() {
595        let latest = ProtocolVersion::V2025_11_25;
596        let oldest = ProtocolVersion::V2024_11_05;
597
598        // Latest server is compatible with all older versions
599        assert!(latest.is_compatible_with(oldest));
600        assert!(latest.is_compatible_with(ProtocolVersion::V2025_03_26));
601        assert!(latest.is_compatible_with(ProtocolVersion::V2025_06_18));
602        assert!(latest.is_compatible_with(latest));
603
604        // Old server can only handle same or older
605        assert!(oldest.is_compatible_with(oldest));
606        assert!(!oldest.is_compatible_with(latest));
607    }
608
609    #[test]
610    fn test_serde() {
611        let v = ProtocolVersion::V2025_11_25;
612
613        // Serialize to string
614        let json = serde_json::to_string(&v).unwrap();
615        assert_eq!(json, "\"2025-11-25\"");
616
617        // Deserialize from string
618        let parsed: ProtocolVersion = serde_json::from_str("\"2024-11-05\"").unwrap();
619        assert_eq!(parsed, ProtocolVersion::V2024_11_05);
620    }
621}