Skip to main content

ultrafast_mcp_core/protocol/
lifecycle.rs

1// Version negotiation moved to version module
2use crate::types::*;
3use crate::types::{ClientCapabilities, ServerCapabilities};
4use serde::{Deserialize, Serialize};
5
6/// MCP connection lifecycle phases
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum LifecyclePhase {
9    Uninitialized,
10    Initializing,
11    Initialized,
12    Operating,
13    ShuttingDown,
14    Shutdown,
15}
16
17/// Initialize request sent by client to server
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct InitializeRequest {
20    /// Protocol version (e.g., "2025-06-18")
21    #[serde(rename = "protocolVersion")]
22    pub protocol_version: String,
23
24    /// Client capabilities
25    pub capabilities: ClientCapabilities,
26
27    /// Information about the client
28    #[serde(rename = "clientInfo")]
29    pub client_info: ClientInfo,
30}
31
32impl InitializeRequest {
33    /// Validate the protocol version format
34    pub fn validate_protocol_version(&self) -> Result<(), crate::error::ProtocolError> {
35        // Check if version follows YYYY-MM-DD format
36        if !self
37            .protocol_version
38            .chars()
39            .all(|c| c.is_ascii_digit() || c == '-')
40        {
41            return Err(crate::error::ProtocolError::InvalidVersion(
42                "Protocol version must contain only digits and hyphens".to_string(),
43            ));
44        }
45
46        // Check if version has correct format (YYYY-MM-DD)
47        let parts: Vec<&str> = self.protocol_version.split('-').collect();
48        if parts.len() != 3 {
49            return Err(crate::error::ProtocolError::InvalidVersion(
50                "Protocol version must be in YYYY-MM-DD format".to_string(),
51            ));
52        }
53
54        // Validate year, month, day
55        if parts[0].len() != 4 || parts[1].len() != 2 || parts[2].len() != 2 {
56            return Err(crate::error::ProtocolError::InvalidVersion(
57                "Protocol version must be in YYYY-MM-DD format".to_string(),
58            ));
59        }
60
61        // Validate ranges
62        if let (Ok(year), Ok(month), Ok(day)) = (
63            parts[0].parse::<u16>(),
64            parts[1].parse::<u8>(),
65            parts[2].parse::<u8>(),
66        ) {
67            if !(2020..=2030).contains(&year) {
68                return Err(crate::error::ProtocolError::InvalidVersion(
69                    "Year must be between 2020 and 2030".to_string(),
70                ));
71            }
72            if !(1..=12).contains(&month) {
73                return Err(crate::error::ProtocolError::InvalidVersion(
74                    "Month must be between 1 and 12".to_string(),
75                ));
76            }
77            if !(1..=31).contains(&day) {
78                return Err(crate::error::ProtocolError::InvalidVersion(
79                    "Day must be between 1 and 31".to_string(),
80                ));
81            }
82        } else {
83            return Err(crate::error::ProtocolError::InvalidVersion(
84                "Protocol version components must be valid numbers".to_string(),
85            ));
86        }
87
88        // Validate supported versions
89        let supported_versions = ["2025-06-18", "2025-03-26", "2024-11-05"];
90        if !supported_versions.contains(&self.protocol_version.as_str()) {
91            return Err(crate::error::ProtocolError::InvalidVersion(format!(
92                "Unsupported protocol version: {}",
93                self.protocol_version
94            )));
95        }
96
97        Ok(())
98    }
99}
100
101/// Initialize response sent by server to client
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct InitializeResponse {
104    /// Protocol version that will be used
105    #[serde(rename = "protocolVersion")]
106    pub protocol_version: String,
107
108    /// Server capabilities
109    pub capabilities: ServerCapabilities,
110
111    /// Information about the server
112    #[serde(rename = "serverInfo")]
113    pub server_info: ServerInfo,
114
115    /// Optional instructions for the client
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub instructions: Option<String>,
118}
119
120/// Initialized notification sent by client after receiving initialize response
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct InitializedNotification {
123    // This is just a marker notification - no parameters needed
124}
125
126/// Shutdown request (can be sent by either client or server)
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct ShutdownRequest {
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub reason: Option<String>,
131}
132
133/// Trait for lifecycle management
134#[async_trait::async_trait]
135pub trait LifecycleManager {
136    /// Get current lifecycle phase
137    fn phase(&self) -> LifecyclePhase;
138
139    /// Handle initialize request
140    async fn initialize(
141        &mut self,
142        request: InitializeRequest,
143    ) -> Result<InitializeResponse, crate::error::MCPError>;
144
145    /// Handle initialized notification
146    async fn initialized(&mut self) -> Result<(), crate::error::MCPError>;
147
148    /// Handle shutdown request
149    async fn shutdown(&mut self, request: ShutdownRequest) -> Result<(), crate::error::MCPError>;
150
151    /// Check if operation is allowed in current phase
152    /// According to MCP 2025-06-18 specification, operations are allowed
153    /// once the phase is initialized (after initialize response)
154    fn can_operate(&self) -> bool {
155        matches!(
156            self.phase(),
157            LifecyclePhase::Initialized | LifecyclePhase::Operating
158        )
159    }
160}
161
162// Version negotiation utility moved to version::VersionNegotiator
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    // Version negotiation test moved to version module
169
170    #[test]
171    fn test_lifecycle_phases() {
172        assert_ne!(LifecyclePhase::Uninitialized, LifecyclePhase::Initialized);
173        assert_eq!(LifecyclePhase::Operating, LifecyclePhase::Operating);
174    }
175
176    #[test]
177    fn test_protocol_version_validation() {
178        // Valid versions
179        let valid_request = InitializeRequest {
180            protocol_version: "2025-06-18".to_string(),
181            capabilities: Default::default(),
182            client_info: Default::default(),
183        };
184        assert!(valid_request.validate_protocol_version().is_ok());
185
186        let valid_request2 = InitializeRequest {
187            protocol_version: "2024-11-05".to_string(),
188            capabilities: Default::default(),
189            client_info: Default::default(),
190        };
191        assert!(valid_request2.validate_protocol_version().is_ok());
192
193        // Invalid versions
194        let invalid_request = InitializeRequest {
195            protocol_version: "invalid".to_string(),
196            capabilities: Default::default(),
197            client_info: Default::default(),
198        };
199        assert!(invalid_request.validate_protocol_version().is_err());
200
201        let invalid_request2 = InitializeRequest {
202            protocol_version: "2025-6-18".to_string(), // Missing leading zero
203            capabilities: Default::default(),
204            client_info: Default::default(),
205        };
206        assert!(invalid_request2.validate_protocol_version().is_err());
207
208        let invalid_request3 = InitializeRequest {
209            protocol_version: "2025-06-18-extra".to_string(), // Too many parts
210            capabilities: Default::default(),
211            client_info: Default::default(),
212        };
213        assert!(invalid_request3.validate_protocol_version().is_err());
214
215        let invalid_request4 = InitializeRequest {
216            protocol_version: "2019-06-18".to_string(), // Year too early
217            capabilities: Default::default(),
218            client_info: Default::default(),
219        };
220        assert!(invalid_request4.validate_protocol_version().is_err());
221
222        let invalid_request5 = InitializeRequest {
223            protocol_version: "2031-06-18".to_string(), // Year too late
224            capabilities: Default::default(),
225            client_info: Default::default(),
226        };
227        assert!(invalid_request5.validate_protocol_version().is_err());
228    }
229}