Skip to main content

mcpkit_server/
state.rs

1//! Typestate connection management for MCP servers.
2//!
3//! This module implements the typestate pattern for managing
4//! connection lifecycle, ensuring compile-time correctness of
5//! state transitions.
6//!
7//! # Connection Lifecycle
8//!
9//! ```text
10//! Disconnected -> Connected -> Initializing -> Ready -> Closing
11//! ```
12//!
13//! Each state transition is enforced at compile time through
14//! different types, preventing invalid state transitions.
15
16use mcpkit_core::capability::{ClientCapabilities, ServerCapabilities, ServerInfo};
17use mcpkit_core::error::McpError;
18use mcpkit_core::protocol_version::ProtocolVersion;
19use std::marker::PhantomData;
20use std::sync::Arc;
21
22/// Connection state markers.
23///
24/// These types represent different states in the connection lifecycle.
25/// They contain no data and are used purely for type-level state tracking.
26pub mod markers {
27    // Note: The types in this module are also re-exported as `state::state::*`
28    // for backwards compatibility with v0.2.5.
29    /// Connection is disconnected (initial state).
30    #[derive(Debug, Clone, Copy)]
31    pub struct Disconnected;
32
33    /// Connection is established but not initialized.
34    #[derive(Debug, Clone, Copy)]
35    pub struct Connected;
36
37    /// Connection is in the initialization handshake.
38    #[derive(Debug, Clone, Copy)]
39    pub struct Initializing;
40
41    /// Connection is fully initialized and ready for requests.
42    #[derive(Debug, Clone, Copy)]
43    pub struct Ready;
44
45    /// Connection is closing down.
46    #[derive(Debug, Clone, Copy)]
47    pub struct Closing;
48}
49
50/// Backwards compatibility alias for the `markers` module.
51///
52/// This module was renamed from `state` to `markers` in v0.2.6.
53/// This alias is provided for backwards compatibility with v0.2.5.
54#[doc(hidden)]
55#[deprecated(since = "0.2.6", note = "Use `markers` module instead")]
56pub mod state {
57    pub use super::markers::*;
58}
59
60/// Internal connection data shared across states.
61#[doc(hidden)]
62#[derive(Debug)]
63pub struct ConnectionData {
64    /// Client capabilities (set after initialization).
65    pub client_capabilities: Option<ClientCapabilities>,
66    /// Server capabilities advertised.
67    pub server_capabilities: ServerCapabilities,
68    /// Server information.
69    pub server_info: ServerInfo,
70    /// Protocol version negotiated.
71    ///
72    /// Stored as a type-safe [`ProtocolVersion`] enum for feature detection.
73    pub protocol_version: Option<ProtocolVersion>,
74    /// Session ID if applicable.
75    pub session_id: Option<String>,
76}
77
78impl ConnectionData {
79    /// Create new connection data.
80    #[must_use]
81    pub const fn new(server_info: ServerInfo, server_capabilities: ServerCapabilities) -> Self {
82        Self {
83            client_capabilities: None,
84            server_capabilities,
85            server_info,
86            protocol_version: None,
87            session_id: None,
88        }
89    }
90}
91
92/// A typestate connection that tracks lifecycle state at the type level.
93///
94/// The state parameter `S` ensures that only valid operations are
95/// available for each connection state.
96///
97/// # Example
98///
99/// ```rust
100/// use mcpkit_server::state::{Connection, markers};
101/// use mcpkit_core::capability::{ServerInfo, ServerCapabilities};
102///
103/// // Start disconnected
104/// let conn: Connection<markers::Disconnected> = Connection::new(
105///     ServerInfo::new("my-server", "1.0.0"),
106///     ServerCapabilities::new().with_tools(),
107/// );
108///
109/// // The typestate pattern ensures compile-time safety:
110/// // - A Disconnected connection can only call connect()
111/// // - A Connected connection can only call initialize() or close()
112/// // - A Ready connection can access capabilities
113/// ```
114pub struct Connection<S> {
115    /// Shared connection data.
116    inner: Arc<ConnectionData>,
117    /// Phantom data to track state type.
118    _state: PhantomData<S>,
119}
120
121impl<S> Clone for Connection<S> {
122    fn clone(&self) -> Self {
123        Self {
124            inner: Arc::clone(&self.inner),
125            _state: PhantomData,
126        }
127    }
128}
129
130impl<S> std::fmt::Debug for Connection<S> {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        f.debug_struct("Connection")
133            .field("inner", &self.inner)
134            .field("state", &std::any::type_name::<S>())
135            .finish()
136    }
137}
138
139impl Connection<markers::Disconnected> {
140    /// Create a new disconnected connection.
141    #[must_use]
142    pub fn new(server_info: ServerInfo, server_capabilities: ServerCapabilities) -> Self {
143        Self {
144            inner: Arc::new(ConnectionData::new(server_info, server_capabilities)),
145            _state: PhantomData,
146        }
147    }
148
149    /// Connect to establish a transport connection.
150    ///
151    /// This transitions from `Disconnected` to `Connected` state.
152    pub async fn connect(self) -> Result<Connection<markers::Connected>, McpError> {
153        // In a real implementation, this would establish the transport
154        Ok(Connection {
155            inner: self.inner,
156            _state: PhantomData,
157        })
158    }
159}
160
161impl Connection<markers::Connected> {
162    /// Start the initialization handshake.
163    ///
164    /// This transitions from `Connected` to `Initializing` state.
165    pub async fn initialize(
166        self,
167        _protocol_version: ProtocolVersion,
168    ) -> Result<Connection<markers::Initializing>, McpError> {
169        // In a real implementation, this would send the initialize request
170        Ok(Connection {
171            inner: self.inner,
172            _state: PhantomData,
173        })
174    }
175
176    /// Close the connection before initialization.
177    pub async fn close(self) -> Result<(), McpError> {
178        // Clean up resources
179        Ok(())
180    }
181}
182
183impl Connection<markers::Initializing> {
184    /// Complete the initialization handshake.
185    ///
186    /// This transitions from `Initializing` to `Ready` state.
187    pub async fn complete(
188        self,
189        client_capabilities: ClientCapabilities,
190        protocol_version: ProtocolVersion,
191    ) -> Result<Connection<markers::Ready>, McpError> {
192        // Update the connection data with negotiated values
193        // In a real implementation, we'd use interior mutability
194        let mut data = ConnectionData::new(
195            self.inner.server_info.clone(),
196            self.inner.server_capabilities.clone(),
197        );
198        data.client_capabilities = Some(client_capabilities);
199        data.protocol_version = Some(protocol_version);
200
201        Ok(Connection {
202            inner: Arc::new(data),
203            _state: PhantomData,
204        })
205    }
206
207    /// Abort initialization.
208    pub async fn abort(self) -> Result<Connection<markers::Disconnected>, McpError> {
209        Ok(Connection {
210            inner: self.inner,
211            _state: PhantomData,
212        })
213    }
214}
215
216impl Connection<markers::Ready> {
217    /// Get the client capabilities.
218    ///
219    /// # Panics
220    ///
221    /// This should never panic if the connection was properly initialized
222    /// through the typestate transitions. Use `try_client_capabilities()`
223    /// for a fallible version.
224    #[must_use]
225    pub fn client_capabilities(&self) -> &ClientCapabilities {
226        self.inner
227            .client_capabilities
228            .as_ref()
229            .expect("Ready connection must have client capabilities")
230    }
231
232    /// Try to get the client capabilities.
233    ///
234    /// Returns `None` if capabilities were not set (should not happen in normal use).
235    #[must_use]
236    pub fn try_client_capabilities(&self) -> Option<&ClientCapabilities> {
237        self.inner.client_capabilities.as_ref()
238    }
239
240    /// Get the server capabilities.
241    #[must_use]
242    pub fn server_capabilities(&self) -> &ServerCapabilities {
243        &self.inner.server_capabilities
244    }
245
246    /// Get the server info.
247    #[must_use]
248    pub fn server_info(&self) -> &ServerInfo {
249        &self.inner.server_info
250    }
251
252    /// Get the negotiated protocol version.
253    ///
254    /// # Panics
255    ///
256    /// This should never panic if the connection was properly initialized
257    /// through the typestate transitions. Use `try_protocol_version()`
258    /// for a fallible version.
259    #[must_use]
260    pub fn protocol_version(&self) -> ProtocolVersion {
261        self.inner
262            .protocol_version
263            .expect("Ready connection must have protocol version")
264    }
265
266    /// Try to get the negotiated protocol version.
267    ///
268    /// Returns `None` if version was not set (should not happen in normal use).
269    #[must_use]
270    pub fn try_protocol_version(&self) -> Option<ProtocolVersion> {
271        self.inner.protocol_version
272    }
273
274    /// Start graceful shutdown.
275    ///
276    /// This transitions from `Ready` to `Closing` state.
277    pub async fn shutdown(self) -> Result<Connection<markers::Closing>, McpError> {
278        Ok(Connection {
279            inner: self.inner,
280            _state: PhantomData,
281        })
282    }
283}
284
285impl Connection<markers::Closing> {
286    /// Complete the shutdown and disconnect.
287    pub async fn disconnect(self) -> Result<(), McpError> {
288        // Clean up resources
289        Ok(())
290    }
291}
292
293/// A state machine wrapper for connections that allows runtime state tracking.
294///
295/// This provides an alternative to the pure typestate approach when
296/// runtime state inspection is needed.
297#[derive(Debug)]
298pub enum ConnectionState {
299    /// Not connected.
300    Disconnected(Connection<markers::Disconnected>),
301    /// Connected but not initialized.
302    Connected(Connection<markers::Connected>),
303    /// In initialization handshake.
304    Initializing(Connection<markers::Initializing>),
305    /// Ready for requests.
306    Ready(Connection<markers::Ready>),
307    /// Closing down.
308    Closing(Connection<markers::Closing>),
309}
310
311impl ConnectionState {
312    /// Create a new disconnected connection state.
313    #[must_use]
314    pub fn new(server_info: ServerInfo, server_capabilities: ServerCapabilities) -> Self {
315        Self::Disconnected(Connection::new(server_info, server_capabilities))
316    }
317
318    /// Check if the connection is ready for requests.
319    #[must_use]
320    pub const fn is_ready(&self) -> bool {
321        matches!(self, Self::Ready(_))
322    }
323
324    /// Check if the connection is disconnected.
325    #[must_use]
326    pub const fn is_disconnected(&self) -> bool {
327        matches!(self, Self::Disconnected(_))
328    }
329
330    /// Get the current state name.
331    #[must_use]
332    pub const fn state_name(&self) -> &'static str {
333        match self {
334            Self::Disconnected(_) => "Disconnected",
335            Self::Connected(_) => "Connected",
336            Self::Initializing(_) => "Initializing",
337            Self::Ready(_) => "Ready",
338            Self::Closing(_) => "Closing",
339        }
340    }
341}
342
343/// Transition events for connection state changes.
344#[derive(Debug, Clone)]
345pub enum ConnectionEvent {
346    /// Connection established.
347    Connected,
348    /// Initialization started.
349    InitializeStarted,
350    /// Initialization completed successfully.
351    InitializeCompleted {
352        /// Negotiated protocol version.
353        protocol_version: ProtocolVersion,
354    },
355    /// Initialization failed.
356    InitializeFailed {
357        /// Error message.
358        error: String,
359    },
360    /// Shutdown requested.
361    ShutdownRequested,
362    /// Connection closed.
363    Disconnected,
364}
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369    use mcpkit_core::capability::{ServerCapabilities, ServerInfo};
370    use mcpkit_core::protocol_version::ProtocolVersion;
371
372    #[test]
373    fn test_connection_creation() {
374        let info = ServerInfo::new("test", "1.0.0");
375        let caps = ServerCapabilities::default();
376        let conn: Connection<markers::Disconnected> = Connection::new(info, caps);
377
378        assert!(std::any::type_name_of_val(&conn._state).contains("Disconnected"));
379    }
380
381    #[tokio::test]
382    async fn test_connection_lifecycle() -> Result<(), Box<dyn std::error::Error>> {
383        let info = ServerInfo::new("test", "1.0.0");
384        let caps = ServerCapabilities::default();
385
386        // Start disconnected
387        let conn = Connection::new(info, caps);
388
389        // Connect
390        let conn = conn.connect().await?;
391
392        // Initialize
393        let conn = conn.initialize(ProtocolVersion::V2025_11_25).await?;
394
395        // Complete
396        let conn = conn
397            .complete(ClientCapabilities::default(), ProtocolVersion::V2025_11_25)
398            .await?;
399
400        // Verify ready state
401        assert_eq!(conn.protocol_version(), ProtocolVersion::V2025_11_25);
402
403        // Shutdown
404        let conn = conn.shutdown().await?;
405
406        // Disconnect
407        conn.disconnect().await?;
408
409        Ok(())
410    }
411
412    #[test]
413    fn test_connection_state_enum() {
414        let info = ServerInfo::new("test", "1.0.0");
415        let caps = ServerCapabilities::default();
416
417        let state = ConnectionState::new(info, caps);
418        assert!(state.is_disconnected());
419        assert!(!state.is_ready());
420        assert_eq!(state.state_name(), "Disconnected");
421    }
422}