turul_http_mcp_server/
protocol.rs

1//! MCP Protocol Version Detection and Features
2//!
3//! This module handles MCP protocol version detection from HTTP headers
4//! and provides feature flags for different protocol versions.
5
6/// Supported MCP protocol versions and features
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum McpProtocolVersion {
9    /// Original protocol without streamable HTTP (introduced 2024-11-05)
10    V2024_11_05,
11    /// Protocol including streamable HTTP (introduced 2025-03-26)
12    V2025_03_26,
13    /// Protocol with structured _meta, cursor, progressToken, and elicitation (introduced 2025-06-18)
14    V2025_06_18,
15}
16
17impl McpProtocolVersion {
18    /// Parses a version string like "2024-11-05", "2025-03-26", or "2025-06-18".
19    pub fn from_str(s: &str) -> Option<Self> {
20        match s {
21            "2024-11-05" => Some(McpProtocolVersion::V2024_11_05),
22            "2025-03-26" => Some(McpProtocolVersion::V2025_03_26),
23            "2025-06-18" => Some(McpProtocolVersion::V2025_06_18),
24            _ => None,
25        }
26    }
27
28    /// Converts this version to its string representation.
29    pub fn to_string(&self) -> &'static str {
30        match self {
31            McpProtocolVersion::V2024_11_05 => "2024-11-05",
32            McpProtocolVersion::V2025_03_26 => "2025-03-26",
33            McpProtocolVersion::V2025_06_18 => "2025-06-18",
34        }
35    }
36
37    /// Returns whether this version supports streamable HTTP (SSE).
38    pub fn supports_streamable_http(&self) -> bool {
39        matches!(self, McpProtocolVersion::V2025_03_26 | McpProtocolVersion::V2025_06_18)
40    }
41
42    /// Returns whether this version supports `_meta` fields in requests, responses, and notifications.
43    pub fn supports_meta_fields(&self) -> bool {
44        matches!(self, McpProtocolVersion::V2025_06_18)
45    }
46
47    /// Returns whether this version supports the use of `progressToken` and `cursor` in `_meta`.
48    pub fn supports_progress_and_cursor(&self) -> bool {
49        matches!(self, McpProtocolVersion::V2025_06_18)
50    }
51
52    /// Returns whether this version supports structured user elicitation via JSON Schema.
53    pub fn supports_elicitation(&self) -> bool {
54        matches!(self, McpProtocolVersion::V2025_06_18)
55    }
56
57    /// Get a list of supported features for this protocol version
58    pub fn supported_features(&self) -> Vec<&'static str> {
59        let mut features = vec![];
60        if self.supports_streamable_http() {
61            features.push("streamable-http");
62        }
63        if self.supports_meta_fields() {
64            features.push("_meta-fields");
65        }
66        if self.supports_progress_and_cursor() {
67            features.push("progress-token");
68            features.push("cursor");
69        }
70        if self.supports_elicitation() {
71            features.push("elicitation");
72        }
73        features
74    }
75
76    /// The latest protocol version this server implements.
77    pub const LATEST: McpProtocolVersion = McpProtocolVersion::V2025_06_18;
78}
79
80impl Default for McpProtocolVersion {
81    fn default() -> Self {
82        Self::LATEST
83    }
84}
85
86impl std::fmt::Display for McpProtocolVersion {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        write!(f, "{}", self.to_string())
89    }
90}
91
92/// Extract MCP protocol version from HTTP request headers
93pub fn extract_protocol_version(headers: &hyper::HeaderMap) -> McpProtocolVersion {
94    headers
95        .get("MCP-Protocol-Version")
96        .and_then(|h| h.to_str().ok())
97        .and_then(McpProtocolVersion::from_str)
98        .unwrap_or(McpProtocolVersion::LATEST)
99}
100
101/// Extract MCP session ID from HTTP request headers
102pub fn extract_session_id(headers: &hyper::HeaderMap) -> Option<String> {
103    headers
104        .get("Mcp-Session-Id")
105        .and_then(|h| h.to_str().ok())
106        .map(|s| s.to_string())
107}
108
109/// Extract Last-Event-ID from HTTP request headers for SSE resumability
110pub fn extract_last_event_id(headers: &hyper::HeaderMap) -> Option<u64> {
111    headers
112        .get("Last-Event-ID")
113        .and_then(|h| h.to_str().ok())
114        .and_then(|s| s.parse::<u64>().ok())
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use hyper::HeaderMap;
121
122    #[test]
123    fn test_version_parsing() {
124        assert_eq!(McpProtocolVersion::from_str("2024-11-05"), Some(McpProtocolVersion::V2024_11_05));
125        assert_eq!(McpProtocolVersion::from_str("2025-03-26"), Some(McpProtocolVersion::V2025_03_26));
126        assert_eq!(McpProtocolVersion::from_str("2025-06-18"), Some(McpProtocolVersion::V2025_06_18));
127        assert_eq!(McpProtocolVersion::from_str("invalid"), None);
128    }
129
130    #[test]
131    fn test_version_features() {
132        let v2024 = McpProtocolVersion::V2024_11_05;
133        assert!(!v2024.supports_streamable_http());
134        assert!(!v2024.supports_meta_fields());
135        assert!(!v2024.supports_elicitation());
136
137        let v2025_03 = McpProtocolVersion::V2025_03_26;
138        assert!(v2025_03.supports_streamable_http());
139        assert!(!v2025_03.supports_meta_fields());
140
141        let v2025_06 = McpProtocolVersion::V2025_06_18;
142        assert!(v2025_06.supports_streamable_http());
143        assert!(v2025_06.supports_meta_fields());
144        assert!(v2025_06.supports_elicitation());
145    }
146
147    #[test]
148    fn test_header_extraction() {
149        let mut headers = HeaderMap::new();
150        headers.insert("MCP-Protocol-Version", "2025-06-18".parse().unwrap());
151        headers.insert("Mcp-Session-Id", "test-session-123".parse().unwrap());
152
153        let version = extract_protocol_version(&headers);
154        assert_eq!(version, McpProtocolVersion::V2025_06_18);
155
156        let session_id = extract_session_id(&headers);
157        assert_eq!(session_id, Some("test-session-123".to_string()));
158    }
159}