use serde::Deserialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Era {
Legacy,
Modern,
}
pub const SUPPORTED_PROTOCOL_VERSIONS: &[&str] = &[
"2026-07-28", "2025-11-25", "2025-06-18",
"2025-03-26",
"2024-11-05",
];
pub const FIRST_MODERN_VERSION: &str = "2026-07-28";
pub const LATEST_MODERN_VERSION: &str = "2026-07-28";
pub const LATEST_LEGACY_VERSION: &str = "2025-11-25";
pub const PROTOCOL_VERSION: &str = LATEST_LEGACY_VERSION;
pub const DEFAULT_NEGOTIATED_VERSION: &str = "2025-03-26";
pub const UNSUPPORTED_PROTOCOL_VERSION_CODE: i64 = -32022;
pub const HEADER_MISMATCH_CODE: i64 = -32020;
pub const META_NS: &str = "io.modelcontextprotocol/";
pub fn is_supported_version(v: &str) -> bool {
SUPPORTED_PROTOCOL_VERSIONS.contains(&v)
}
pub fn is_date_version(s: &str) -> bool {
let b = s.as_bytes();
b.len() == 10
&& b[4] == b'-'
&& b[7] == b'-'
&& b.iter()
.enumerate()
.all(|(i, &c)| i == 4 || i == 7 || c.is_ascii_digit())
}
pub fn era_of(version: &str) -> Era {
if is_date_version(version) && version >= FIRST_MODERN_VERSION {
Era::Modern
} else {
Era::Legacy
}
}
pub fn negotiate_version(server_version: &str) -> Option<String> {
if is_supported_version(server_version) {
return Some(server_version.to_string());
}
if is_date_version(server_version) && server_version > SUPPORTED_PROTOCOL_VERSIONS[0] {
return Some(server_version.to_string());
}
None
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct UnsupportedProtocolVersion {
#[serde(default)]
pub supported: Vec<String>,
#[serde(default)]
pub requested: Option<String>,
}
pub fn best_mutual_version(server_supported: &[String]) -> Option<String> {
SUPPORTED_PROTOCOL_VERSIONS
.iter()
.find(|&&ours| server_supported.iter().any(|s| s == ours))
.map(|v| v.to_string())
}
pub fn is_modern_error_code(code: i64) -> bool {
code == UNSUPPORTED_PROTOCOL_VERSION_CODE || code == HEADER_MISMATCH_CODE
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn eras_split_at_the_2026_boundary() {
assert_eq!(era_of("2024-11-05"), Era::Legacy);
assert_eq!(era_of("2025-11-25"), Era::Legacy);
assert_eq!(era_of("2026-07-28"), Era::Modern);
assert_eq!(era_of("2099-01-01"), Era::Modern);
assert_eq!(era_of("1.0.0"), Era::Legacy);
}
#[test]
fn era_latests_are_consistent() {
assert_eq!(era_of(LATEST_LEGACY_VERSION), Era::Legacy);
assert_eq!(era_of(LATEST_MODERN_VERSION), Era::Modern);
assert_eq!(FIRST_MODERN_VERSION, LATEST_MODERN_VERSION);
assert!(is_supported_version(LATEST_LEGACY_VERSION));
assert!(is_supported_version(LATEST_MODERN_VERSION));
let mut sorted = SUPPORTED_PROTOCOL_VERSIONS.to_vec();
sorted.sort_unstable();
sorted.reverse();
assert_eq!(sorted.as_slice(), SUPPORTED_PROTOCOL_VERSIONS);
}
#[test]
fn is_date_version_recognizes_the_shape() {
assert!(is_date_version("2025-11-25"));
assert!(is_date_version("2026-07-28"));
assert!(!is_date_version("2025-11-5"));
assert!(!is_date_version("2025/11/25"));
assert!(!is_date_version("1.0.0"));
}
#[test]
fn legacy_negotiate_adopts_known_and_newer_but_refuses_old_unknown() {
for v in SUPPORTED_PROTOCOL_VERSIONS {
assert_eq!(negotiate_version(v).as_deref(), Some(*v));
}
assert_eq!(
negotiate_version("2099-01-01").as_deref(),
Some("2099-01-01")
);
assert_eq!(negotiate_version("2020-01-01"), None);
assert_eq!(negotiate_version("1.0.0"), None);
}
#[test]
fn modern_best_mutual_picks_our_newest_common() {
let supported = vec!["2025-11-25".to_string(), "2026-07-28".to_string()];
assert_eq!(
best_mutual_version(&supported).as_deref(),
Some("2026-07-28")
);
let supported = vec!["2025-06-18".to_string()];
assert_eq!(
best_mutual_version(&supported).as_deref(),
Some("2025-06-18")
);
let supported = vec!["1900-01-01".to_string()];
assert_eq!(best_mutual_version(&supported), None);
}
#[test]
fn modern_error_codes_are_recognized() {
assert!(is_modern_error_code(UNSUPPORTED_PROTOCOL_VERSION_CODE));
assert!(is_modern_error_code(HEADER_MISMATCH_CODE));
assert!(!is_modern_error_code(-32601)); assert!(!is_modern_error_code(-32602)); assert!(!is_modern_error_code(-32000));
}
#[test]
fn unsupported_error_payload_parses() {
let data = serde_json::json!({
"supported": ["2026-07-28", "2025-11-25"],
"requested": "1900-01-01"
});
let p: UnsupportedProtocolVersion = serde_json::from_value(data).unwrap();
assert_eq!(p.supported, ["2026-07-28", "2025-11-25"]);
assert_eq!(p.requested.as_deref(), Some("1900-01-01"));
}
}