use std::time::{Duration, Instant};
use anyhow::{Result, bail};
use mcp_common::McpClientConfig;
use mcp_sse_proxy::SseClientConnection;
use mcp_streamable_proxy::StreamClientConnection;
use crate::client::protocol::detect_mcp_protocol_with_headers;
use crate::client::proxy_server::ProxyProtocol;
use crate::client::support::{HealthArgs, merge_headers};
use crate::model::McpProtocol;
use std::collections::HashMap;
struct HealthCheckResult {
tool_count: usize,
server_name: Option<String>,
server_version: Option<String>,
}
pub async fn run_health_command(args: HealthArgs, quiet: bool) -> Result<()> {
if !quiet {
eprintln!("Checking health for: {}", args.url);
}
let protocol = match &args.protocol {
Some(p) => {
let proto = proxy_protocol_to_mcp_protocol(p.clone());
if !quiet {
eprintln!("Using protocol: {}", protocol_display_name(&proto));
}
proto
}
None => {
if !quiet {
eprintln!("Detecting protocol...");
}
let detection_headers = merge_headers(HashMap::new(), &args.header, args.auth.as_ref());
let proto =
detect_mcp_protocol_with_headers(&args.url, Some(&detection_headers)).await?;
if !quiet {
eprintln!("Detected protocol: {}", protocol_display_name(&proto));
}
proto
}
};
if protocol == McpProtocol::Stdio {
bail!("Stdio protocol is not supported for health checks");
}
let config = build_config(&args);
let start = Instant::now();
let result = tokio::time::timeout(
Duration::from_secs(args.timeout),
connect_and_verify(&config, protocol.clone()),
)
.await;
let elapsed = start.elapsed();
match result {
Ok(Ok(health_result)) => {
if !quiet {
eprintln!("Health check passed");
eprintln!(" Protocol: {}", protocol_display_name(&protocol));
eprintln!(" Tools: {}", health_result.tool_count);
eprintln!(" Response time: {}ms", elapsed.as_millis());
if let (Some(name), Some(version)) =
(&health_result.server_name, &health_result.server_version)
{
eprintln!(" Server: {} v{}", name, version);
} else if let Some(name) = &health_result.server_name {
eprintln!(" Server: {}", name);
}
}
Ok(())
}
Ok(Err(e)) => {
if !quiet {
eprintln!("Health check failed");
eprintln!(" Error: {}", e);
eprintln!(" Response time: {}ms", elapsed.as_millis());
}
Err(anyhow::anyhow!("health check failed: {}", e))
}
Err(_) => {
if !quiet {
eprintln!("Health check failed");
eprintln!(" Error: connection timed out ({}s)", args.timeout);
}
Err(anyhow::anyhow!("health check timeout"))
}
}
}
fn protocol_display_name(protocol: &McpProtocol) -> &'static str {
match protocol {
McpProtocol::Sse => "SSE",
McpProtocol::Stream => "Streamable HTTP",
McpProtocol::Stdio => "Stdio",
}
}
fn proxy_protocol_to_mcp_protocol(p: ProxyProtocol) -> McpProtocol {
match p {
ProxyProtocol::Sse => McpProtocol::Sse,
ProxyProtocol::Stream => McpProtocol::Stream,
}
}
fn build_config(args: &HealthArgs) -> McpClientConfig {
let mut config = McpClientConfig::new(&args.url);
if let Some(auth) = &args.auth {
config = config.with_header("Authorization", auth);
}
for (key, value) in &args.header {
config = config.with_header(key, value);
}
config = config.with_connect_timeout(Duration::from_secs(args.timeout));
config = config.with_read_timeout(Duration::from_secs(args.timeout));
config
}
async fn connect_and_verify(
config: &McpClientConfig,
protocol: McpProtocol,
) -> Result<HealthCheckResult> {
match protocol {
McpProtocol::Sse => {
let conn = SseClientConnection::connect(config.clone()).await?;
let tools = conn.list_tools().await?;
let tool_count = tools.len();
let (server_name, server_version) = conn
.peer_info()
.map(|info| {
(
Some(info.server_info.name.clone()),
Some(info.server_info.version.clone()),
)
})
.unwrap_or((None, None));
Ok(HealthCheckResult {
tool_count,
server_name,
server_version,
})
}
McpProtocol::Stream => {
let conn = StreamClientConnection::connect(config.clone()).await?;
let tools = conn.list_tools().await?;
let tool_count = tools.len();
let (server_name, server_version) = conn
.peer_info()
.map(|info| {
(
Some(info.server_info.name.clone()),
Some(info.server_info.version.clone()),
)
})
.unwrap_or((None, None));
Ok(HealthCheckResult {
tool_count,
server_name,
server_version,
})
}
McpProtocol::Stdio => {
bail!("stdio protocol is not supported for health check")
}
}
}