Skip to main content

turbomcp_protocol/
versioning.rs

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