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