mcp_host/protocol/
version.rs

1//! Protocol version negotiation
2//!
3//! MCP supports multiple protocol versions. This module handles version negotiation
4//! between client and server.
5
6/// Latest supported MCP protocol version
7pub const LATEST_PROTOCOL_VERSION: &str = "2025-11-25";
8
9// =============================================================================
10// CONST STRING MACRO - Copied from rust-sdk
11// =============================================================================
12
13/// Macro for creating compile-time constant string types
14///
15/// This creates a zero-sized type that serializes/deserializes to a specific string value.
16/// Useful for JSON schema type fields that must always be a specific constant.
17#[macro_export]
18macro_rules! const_string {
19    ($name:ident = $value:literal) => {
20        #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
21        pub struct $name;
22
23        impl $name {
24            pub const VALUE: &'static str = $value;
25
26            pub fn as_str(&self) -> &'static str {
27                Self::VALUE
28            }
29        }
30
31        impl serde::Serialize for $name {
32            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
33            where
34                S: serde::Serializer,
35            {
36                $value.serialize(serializer)
37            }
38        }
39
40        impl<'de> serde::Deserialize<'de> for $name {
41            fn deserialize<D>(deserializer: D) -> Result<$name, D::Error>
42            where
43                D: serde::Deserializer<'de>,
44            {
45                let s: String = serde::Deserialize::deserialize(deserializer)?;
46                if s == $value {
47                    Ok($name)
48                } else {
49                    Err(serde::de::Error::custom(format!(
50                        "expected const string value {:?}, but got {:?}",
51                        $value, s
52                    )))
53                }
54            }
55        }
56    };
57}
58
59// Re-export for use in submodules
60pub use const_string;
61
62/// JSON-RPC version (always "2.0")
63pub const JSON_RPC_VERSION: &str = "2.0";
64
65/// All supported protocol versions in order of preference
66///
67/// Order matters: first match wins during negotiation
68pub const SUPPORTED_PROTOCOL_VERSIONS: &[&str] = &[
69    "2025-11-25", // Latest with tasks, elicitation, icons
70    "2025-06-18", // Previous stable version
71];
72
73/// Default minimum client versions
74///
75/// Can be overridden by specific implementations
76pub const DEFAULT_MIN_CLIENT_VERSIONS: &[(&str, &str)] =
77    &[("claude-code", "2.0.56"), ("codex-mcp-client", "0.63.0")];
78
79/// Check if a protocol version is supported
80pub fn is_supported_protocol_version(version: &str) -> bool {
81    SUPPORTED_PROTOCOL_VERSIONS.contains(&version)
82}
83
84/// Negotiate protocol version between client and server
85///
86/// Returns the client's version if supported, otherwise returns an error with supported versions
87pub fn negotiate_protocol_version(client_version: &str) -> Result<String, Vec<String>> {
88    if is_supported_protocol_version(client_version) {
89        Ok(client_version.to_string())
90    } else {
91        Err(SUPPORTED_PROTOCOL_VERSIONS
92            .iter()
93            .map(|v| v.to_string())
94            .collect())
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_supported_version() {
104        assert!(is_supported_protocol_version("2025-11-25"));
105        assert!(is_supported_protocol_version("2025-06-18"));
106        assert!(!is_supported_protocol_version("2024-01-01"));
107    }
108
109    #[test]
110    fn test_negotiate_supported_version() {
111        assert_eq!(
112            negotiate_protocol_version("2025-11-25"),
113            Ok("2025-11-25".to_string())
114        );
115        assert_eq!(
116            negotiate_protocol_version("2025-06-18"),
117            Ok("2025-06-18".to_string())
118        );
119    }
120
121    #[test]
122    fn test_negotiate_unsupported_version() {
123        let result = negotiate_protocol_version("2024-01-01");
124        assert!(result.is_err());
125        let supported = result.unwrap_err();
126        assert_eq!(supported, vec!["2025-11-25", "2025-06-18"]);
127    }
128}