turul_http_mcp_server/
protocol.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum McpProtocolVersion {
9 V2024_11_05,
11 V2025_03_26,
13 V2025_06_18,
15 V2025_11_25,
17}
18
19impl McpProtocolVersion {
20 pub fn parse_version(s: &str) -> Option<Self> {
22 match s {
23 "2024-11-05" => Some(McpProtocolVersion::V2024_11_05),
24 "2025-03-26" => Some(McpProtocolVersion::V2025_03_26),
25 "2025-06-18" => Some(McpProtocolVersion::V2025_06_18),
26 "2025-11-25" => Some(McpProtocolVersion::V2025_11_25),
27 _ => None,
28 }
29 }
30
31 pub fn to_string(&self) -> &'static str {
33 match self {
34 McpProtocolVersion::V2024_11_05 => "2024-11-05",
35 McpProtocolVersion::V2025_03_26 => "2025-03-26",
36 McpProtocolVersion::V2025_06_18 => "2025-06-18",
37 McpProtocolVersion::V2025_11_25 => "2025-11-25",
38 }
39 }
40
41 pub fn supports_streamable_http(&self) -> bool {
43 matches!(
44 self,
45 McpProtocolVersion::V2025_03_26
46 | McpProtocolVersion::V2025_06_18
47 | McpProtocolVersion::V2025_11_25
48 )
49 }
50
51 pub fn supports_meta_fields(&self) -> bool {
53 matches!(
54 self,
55 McpProtocolVersion::V2025_06_18 | McpProtocolVersion::V2025_11_25
56 )
57 }
58
59 pub fn supports_progress_and_cursor(&self) -> bool {
61 matches!(
62 self,
63 McpProtocolVersion::V2025_06_18 | McpProtocolVersion::V2025_11_25
64 )
65 }
66
67 pub fn supports_elicitation(&self) -> bool {
69 matches!(
70 self,
71 McpProtocolVersion::V2025_06_18 | McpProtocolVersion::V2025_11_25
72 )
73 }
74
75 pub fn supports_tasks(&self) -> bool {
77 matches!(self, McpProtocolVersion::V2025_11_25)
78 }
79
80 pub fn supports_icons(&self) -> bool {
82 matches!(self, McpProtocolVersion::V2025_11_25)
83 }
84
85 pub fn supported_features(&self) -> Vec<&'static str> {
87 let mut features = vec![];
88 if self.supports_streamable_http() {
89 features.push("streamable-http");
90 }
91 if self.supports_meta_fields() {
92 features.push("_meta-fields");
93 }
94 if self.supports_progress_and_cursor() {
95 features.push("progress-token");
96 features.push("cursor");
97 }
98 if self.supports_elicitation() {
99 features.push("elicitation");
100 }
101 if self.supports_tasks() {
102 features.push("tasks");
103 }
104 if self.supports_icons() {
105 features.push("icons");
106 }
107 features
108 }
109
110 pub const LATEST: McpProtocolVersion = McpProtocolVersion::V2025_11_25;
112}
113
114impl Default for McpProtocolVersion {
115 fn default() -> Self {
116 Self::LATEST
117 }
118}
119
120impl std::fmt::Display for McpProtocolVersion {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 write!(f, "{}", self.to_string())
123 }
124}
125
126pub fn extract_protocol_version(headers: &hyper::HeaderMap) -> McpProtocolVersion {
128 headers
129 .get("MCP-Protocol-Version")
130 .and_then(|h| h.to_str().ok())
131 .and_then(McpProtocolVersion::parse_version)
132 .unwrap_or(McpProtocolVersion::LATEST)
133}
134
135pub fn extract_session_id(headers: &hyper::HeaderMap) -> Option<String> {
137 headers
138 .get("Mcp-Session-Id")
139 .and_then(|h| h.to_str().ok())
140 .map(|s| s.to_string())
141}
142
143pub fn extract_last_event_id(headers: &hyper::HeaderMap) -> Option<u64> {
145 headers
146 .get("Last-Event-ID")
147 .and_then(|h| h.to_str().ok())
148 .and_then(|s| s.parse::<u64>().ok())
149}
150
151pub fn normalize_header_value(value: &str) -> String {
156 value.trim().to_ascii_lowercase()
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use hyper::HeaderMap;
163
164 #[test]
165 fn test_version_parsing() {
166 assert_eq!(
167 McpProtocolVersion::parse_version("2024-11-05"),
168 Some(McpProtocolVersion::V2024_11_05)
169 );
170 assert_eq!(
171 McpProtocolVersion::parse_version("2025-03-26"),
172 Some(McpProtocolVersion::V2025_03_26)
173 );
174 assert_eq!(
175 McpProtocolVersion::parse_version("2025-06-18"),
176 Some(McpProtocolVersion::V2025_06_18)
177 );
178 assert_eq!(
179 McpProtocolVersion::parse_version("2025-11-25"),
180 Some(McpProtocolVersion::V2025_11_25)
181 );
182 assert_eq!(McpProtocolVersion::parse_version("invalid"), None);
183 }
184
185 #[test]
186 fn test_version_features() {
187 let v2024 = McpProtocolVersion::V2024_11_05;
188 assert!(!v2024.supports_streamable_http());
189 assert!(!v2024.supports_meta_fields());
190 assert!(!v2024.supports_elicitation());
191 assert!(!v2024.supports_tasks());
192
193 let v2025_03 = McpProtocolVersion::V2025_03_26;
194 assert!(v2025_03.supports_streamable_http());
195 assert!(!v2025_03.supports_meta_fields());
196 assert!(!v2025_03.supports_tasks());
197
198 let v2025_06 = McpProtocolVersion::V2025_06_18;
199 assert!(v2025_06.supports_streamable_http());
200 assert!(v2025_06.supports_meta_fields());
201 assert!(v2025_06.supports_elicitation());
202 assert!(!v2025_06.supports_tasks());
203
204 let v2025_11 = McpProtocolVersion::V2025_11_25;
205 assert!(v2025_11.supports_streamable_http());
206 assert!(v2025_11.supports_meta_fields());
207 assert!(v2025_11.supports_elicitation());
208 assert!(v2025_11.supports_tasks());
209 assert!(v2025_11.supports_icons());
210 }
211
212 #[test]
213 fn test_normalize_header_value() {
214 assert_eq!(
215 normalize_header_value("application/json"),
216 "application/json"
217 );
218 assert_eq!(
219 normalize_header_value(" application/json "),
220 "application/json"
221 );
222 assert_eq!(
223 normalize_header_value("Application/JSON"),
224 "application/json"
225 );
226 assert_eq!(
227 normalize_header_value("Application/Json; Charset=UTF-8"),
228 "application/json; charset=utf-8"
229 );
230 assert_eq!(
231 normalize_header_value(" TEXT/EVENT-STREAM "),
232 "text/event-stream"
233 );
234 assert_eq!(normalize_header_value(""), "");
235 }
236
237 #[test]
238 fn test_header_extraction() {
239 let mut headers = HeaderMap::new();
240 headers.insert("MCP-Protocol-Version", "2025-11-25".parse().unwrap());
241 headers.insert("Mcp-Session-Id", "test-session-123".parse().unwrap());
242
243 let version = extract_protocol_version(&headers);
244 assert_eq!(version, McpProtocolVersion::V2025_11_25);
245
246 let session_id = extract_session_id(&headers);
247 assert_eq!(session_id, Some("test-session-123".to_string()));
248 }
249}