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