Skip to main content

turbomcp_protocol/
versioning.rs

1//! # Protocol Versioning and Compatibility
2//!
3//! This module provides comprehensive protocol version management and compatibility
4//! checking for MCP implementations.
5//!
6//! ## Production Guidance
7//!
8//! **For new deployments, use `Version::latest()` (2025-11-25)** - the official stable
9//! MCP specification released on November 25, 2025.
10//!
11//! **For maximum backwards compatibility**, use `Version::stable()` (2025-06-18) which
12//! is widely supported by existing MCP clients including older versions of Claude Code.
13//!
14//! ```rust
15//! use turbomcp_protocol::versioning::Version;
16//!
17//! // Recommended: Use latest() for the official 2025-11-25 spec
18//! let version = Version::latest(); // 2025-11-25 (official release)
19//!
20//! // Legacy compatibility: Use stable() for older clients
21//! let version = Version::stable(); // 2025-06-18
22//! ```
23//!
24//! ## Supported Versions
25//!
26//! TurboMCP supports multiple MCP specification versions:
27//!
28//! | Version | Constant | Status |
29//! |---------|----------|--------|
30//! | **2025-11-25** | `Version::latest()` | **Current** - Official MCP spec (Tasks, Extensions, CIMD) |
31//! | **2025-06-18** | `Version::stable()` | Stable - Widely deployed, legacy client compatibility |
32//! | 2025-03-26 | - | Legacy support |
33//! | 2024-11-05 | - | Legacy support |
34//!
35//! ## Version Selection Guidelines
36//!
37//! - **New production servers**: Use `Version::latest()` for the official 2025-11-25 spec
38//!   with Tasks API, Extensions, and modern authorization features
39//! - **Legacy compatibility**: Use `Version::stable()` if you need to support older
40//!   clients that haven't updated to the 2025-11-25 spec
41//! - **Building clients**: Support multiple versions and negotiate with the server
42//!
43//! ## Version Negotiation
44//!
45//! The MCP protocol uses a client-request, server-decide negotiation model:
46//!
47//! ```rust
48//! use turbomcp_protocol::versioning::{Version, VersionManager};
49//!
50//! // Server setup - prefers stable for production compatibility
51//! let manager = VersionManager::with_default_versions();
52//!
53//! // Client requests the latest version
54//! let client_versions = vec![Version::latest(), Version::stable()];
55//!
56//! // Server negotiates (picks highest mutually supported version)
57//! let negotiated = manager.negotiate_version(&client_versions);
58//! assert_eq!(negotiated, Some(Version::latest()));
59//! ```
60//!
61//! ## Feature Flags vs Runtime Negotiation
62//!
63//! Most MCP 2025-11-25 features are now **always available** (no feature flag needed).
64//! Only experimental features like the Tasks API require a feature flag:
65//! ```toml
66//! # Enable experimental Tasks API
67//! turbomcp-protocol = { version = "3.0", features = ["experimental-tasks"] }
68//! ```
69//!
70//! **Runtime negotiation** determines what protocol version is used for a session:
71//! ```rust,ignore
72//! use turbomcp_protocol::{InitializeRequest, InitializeResult};
73//!
74//! // Client asks for latest spec
75//! let request = InitializeRequest { protocol_version: "2025-11-25".into(), ..Default::default() };
76//!
77//! // Server responds with actual version (may downgrade for compatibility)
78//! let response = InitializeResult { protocol_version: "2025-06-18".into(), ..Default::default() };
79//! ```
80//!
81//! **Key Principle:** The 2025-11-25 spec is the current official release. Use it for new
82//! deployments. The 2025-06-18 version remains available for backwards compatibility with
83//! older clients. Always negotiate at runtime based on what the other party supports.
84
85use serde::{Deserialize, Serialize};
86use std::cmp::Ordering;
87use std::fmt;
88use std::str::FromStr;
89
90/// Version manager for handling protocol versions
91#[derive(Debug, Clone)]
92pub struct VersionManager {
93    /// Supported versions in order of preference
94    supported_versions: Vec<Version>,
95    /// Current protocol version
96    current_version: Version,
97}
98
99/// Semantic version representation
100#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101pub struct Version {
102    /// Year component
103    pub year: u16,
104    /// Month component  
105    pub month: u8,
106    /// Day component
107    pub day: u8,
108}
109
110/// Version compatibility result
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub enum VersionCompatibility {
113    /// Versions are fully compatible
114    Compatible,
115    /// Versions are compatible with warnings
116    CompatibleWithWarnings(Vec<String>),
117    /// Versions are incompatible
118    Incompatible(String),
119}
120
121/// Version requirement specification
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub enum VersionRequirement {
124    /// Exact version match
125    Exact(Version),
126    /// Minimum version required
127    Minimum(Version),
128    /// Maximum version supported
129    Maximum(Version),
130    /// Version range (inclusive)
131    Range(Version, Version),
132    /// Any version from the list
133    Any(Vec<Version>),
134}
135
136impl Version {
137    /// Create a new version
138    ///
139    /// # Errors
140    ///
141    /// Returns [`VersionError::InvalidMonth`] if month is not in range 1-12.
142    /// Returns [`VersionError::InvalidDay`] if day is invalid for the given month.
143    pub fn new(year: u16, month: u8, day: u8) -> Result<Self, VersionError> {
144        if !(1..=12).contains(&month) {
145            return Err(VersionError::InvalidMonth(month.to_string()));
146        }
147
148        if !(1..=31).contains(&day) {
149            return Err(VersionError::InvalidDay(day.to_string()));
150        }
151
152        // Basic month/day validation
153        if month == 2 && day > 29 {
154            return Err(VersionError::InvalidDay(format!(
155                "{} (invalid for February)",
156                day
157            )));
158        }
159
160        if matches!(month, 4 | 6 | 9 | 11) && day > 30 {
161            return Err(VersionError::InvalidDay(format!(
162                "{} (month {} only has 30 days)",
163                day, month
164            )));
165        }
166
167        Ok(Self { year, month, day })
168    }
169
170    /// Get the stable MCP protocol version (2025-06-18)
171    ///
172    /// This version is widely compatible with MCP clients including Claude Code.
173    /// Use this as the default for maximum compatibility.
174    pub fn stable() -> Self {
175        Self {
176            year: 2025,
177            month: 6,
178            day: 18,
179        }
180    }
181
182    /// Get the latest MCP protocol version (2025-11-25)
183    ///
184    /// This is the current official MCP specification version.
185    /// Note: Some clients like Claude Code may not support this version yet.
186    pub fn latest() -> Self {
187        Self {
188            year: 2025,
189            month: 11,
190            day: 25,
191        }
192    }
193
194    /// Check if this version is newer than another
195    pub fn is_newer_than(&self, other: &Version) -> bool {
196        self > other
197    }
198
199    /// Check if this version is older than another
200    pub fn is_older_than(&self, other: &Version) -> bool {
201        self < other
202    }
203
204    /// Check if this version is compatible with another
205    pub fn is_compatible_with(&self, other: &Version) -> bool {
206        // For MCP, we consider versions compatible if they're the same
207        // or if the difference is minor (same year)
208        self.year == other.year
209    }
210
211    /// Get version as a date string (YYYY-MM-DD)
212    pub fn to_date_string(&self) -> String {
213        format!("{:04}-{:02}-{:02}", self.year, self.month, self.day)
214    }
215
216    /// Parse version from date string
217    ///
218    /// # Errors
219    ///
220    /// Returns [`VersionError`] if the string is not in `YYYY-MM-DD` format
221    /// or contains invalid date components.
222    pub fn from_date_string(s: &str) -> Result<Self, VersionError> {
223        s.parse()
224    }
225
226    /// Get all known MCP versions
227    pub fn known_versions() -> Vec<Version> {
228        vec![
229            Version::new(2025, 11, 25).unwrap(), // Latest official spec
230            Version::new(2025, 6, 18).unwrap(),  // Stable (Claude Code compatible)
231            Version::new(2025, 3, 26).unwrap(),  // Previous
232            Version::new(2024, 11, 5).unwrap(),  // Legacy
233        ]
234    }
235}
236
237impl fmt::Display for Version {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        write!(f, "{}", self.to_date_string())
240    }
241}
242
243impl FromStr for Version {
244    type Err = VersionError;
245
246    fn from_str(s: &str) -> Result<Self, Self::Err> {
247        let parts: Vec<&str> = s.split('-').collect();
248
249        if parts.len() != 3 {
250            return Err(VersionError::InvalidFormat(s.to_string()));
251        }
252
253        let year = parts[0]
254            .parse::<u16>()
255            .map_err(|_| VersionError::InvalidYear(parts[0].to_string()))?;
256
257        let month = parts[1]
258            .parse::<u8>()
259            .map_err(|_| VersionError::InvalidMonth(parts[1].to_string()))?;
260
261        let day = parts[2]
262            .parse::<u8>()
263            .map_err(|_| VersionError::InvalidDay(parts[2].to_string()))?;
264
265        Self::new(year, month, day)
266    }
267}
268
269impl PartialOrd for Version {
270    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
271        Some(self.cmp(other))
272    }
273}
274
275impl Ord for Version {
276    fn cmp(&self, other: &Self) -> Ordering {
277        (self.year, self.month, self.day).cmp(&(other.year, other.month, other.day))
278    }
279}
280
281impl VersionManager {
282    /// Create a new version manager
283    ///
284    /// # Errors
285    ///
286    /// Returns [`VersionError::NoSupportedVersions`] if the provided vector is empty.
287    pub fn new(supported_versions: Vec<Version>) -> Result<Self, VersionError> {
288        if supported_versions.is_empty() {
289            return Err(VersionError::NoSupportedVersions);
290        }
291
292        let mut versions = supported_versions;
293        versions.sort_by(|a, b| b.cmp(a)); // Sort newest first
294
295        let current_version = versions[0].clone();
296
297        Ok(Self {
298            supported_versions: versions,
299            current_version,
300        })
301    }
302
303    /// Create a version manager with default MCP versions
304    pub fn with_default_versions() -> Self {
305        Self::new(Version::known_versions()).unwrap()
306    }
307    /// Get the current version
308    pub fn current_version(&self) -> &Version {
309        &self.current_version
310    }
311
312    /// Get all supported versions
313    pub fn supported_versions(&self) -> &[Version] {
314        &self.supported_versions
315    }
316
317    /// Check if a version is supported
318    pub fn is_version_supported(&self, version: &Version) -> bool {
319        self.supported_versions.contains(version)
320    }
321
322    /// Find the best compatible version for a client request
323    pub fn negotiate_version(&self, client_versions: &[Version]) -> Option<Version> {
324        // Find the newest version that both client and server support
325        for server_version in &self.supported_versions {
326            if client_versions.contains(server_version) {
327                return Some(server_version.clone());
328            }
329        }
330
331        None
332    }
333
334    /// Check compatibility between two versions
335    pub fn check_compatibility(
336        &self,
337        client_version: &Version,
338        server_version: &Version,
339    ) -> VersionCompatibility {
340        if client_version == server_version {
341            return VersionCompatibility::Compatible;
342        }
343
344        // Check if versions are in the same year (considered compatible)
345        if client_version.year == server_version.year {
346            let warning = format!(
347                "Version mismatch but compatible: client={client_version}, server={server_version}"
348            );
349            return VersionCompatibility::CompatibleWithWarnings(vec![warning]);
350        }
351
352        // Major version difference
353        let reason =
354            format!("Incompatible versions: client={client_version}, server={server_version}");
355        VersionCompatibility::Incompatible(reason)
356    }
357
358    /// Get the minimum supported version
359    pub fn minimum_version(&self) -> &Version {
360        // SAFETY: Constructor ensures non-empty via Result<T, VersionError::NoSupportedVersions>
361        self.supported_versions
362            .last()
363            .expect("BUG: VersionManager has no versions (constructor should prevent this)")
364    }
365
366    /// Get the maximum supported version  
367    pub fn maximum_version(&self) -> &Version {
368        &self.supported_versions[0] // First because sorted newest first
369    }
370
371    /// Check if a version requirement is satisfied
372    pub fn satisfies_requirement(
373        &self,
374        version: &Version,
375        requirement: &VersionRequirement,
376    ) -> bool {
377        match requirement {
378            VersionRequirement::Exact(required) => version == required,
379            VersionRequirement::Minimum(min) => version >= min,
380            VersionRequirement::Maximum(max) => version <= max,
381            VersionRequirement::Range(min, max) => version >= min && version <= max,
382            VersionRequirement::Any(versions) => versions.contains(version),
383        }
384    }
385}
386
387impl Default for VersionManager {
388    fn default() -> Self {
389        Self::with_default_versions()
390    }
391}
392
393impl VersionRequirement {
394    /// Create an exact version requirement
395    pub fn exact(version: Version) -> Self {
396        Self::Exact(version)
397    }
398
399    /// Create a minimum version requirement
400    pub fn minimum(version: Version) -> Self {
401        Self::Minimum(version)
402    }
403
404    /// Create a maximum version requirement
405    pub fn maximum(version: Version) -> Self {
406        Self::Maximum(version)
407    }
408
409    /// Create a version range requirement
410    ///
411    /// # Errors
412    ///
413    /// Returns [`VersionError::InvalidRange`] if `min` is greater than `max`.
414    pub fn range(min: Version, max: Version) -> Result<Self, VersionError> {
415        if min > max {
416            return Err(VersionError::InvalidRange(min, max));
417        }
418        Ok(Self::Range(min, max))
419    }
420
421    /// Create an "any of" requirement
422    ///
423    /// # Errors
424    ///
425    /// Returns [`VersionError::EmptyVersionList`] if the provided vector is empty.
426    pub fn any(versions: Vec<Version>) -> Result<Self, VersionError> {
427        if versions.is_empty() {
428            return Err(VersionError::EmptyVersionList);
429        }
430        Ok(Self::Any(versions))
431    }
432
433    /// Check if a version satisfies this requirement
434    pub fn is_satisfied_by(&self, version: &Version) -> bool {
435        match self {
436            Self::Exact(required) => version == required,
437            Self::Minimum(min) => version >= min,
438            Self::Maximum(max) => version <= max,
439            Self::Range(min, max) => version >= min && version <= max,
440            Self::Any(versions) => versions.contains(version),
441        }
442    }
443}
444
445/// Version-related errors
446#[derive(Debug, Clone, thiserror::Error)]
447pub enum VersionError {
448    /// Invalid version format
449    #[error("Invalid version format: {0}")]
450    InvalidFormat(String),
451    /// Invalid year
452    #[error("Invalid year: {0}")]
453    InvalidYear(String),
454    /// Invalid month
455    #[error("Invalid month: {0} (must be 1-12)")]
456    InvalidMonth(String),
457    /// Invalid day
458    #[error("Invalid day: {0} (must be 1-31)")]
459    InvalidDay(String),
460    /// No supported versions
461    #[error("No supported versions provided")]
462    NoSupportedVersions,
463    /// Invalid version range
464    #[error("Invalid version range: {0} > {1}")]
465    InvalidRange(Version, Version),
466    /// Empty version list
467    #[error("Empty version list")]
468    EmptyVersionList,
469}
470
471/// Utility functions for version management
472pub mod utils {
473    use super::*;
474
475    /// Parse multiple versions from strings
476    ///
477    /// # Errors
478    ///
479    /// Returns [`VersionError`] if any version string cannot be parsed.
480    pub fn parse_versions(version_strings: &[&str]) -> Result<Vec<Version>, VersionError> {
481        version_strings.iter().map(|s| s.parse()).collect()
482    }
483
484    /// Find the newest version in a list
485    pub fn newest_version(versions: &[Version]) -> Option<&Version> {
486        versions.iter().max()
487    }
488
489    /// Find the oldest version in a list
490    pub fn oldest_version(versions: &[Version]) -> Option<&Version> {
491        versions.iter().min()
492    }
493
494    /// Check if all versions in a list are compatible with each other
495    pub fn are_all_compatible(versions: &[Version]) -> bool {
496        if versions.len() < 2 {
497            return true;
498        }
499
500        let first = &versions[0];
501        versions.iter().all(|v| first.is_compatible_with(v))
502    }
503
504    /// Get a human-readable description of version compatibility
505    pub fn compatibility_description(compatibility: &VersionCompatibility) -> String {
506        match compatibility {
507            VersionCompatibility::Compatible => "Fully compatible".to_string(),
508            VersionCompatibility::CompatibleWithWarnings(warnings) => {
509                format!("Compatible with warnings: {}", warnings.join(", "))
510            }
511            VersionCompatibility::Incompatible(reason) => {
512                format!("Incompatible: {reason}")
513            }
514        }
515    }
516}
517
518#[cfg(test)]
519mod tests {
520    use super::*;
521    use proptest::prelude::*;
522
523    #[test]
524    fn test_version_creation() {
525        let version = Version::new(2025, 6, 18).unwrap();
526        assert_eq!(version.year, 2025);
527        assert_eq!(version.month, 6);
528        assert_eq!(version.day, 18);
529
530        // Invalid month should fail
531        assert!(Version::new(2025, 13, 18).is_err());
532
533        // Invalid day should fail
534        assert!(Version::new(2025, 6, 32).is_err());
535    }
536
537    #[test]
538    fn test_version_parsing() {
539        let version: Version = "2025-06-18".parse().unwrap();
540        assert_eq!(version, Version::new(2025, 6, 18).unwrap());
541
542        // Invalid format should fail
543        assert!("2025/06/18".parse::<Version>().is_err());
544        assert!("invalid".parse::<Version>().is_err());
545    }
546
547    #[test]
548    fn test_version_comparison() {
549        let v1 = Version::new(2025, 6, 18).unwrap();
550        let v2 = Version::new(2024, 11, 5).unwrap();
551        let v3 = Version::new(2025, 6, 18).unwrap();
552
553        assert!(v1 > v2);
554        assert!(v1.is_newer_than(&v2));
555        assert!(v2.is_older_than(&v1));
556        assert_eq!(v1, v3);
557    }
558
559    #[test]
560    fn test_version_compatibility() {
561        let v1 = Version::new(2025, 6, 18).unwrap();
562        let v2 = Version::new(2025, 12, 1).unwrap(); // Same year
563        let v3 = Version::new(2024, 6, 18).unwrap(); // Different year
564
565        assert!(v1.is_compatible_with(&v2));
566        assert!(!v1.is_compatible_with(&v3));
567    }
568
569    #[test]
570    fn test_version_manager() {
571        let versions = vec![
572            Version::new(2025, 6, 18).unwrap(),
573            Version::new(2024, 11, 5).unwrap(),
574        ];
575
576        let manager = VersionManager::new(versions).unwrap();
577
578        assert_eq!(
579            manager.current_version(),
580            &Version::new(2025, 6, 18).unwrap()
581        );
582        assert!(manager.is_version_supported(&Version::new(2024, 11, 5).unwrap()));
583        assert!(!manager.is_version_supported(&Version::new(2023, 1, 1).unwrap()));
584    }
585
586    #[test]
587    fn test_version_negotiation() {
588        let manager = VersionManager::default();
589
590        let client_versions = vec![
591            Version::new(2024, 11, 5).unwrap(),
592            Version::new(2025, 6, 18).unwrap(),
593        ];
594
595        let negotiated = manager.negotiate_version(&client_versions);
596        assert_eq!(negotiated, Some(Version::new(2025, 6, 18).unwrap()));
597    }
598
599    #[test]
600    fn test_version_requirements() {
601        let version = Version::new(2025, 6, 18).unwrap();
602
603        let exact_req = VersionRequirement::exact(version.clone());
604        assert!(exact_req.is_satisfied_by(&version));
605
606        let min_req = VersionRequirement::minimum(Version::new(2024, 1, 1).unwrap());
607        assert!(min_req.is_satisfied_by(&version));
608
609        let max_req = VersionRequirement::maximum(Version::new(2024, 1, 1).unwrap());
610        assert!(!max_req.is_satisfied_by(&version));
611    }
612
613    #[test]
614    fn test_compatibility_checking() {
615        let manager = VersionManager::default();
616
617        let v1 = Version::new(2025, 6, 18).unwrap();
618        let v2 = Version::new(2025, 12, 1).unwrap();
619        let v3 = Version::new(2024, 1, 1).unwrap();
620
621        // Same year - compatible with warnings
622        let compat = manager.check_compatibility(&v1, &v2);
623        assert!(matches!(
624            compat,
625            VersionCompatibility::CompatibleWithWarnings(_)
626        ));
627
628        // Different year - incompatible
629        let compat = manager.check_compatibility(&v1, &v3);
630        assert!(matches!(compat, VersionCompatibility::Incompatible(_)));
631
632        // Exact match - compatible
633        let compat = manager.check_compatibility(&v1, &v1);
634        assert_eq!(compat, VersionCompatibility::Compatible);
635    }
636
637    #[test]
638    fn test_utils() {
639        let versions = utils::parse_versions(&["2025-06-18", "2024-11-05"]).unwrap();
640        assert_eq!(versions.len(), 2);
641
642        let newest = utils::newest_version(&versions);
643        assert_eq!(newest, Some(&Version::new(2025, 6, 18).unwrap()));
644
645        let oldest = utils::oldest_version(&versions);
646        assert_eq!(oldest, Some(&Version::new(2024, 11, 5).unwrap()));
647    }
648
649    // Property-based tests for comprehensive coverage
650    proptest! {
651        #[test]
652        fn test_version_parse_roundtrip(
653            year in 2020u16..2030u16,
654            month in 1u8..=12u8,
655            day in 1u8..=28u8, // Use 28 to avoid month-specific day validation
656        ) {
657            let version = Version::new(year, month, day)?;
658            let string = version.to_date_string();
659            let parsed = Version::from_date_string(&string)?;
660            prop_assert_eq!(version, parsed);
661        }
662
663        #[test]
664        fn test_version_comparison_transitive(
665            y1 in 2020u16..2030u16,
666            m1 in 1u8..=12u8,
667            d1 in 1u8..=28u8,
668            y2 in 2020u16..2030u16,
669            m2 in 1u8..=12u8,
670            d2 in 1u8..=28u8,
671            y3 in 2020u16..2030u16,
672            m3 in 1u8..=12u8,
673            d3 in 1u8..=28u8,
674        ) {
675            let v1 = Version::new(y1, m1, d1)?;
676            let v2 = Version::new(y2, m2, d2)?;
677            let v3 = Version::new(y3, m3, d3)?;
678
679            // Transitivity: if v1 < v2 and v2 < v3, then v1 < v3
680            if v1 < v2 && v2 < v3 {
681                prop_assert!(v1 < v3);
682            }
683        }
684
685        #[test]
686        fn test_version_compatibility_symmetric(
687            year in 2020u16..2030u16,
688            m1 in 1u8..=12u8,
689            d1 in 1u8..=28u8,
690            m2 in 1u8..=12u8,
691            d2 in 1u8..=28u8,
692        ) {
693            let v1 = Version::new(year, m1, d1)?;
694            let v2 = Version::new(year, m2, d2)?;
695
696            // Same-year versions should be compatible in both directions
697            prop_assert_eq!(v1.is_compatible_with(&v2), v2.is_compatible_with(&v1));
698        }
699
700        #[test]
701        fn test_invalid_month_rejected(
702            year in 2020u16..2030u16,
703            month in 13u8..=255u8,
704            day in 1u8..=28u8,
705        ) {
706            prop_assert!(Version::new(year, month, day).is_err());
707        }
708
709        #[test]
710        fn test_invalid_day_rejected(
711            year in 2020u16..2030u16,
712            month in 1u8..=12u8,
713            day in 32u8..=255u8,
714        ) {
715            prop_assert!(Version::new(year, month, day).is_err());
716        }
717    }
718}