use std::time::Duration;
use crate::mcp::{McpRegistry, McpServer};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum McpReadyError {
NotFound,
Disabled,
PermanentlyFailed { last_error: Option<String> },
}
impl std::fmt::Display for McpReadyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotFound => write!(f, "MCP server not found"),
Self::Disabled => write!(f, "MCP server is disabled"),
Self::PermanentlyFailed { last_error } => match last_error {
Some(detail) => write!(f, "MCP server connect failed: {detail}"),
None => write!(f, "MCP server connect failed"),
},
}
}
}
impl std::error::Error for McpReadyError {}
#[derive(Debug, Clone, Copy)]
pub struct EnsureReadyPolicy {
pub attempts: usize,
pub initial_delay_ms: u64,
pub backoff_factor: u32,
}
impl Default for EnsureReadyPolicy {
fn default() -> Self {
Self {
attempts: 1,
initial_delay_ms: 0,
backoff_factor: 1,
}
}
}
impl EnsureReadyPolicy {
pub fn with_retries(attempts: usize, initial_delay_ms: u64) -> Self {
Self {
attempts: attempts.max(1),
initial_delay_ms,
backoff_factor: 2,
}
}
}
impl McpRegistry {
pub async fn ensure_ready(
&self,
server_name: &str,
policy: EnsureReadyPolicy,
) -> Result<McpServer, McpReadyError> {
let initial = self.list().await.get(server_name).cloned();
let Some(server) = initial else {
return Err(McpReadyError::NotFound);
};
if !server.enabled {
return Err(McpReadyError::Disabled);
}
if server.connected {
return Ok(server);
}
let attempts = policy.attempts.max(1);
let mut next_delay_ms = policy.initial_delay_ms;
for attempt in 0..attempts {
if attempt > 0 {
if next_delay_ms > 0 {
tokio::time::sleep(Duration::from_millis(next_delay_ms)).await;
}
next_delay_ms = next_delay_ms.saturating_mul(policy.backoff_factor.max(1) as u64);
}
if self.connect(server_name).await {
if let Some(server) = self.list().await.get(server_name).cloned() {
if server.connected {
return Ok(server);
}
}
}
}
let last_error = self
.list()
.await
.get(server_name)
.and_then(|s| s.last_error.clone())
.filter(|e| !e.trim().is_empty());
Err(McpReadyError::PermanentlyFailed { last_error })
}
}