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