use mcpkit_core::capability::{ClientCapabilities, ServerCapabilities, ServerInfo};
use mcpkit_core::error::McpError;
use mcpkit_core::protocol_version::ProtocolVersion;
use std::marker::PhantomData;
use std::sync::Arc;
pub mod markers {
#[derive(Debug, Clone, Copy)]
pub struct Disconnected;
#[derive(Debug, Clone, Copy)]
pub struct Connected;
#[derive(Debug, Clone, Copy)]
pub struct Initializing;
#[derive(Debug, Clone, Copy)]
pub struct Ready;
#[derive(Debug, Clone, Copy)]
pub struct Closing;
}
#[doc(hidden)]
#[deprecated(since = "0.2.6", note = "Use `markers` module instead")]
pub mod state {
pub use super::markers::*;
}
#[derive(Debug)]
pub struct ConnectionData {
pub client_capabilities: Option<ClientCapabilities>,
pub server_capabilities: ServerCapabilities,
pub server_info: ServerInfo,
pub protocol_version: Option<ProtocolVersion>,
pub session_id: Option<String>,
}
impl ConnectionData {
#[must_use]
pub const fn new(server_info: ServerInfo, server_capabilities: ServerCapabilities) -> Self {
Self {
client_capabilities: None,
server_capabilities,
server_info,
protocol_version: None,
session_id: None,
}
}
}
pub struct Connection<S> {
inner: Arc<ConnectionData>,
_state: PhantomData<S>,
}
impl<S> Clone for Connection<S> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
_state: PhantomData,
}
}
}
impl<S> std::fmt::Debug for Connection<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Connection")
.field("inner", &self.inner)
.field("state", &std::any::type_name::<S>())
.finish()
}
}
impl Connection<markers::Disconnected> {
#[must_use]
pub fn new(server_info: ServerInfo, server_capabilities: ServerCapabilities) -> Self {
Self {
inner: Arc::new(ConnectionData::new(server_info, server_capabilities)),
_state: PhantomData,
}
}
pub async fn connect(self) -> Result<Connection<markers::Connected>, McpError> {
Ok(Connection {
inner: self.inner,
_state: PhantomData,
})
}
}
impl Connection<markers::Connected> {
pub async fn initialize(
self,
_protocol_version: ProtocolVersion,
) -> Result<Connection<markers::Initializing>, McpError> {
Ok(Connection {
inner: self.inner,
_state: PhantomData,
})
}
pub async fn close(self) -> Result<(), McpError> {
Ok(())
}
}
impl Connection<markers::Initializing> {
pub async fn complete(
self,
client_capabilities: ClientCapabilities,
protocol_version: ProtocolVersion,
) -> Result<Connection<markers::Ready>, McpError> {
let mut data = ConnectionData::new(
self.inner.server_info.clone(),
self.inner.server_capabilities.clone(),
);
data.client_capabilities = Some(client_capabilities);
data.protocol_version = Some(protocol_version);
Ok(Connection {
inner: Arc::new(data),
_state: PhantomData,
})
}
pub async fn abort(self) -> Result<Connection<markers::Disconnected>, McpError> {
Ok(Connection {
inner: self.inner,
_state: PhantomData,
})
}
}
impl Connection<markers::Ready> {
#[must_use]
pub fn client_capabilities(&self) -> &ClientCapabilities {
self.inner
.client_capabilities
.as_ref()
.expect("Ready connection must have client capabilities")
}
#[must_use]
pub fn try_client_capabilities(&self) -> Option<&ClientCapabilities> {
self.inner.client_capabilities.as_ref()
}
#[must_use]
pub fn server_capabilities(&self) -> &ServerCapabilities {
&self.inner.server_capabilities
}
#[must_use]
pub fn server_info(&self) -> &ServerInfo {
&self.inner.server_info
}
#[must_use]
pub fn protocol_version(&self) -> ProtocolVersion {
self.inner
.protocol_version
.expect("Ready connection must have protocol version")
}
#[must_use]
pub fn try_protocol_version(&self) -> Option<ProtocolVersion> {
self.inner.protocol_version
}
pub async fn shutdown(self) -> Result<Connection<markers::Closing>, McpError> {
Ok(Connection {
inner: self.inner,
_state: PhantomData,
})
}
}
impl Connection<markers::Closing> {
pub async fn disconnect(self) -> Result<(), McpError> {
Ok(())
}
}
#[derive(Debug)]
pub enum ConnectionState {
Disconnected(Connection<markers::Disconnected>),
Connected(Connection<markers::Connected>),
Initializing(Connection<markers::Initializing>),
Ready(Connection<markers::Ready>),
Closing(Connection<markers::Closing>),
}
impl ConnectionState {
#[must_use]
pub fn new(server_info: ServerInfo, server_capabilities: ServerCapabilities) -> Self {
Self::Disconnected(Connection::new(server_info, server_capabilities))
}
#[must_use]
pub const fn is_ready(&self) -> bool {
matches!(self, Self::Ready(_))
}
#[must_use]
pub const fn is_disconnected(&self) -> bool {
matches!(self, Self::Disconnected(_))
}
#[must_use]
pub const fn state_name(&self) -> &'static str {
match self {
Self::Disconnected(_) => "Disconnected",
Self::Connected(_) => "Connected",
Self::Initializing(_) => "Initializing",
Self::Ready(_) => "Ready",
Self::Closing(_) => "Closing",
}
}
}
#[derive(Debug, Clone)]
pub enum ConnectionEvent {
Connected,
InitializeStarted,
InitializeCompleted {
protocol_version: ProtocolVersion,
},
InitializeFailed {
error: String,
},
ShutdownRequested,
Disconnected,
}
#[cfg(test)]
mod tests {
use super::*;
use mcpkit_core::capability::{ServerCapabilities, ServerInfo};
use mcpkit_core::protocol_version::ProtocolVersion;
#[test]
fn test_connection_creation() {
let info = ServerInfo::new("test", "1.0.0");
let caps = ServerCapabilities::default();
let conn: Connection<markers::Disconnected> = Connection::new(info, caps);
assert!(std::any::type_name_of_val(&conn._state).contains("Disconnected"));
}
#[tokio::test]
async fn test_connection_lifecycle() -> Result<(), Box<dyn std::error::Error>> {
let info = ServerInfo::new("test", "1.0.0");
let caps = ServerCapabilities::default();
let conn = Connection::new(info, caps);
let conn = conn.connect().await?;
let conn = conn.initialize(ProtocolVersion::V2025_11_25).await?;
let conn = conn
.complete(ClientCapabilities::default(), ProtocolVersion::V2025_11_25)
.await?;
assert_eq!(conn.protocol_version(), ProtocolVersion::V2025_11_25);
let conn = conn.shutdown().await?;
conn.disconnect().await?;
Ok(())
}
#[test]
fn test_connection_state_enum() {
let info = ServerInfo::new("test", "1.0.0");
let caps = ServerCapabilities::default();
let state = ConnectionState::new(info, caps);
assert!(state.is_disconnected());
assert!(!state.is_ready());
assert_eq!(state.state_name(), "Disconnected");
}
}