use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum McpServerStatus {
Healthy,
Degraded,
Unhealthy,
Disconnected,
Starting,
Stopping,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpServerHealth {
pub server_name: String,
pub status: McpServerStatus,
pub last_successful_call: Option<chrono::DateTime<chrono::Utc>>,
pub error_count: u64,
pub total_calls: u64,
pub average_response_time: std::time::Duration,
pub last_error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpToolStats {
pub tool_name: String,
pub server_name: String,
pub call_count: u64,
pub success_count: u64,
pub error_count: u64,
pub average_execution_time: std::time::Duration,
pub last_call: Option<chrono::DateTime<chrono::Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpConnectionInfo {
pub server_name: String,
pub transport_type: String,
pub connected_at: chrono::DateTime<chrono::Utc>,
pub reconnection_count: u32,
pub capabilities: Option<McpServerCapabilities>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpServerCapabilities {
pub tools: bool,
pub resources: bool,
pub prompts: bool,
pub logging: bool,
pub progress: bool,
}
impl Default for McpServerHealth {
fn default() -> Self {
Self {
server_name: String::new(),
status: McpServerStatus::Disconnected,
last_successful_call: None,
error_count: 0,
total_calls: 0,
average_response_time: std::time::Duration::from_millis(0),
last_error: None,
}
}
}
impl Default for McpServerCapabilities {
fn default() -> Self {
Self {
tools: true,
resources: false,
prompts: false,
logging: false,
progress: false,
}
}
}
impl McpServerHealth {
pub fn new(server_name: String) -> Self {
Self {
server_name,
..Default::default()
}
}
pub fn record_success(&mut self, response_time: std::time::Duration) {
self.total_calls += 1;
self.last_successful_call = Some(chrono::Utc::now());
self.status = McpServerStatus::Healthy;
let total_time = self.average_response_time.as_millis() as u64 * (self.total_calls - 1) + response_time.as_millis() as u64;
self.average_response_time = std::time::Duration::from_millis(total_time / self.total_calls);
}
pub fn record_error(&mut self, error: String) {
self.total_calls += 1;
self.error_count += 1;
self.last_error = Some(error);
let error_rate = self.error_count as f64 / self.total_calls as f64;
self.status = if error_rate > 0.5 {
McpServerStatus::Unhealthy
} else if error_rate > 0.1 {
McpServerStatus::Degraded
} else {
McpServerStatus::Healthy
};
}
pub fn is_healthy(&self) -> bool {
matches!(self.status, McpServerStatus::Healthy)
}
pub fn is_available(&self) -> bool {
matches!(
self.status,
McpServerStatus::Healthy | McpServerStatus::Degraded
)
}
}
impl McpToolStats {
pub fn new(tool_name: String, server_name: String) -> Self {
Self {
tool_name,
server_name,
call_count: 0,
success_count: 0,
error_count: 0,
average_execution_time: std::time::Duration::from_millis(0),
last_call: None,
}
}
pub fn record_success(&mut self, execution_time: std::time::Duration) {
self.call_count += 1;
self.success_count += 1;
self.last_call = Some(chrono::Utc::now());
let total_time = self.average_execution_time.as_millis() as u64 * (self.call_count - 1) + execution_time.as_millis() as u64;
self.average_execution_time = std::time::Duration::from_millis(total_time / self.call_count);
}
pub fn record_error(&mut self) {
self.call_count += 1;
self.error_count += 1;
self.last_call = Some(chrono::Utc::now());
}
pub fn success_rate(&self) -> f64 {
if self.call_count == 0 {
0.0
} else {
(self.success_count as f64 / self.call_count as f64) * 100.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_server_health_success_recording() {
let mut health = McpServerHealth::new("test-server".to_string());
health.record_success(std::time::Duration::from_millis(100));
assert_eq!(health.total_calls, 1);
assert_eq!(health.status, McpServerStatus::Healthy);
assert!(health.last_successful_call.is_some());
assert_eq!(health.average_response_time, std::time::Duration::from_millis(100));
}
#[test]
fn test_server_health_error_recording() {
let mut health = McpServerHealth::new("test-server".to_string());
health.record_error("Test error".to_string());
assert_eq!(health.total_calls, 1);
assert_eq!(health.error_count, 1);
assert_eq!(health.last_error, Some("Test error".to_string()));
}
#[test]
fn test_tool_stats() {
let mut stats = McpToolStats::new("test-tool".to_string(), "test-server".to_string());
stats.record_success(std::time::Duration::from_millis(50));
stats.record_error();
assert_eq!(stats.call_count, 2);
assert_eq!(stats.success_count, 1);
assert_eq!(stats.error_count, 1);
assert_eq!(stats.success_rate(), 50.0);
}
}