Skip to main content

tower_mcp/
protocol_support.rs

1//! Compile-time and runtime protocol-version policy.
2//!
3//! The types crate exposes every wire version it knows. This module answers a
4//! different question: which implementations were compiled into `tower-mcp`,
5//! and which subset should a particular transport advertise and accept?
6
7use std::collections::HashSet;
8
9#[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
10use crate::protocol::PROTOCOL_VERSION_2026_07_28;
11use crate::protocol::SUPPORTED_PROTOCOL_VERSIONS;
12
13/// Protocol versions compiled into this build, in preference order.
14///
15/// Enabling `protocol-2026-07-28` adds the released implementation.
16/// The former `stateless` feature remains a compatibility alias and produces
17/// the same compiled set.
18#[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
19pub const COMPILED_PROTOCOL_VERSIONS: &[&str] =
20    &[PROTOCOL_VERSION_2026_07_28, "2025-11-25", "2025-03-26"];
21
22/// Protocol versions compiled into this build, in preference order.
23#[cfg(not(any(feature = "protocol-2026-07-28", feature = "stateless")))]
24pub const COMPILED_PROTOCOL_VERSIONS: &[&str] = SUPPORTED_PROTOCOL_VERSIONS;
25
26/// Returns whether a protocol implementation is present in this build.
27pub fn is_protocol_version_compiled(version: &str) -> bool {
28    COMPILED_PROTOCOL_VERSIONS.contains(&version)
29}
30
31/// Error returned for an invalid runtime protocol configuration.
32#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
33pub enum ProtocolSupportError {
34    /// At least one protocol version must remain enabled.
35    #[error("at least one protocol version must be enabled")]
36    Empty,
37    /// The requested version was not compiled into this build.
38    #[error(
39        "protocol version `{version}` is not compiled into this build; compiled versions: {compiled:?}"
40    )]
41    NotCompiled {
42        /// Version requested by the application.
43        version: String,
44        /// Versions available in this build.
45        compiled: &'static [&'static str],
46    },
47    /// A version appeared more than once in the preference list.
48    #[error("protocol version `{0}` is configured more than once")]
49    Duplicate(String),
50}
51
52/// Exact, ordered protocol-version allow-list for one runtime component.
53///
54/// [`ProtocolSupport::default`] enables every version compiled into the crate.
55/// Use [`ProtocolSupport::try_new`] to narrow an individual server or client.
56/// The order is preserved and is used as the advertised preference order.
57///
58/// ```
59/// use tower_mcp::ProtocolSupport;
60///
61/// let support = ProtocolSupport::try_new(["2025-11-25"])?;
62/// assert!(support.contains("2025-11-25"));
63/// assert!(!support.contains("2025-03-26"));
64/// # Ok::<(), tower_mcp::ProtocolSupportError>(())
65/// ```
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct ProtocolSupport {
68    versions: Vec<String>,
69}
70
71impl ProtocolSupport {
72    /// Enable every protocol implementation compiled into this build.
73    pub fn compiled() -> Self {
74        Self {
75            versions: COMPILED_PROTOCOL_VERSIONS
76                .iter()
77                .map(|version| (*version).to_string())
78                .collect(),
79        }
80    }
81
82    /// Enable only the stable-by-default compatibility set.
83    ///
84    /// This narrows a build to the `initialize`-negotiable session protocols,
85    /// excluding 2026-07-28 even when its Cargo feature is compiled.
86    pub fn stable() -> Self {
87        Self {
88            versions: SUPPORTED_PROTOCOL_VERSIONS
89                .iter()
90                .map(|version| (*version).to_string())
91                .collect(),
92        }
93    }
94
95    /// Construct an exact runtime allow-list.
96    ///
97    /// Versions must be compiled into the crate, must not be duplicated, and
98    /// the list must not be empty.
99    pub fn try_new<I, S>(versions: I) -> Result<Self, ProtocolSupportError>
100    where
101        I: IntoIterator<Item = S>,
102        S: Into<String>,
103    {
104        let mut configured = Vec::new();
105        let mut seen = HashSet::new();
106
107        for version in versions {
108            let version = version.into();
109            if !is_protocol_version_compiled(&version) {
110                return Err(ProtocolSupportError::NotCompiled {
111                    version,
112                    compiled: COMPILED_PROTOCOL_VERSIONS,
113                });
114            }
115            if !seen.insert(version.clone()) {
116                return Err(ProtocolSupportError::Duplicate(version));
117            }
118            configured.push(version);
119        }
120
121        if configured.is_empty() {
122            return Err(ProtocolSupportError::Empty);
123        }
124
125        Ok(Self {
126            versions: configured,
127        })
128    }
129
130    /// Enabled versions in advertised preference order.
131    pub fn versions(&self) -> &[String] {
132        &self.versions
133    }
134
135    /// Whether this runtime component should accept a version.
136    pub fn contains(&self, version: &str) -> bool {
137        self.versions.iter().any(|candidate| candidate == version)
138    }
139
140    /// Most-preferred enabled version.
141    pub fn preferred(&self) -> &str {
142        // Construction and the two built-in policies guarantee non-empty.
143        &self.versions[0]
144    }
145}
146
147impl Default for ProtocolSupport {
148    fn default() -> Self {
149        Self::compiled()
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn stable_policy_excludes_opt_in_versions() {
159        let support = ProtocolSupport::stable();
160        assert_eq!(support.versions(), SUPPORTED_PROTOCOL_VERSIONS);
161        assert_eq!(support.preferred(), "2025-11-25");
162    }
163
164    #[test]
165    fn rejects_empty_duplicate_and_uncompiled_sets() {
166        assert_eq!(
167            ProtocolSupport::try_new(Vec::<String>::new()).unwrap_err(),
168            ProtocolSupportError::Empty
169        );
170        assert_eq!(
171            ProtocolSupport::try_new(["2025-11-25", "2025-11-25"]).unwrap_err(),
172            ProtocolSupportError::Duplicate("2025-11-25".to_string())
173        );
174        assert!(matches!(
175            ProtocolSupport::try_new(["2099-01-01"]).unwrap_err(),
176            ProtocolSupportError::NotCompiled { .. }
177        ));
178    }
179
180    #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
181    #[test]
182    fn opt_in_feature_adds_2026_implementation() {
183        assert!(is_protocol_version_compiled(PROTOCOL_VERSION_2026_07_28));
184        assert!(ProtocolSupport::compiled().contains(PROTOCOL_VERSION_2026_07_28));
185    }
186
187    #[cfg(not(any(feature = "protocol-2026-07-28", feature = "stateless")))]
188    #[test]
189    fn default_build_does_not_compile_2026_implementation() {
190        assert!(!is_protocol_version_compiled("2026-07-28"));
191    }
192}