use std::marker::PhantomData;
use std::time::{Duration, Instant};
use crate::capability::{
ClientCapabilities, ClientInfo, InitializeRequest, InitializeResult, ServerCapabilities,
ServerInfo,
};
use crate::error::McpError;
use crate::protocol::RequestId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Disconnected;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Connected;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Initializing;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Ready;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Closing;
#[derive(Debug)]
pub struct ConnectionInner {
pub id: String,
pub connected_at: Option<Instant>,
pub last_activity: Option<Instant>,
pub request_counter: u64,
pub client_info: Option<ClientInfo>,
pub server_info: Option<ServerInfo>,
pub client_capabilities: Option<ClientCapabilities>,
pub server_capabilities: Option<ServerCapabilities>,
}
impl ConnectionInner {
fn new() -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
connected_at: None,
last_activity: None,
request_counter: 0,
client_info: None,
server_info: None,
client_capabilities: None,
server_capabilities: None,
}
}
fn next_request_id(&mut self) -> RequestId {
self.request_counter += 1;
RequestId::Number(self.request_counter)
}
fn touch(&mut self) {
self.last_activity = Some(Instant::now());
}
}
impl Default for ConnectionInner {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct Connection<S> {
inner: ConnectionInner,
_state: PhantomData<S>,
}
impl Connection<Disconnected> {
#[must_use]
pub fn new() -> Self {
Self {
inner: ConnectionInner::new(),
_state: PhantomData,
}
}
#[must_use]
pub fn id(&self) -> &str {
&self.inner.id
}
#[must_use]
pub fn connect(mut self) -> Connection<Connected> {
self.inner.connected_at = Some(Instant::now());
self.inner.touch();
Connection {
inner: self.inner,
_state: PhantomData,
}
}
}
impl Default for Connection<Disconnected> {
fn default() -> Self {
Self::new()
}
}
impl Connection<Connected> {
#[must_use]
pub fn id(&self) -> &str {
&self.inner.id
}
#[must_use]
pub const fn connected_at(&self) -> Option<Instant> {
self.inner.connected_at
}
#[must_use]
pub fn uptime(&self) -> Duration {
self.inner
.connected_at
.map(|t| t.elapsed())
.unwrap_or_default()
}
#[must_use]
pub fn initialize(
mut self,
client_info: ClientInfo,
client_capabilities: ClientCapabilities,
) -> (Connection<Initializing>, InitializeRequest) {
self.inner.client_info = Some(client_info.clone());
self.inner.client_capabilities = Some(client_capabilities.clone());
self.inner.touch();
let request = InitializeRequest::new(client_info, client_capabilities);
(
Connection {
inner: self.inner,
_state: PhantomData,
},
request,
)
}
#[must_use]
pub fn disconnect(self) -> Connection<Disconnected> {
Connection {
inner: ConnectionInner::new(),
_state: PhantomData,
}
}
}
impl Connection<Initializing> {
#[must_use]
pub fn id(&self) -> &str {
&self.inner.id
}
#[must_use]
pub const fn client_info(&self) -> Option<&ClientInfo> {
self.inner.client_info.as_ref()
}
#[must_use]
pub const fn client_capabilities(&self) -> Option<&ClientCapabilities> {
self.inner.client_capabilities.as_ref()
}
#[must_use]
pub fn complete(
mut self,
server_info: ServerInfo,
server_capabilities: ServerCapabilities,
) -> Connection<Ready> {
self.inner.server_info = Some(server_info);
self.inner.server_capabilities = Some(server_capabilities);
self.inner.touch();
Connection {
inner: self.inner,
_state: PhantomData,
}
}
#[must_use]
pub fn abort(self) -> Connection<Disconnected> {
Connection {
inner: ConnectionInner::new(),
_state: PhantomData,
}
}
}
impl Connection<Ready> {
#[must_use]
pub fn id(&self) -> &str {
&self.inner.id
}
#[must_use]
pub const fn connected_at(&self) -> Option<Instant> {
self.inner.connected_at
}
#[must_use]
pub fn uptime(&self) -> Duration {
self.inner
.connected_at
.map(|t| t.elapsed())
.unwrap_or_default()
}
#[must_use]
pub const fn last_activity(&self) -> Option<Instant> {
self.inner.last_activity
}
#[must_use]
pub fn client_info(&self) -> &ClientInfo {
self.inner
.client_info
.as_ref()
.expect("client_info should be set in Ready state")
}
#[must_use]
pub const fn try_client_info(&self) -> Option<&ClientInfo> {
self.inner.client_info.as_ref()
}
#[must_use]
pub fn server_info(&self) -> &ServerInfo {
self.inner
.server_info
.as_ref()
.expect("server_info should be set in Ready state")
}
#[must_use]
pub const fn try_server_info(&self) -> Option<&ServerInfo> {
self.inner.server_info.as_ref()
}
#[must_use]
pub fn client_capabilities(&self) -> &ClientCapabilities {
self.inner
.client_capabilities
.as_ref()
.expect("client_capabilities should be set in Ready state")
}
#[must_use]
pub const 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
.as_ref()
.expect("server_capabilities should be set in Ready state")
}
#[must_use]
pub const fn try_server_capabilities(&self) -> Option<&ServerCapabilities> {
self.inner.server_capabilities.as_ref()
}
pub fn next_request_id(&mut self) -> RequestId {
self.inner.next_request_id()
}
pub fn touch(&mut self) {
self.inner.touch();
}
#[must_use]
pub fn is_idle(&self, timeout: Duration) -> bool {
self.inner
.last_activity
.is_some_and(|t| t.elapsed() > timeout)
}
#[must_use]
pub fn shutdown(self) -> Connection<Closing> {
Connection {
inner: self.inner,
_state: PhantomData,
}
}
}
impl Connection<Closing> {
#[must_use]
pub fn id(&self) -> &str {
&self.inner.id
}
#[must_use]
pub fn close(self) -> Connection<Disconnected> {
Connection {
inner: ConnectionInner::new(),
_state: PhantomData,
}
}
}
pub struct InitializeResultBuilder {
server_info: ServerInfo,
capabilities: ServerCapabilities,
instructions: Option<String>,
}
impl InitializeResultBuilder {
#[must_use]
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
Self {
server_info: ServerInfo::new(name, version),
capabilities: ServerCapabilities::new(),
instructions: None,
}
}
#[must_use]
pub fn capabilities(mut self, caps: ServerCapabilities) -> Self {
self.capabilities = caps;
self
}
#[must_use]
pub fn with_tools(mut self) -> Self {
self.capabilities = self.capabilities.with_tools();
self
}
#[must_use]
pub fn with_resources(mut self) -> Self {
self.capabilities = self.capabilities.with_resources();
self
}
#[must_use]
pub fn with_prompts(mut self) -> Self {
self.capabilities = self.capabilities.with_prompts();
self
}
#[must_use]
pub fn with_tasks(mut self) -> Self {
self.capabilities = self.capabilities.with_tasks();
self
}
#[must_use]
pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
self.instructions = Some(instructions.into());
self
}
#[must_use]
pub fn build(self) -> InitializeResult {
let mut result = InitializeResult::new(self.server_info, self.capabilities);
if let Some(instructions) = self.instructions {
result = result.instructions(instructions);
}
result
}
}
pub const fn validate_initialization(
_client_caps: &ClientCapabilities,
_server_caps: &ServerCapabilities,
) -> Result<(), McpError> {
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_connection_lifecycle() {
let conn: Connection<Disconnected> = Connection::new();
assert!(!conn.id().is_empty());
let conn: Connection<Connected> = conn.connect();
assert!(conn.connected_at().is_some());
let client = ClientInfo::new("test", "1.0.0");
let caps = ClientCapabilities::new();
let (conn, _request): (Connection<Initializing>, _) = conn.initialize(client, caps);
assert!(conn.client_info().is_some());
let server = ServerInfo::new("server", "1.0.0");
let server_caps = ServerCapabilities::new().with_tools();
let mut conn: Connection<Ready> = conn.complete(server, server_caps);
assert!(conn.server_capabilities().has_tools());
let id1 = conn.next_request_id();
let id2 = conn.next_request_id();
assert_ne!(id1, id2);
let conn: Connection<Closing> = conn.shutdown();
let _conn: Connection<Disconnected> = conn.close();
}
#[test]
fn test_uptime() {
let conn = Connection::new().connect();
std::thread::sleep(std::time::Duration::from_millis(10));
assert!(conn.uptime() >= std::time::Duration::from_millis(10));
}
#[test]
fn test_idle_detection() {
let client = ClientInfo::new("test", "1.0.0");
let server = ServerInfo::new("server", "1.0.0");
let (conn, _) = Connection::new()
.connect()
.initialize(client, ClientCapabilities::new());
let conn = conn.complete(server, ServerCapabilities::new());
assert!(!conn.is_idle(Duration::from_secs(1)));
}
#[test]
fn test_initialize_result_builder() {
let result = InitializeResultBuilder::new("my-server", "1.0.0")
.with_tools()
.with_resources()
.instructions("Use this server to access tools and resources")
.build();
assert_eq!(result.server_info.name, "my-server");
assert!(result.capabilities.has_tools());
assert!(result.capabilities.has_resources());
assert!(result.instructions.is_some());
}
#[test]
fn test_abort_initialization() {
let client = ClientInfo::new("test", "1.0.0");
let (conn, _) = Connection::new()
.connect()
.initialize(client, ClientCapabilities::new());
let _conn: Connection<Disconnected> = conn.abort();
}
#[test]
fn test_disconnect_from_connected() {
let conn = Connection::new().connect();
let _conn: Connection<Disconnected> = conn.disconnect();
}
#[test]
fn test_fallible_accessors() -> Result<(), Box<dyn std::error::Error>> {
let client = ClientInfo::new("test-client", "1.0.0");
let server = ServerInfo::new("test-server", "2.0.0");
let client_caps = ClientCapabilities::new();
let server_caps = ServerCapabilities::new().with_tools();
let (conn, _) = Connection::new().connect().initialize(client, client_caps);
let conn = conn.complete(server, server_caps);
assert!(conn.try_client_info().is_some());
assert!(conn.try_server_info().is_some());
assert!(conn.try_client_capabilities().is_some());
assert!(conn.try_server_capabilities().is_some());
assert_eq!(
conn.try_client_info().ok_or("Expected client info")?.name,
"test-client"
);
assert_eq!(
conn.try_server_info().ok_or("Expected server info")?.name,
"test-server"
);
assert!(
conn.try_server_capabilities()
.ok_or("Expected server capabilities")?
.has_tools()
);
Ok(())
}
}