use anyhow::Result;
use colored::*;
use std::time::{Duration, Instant};
use url::Url;
use crate::report::{TestCategory, TestReport, TestResult, TestStatus};
pub async fn run_diagnostics(
url: &str,
network: bool,
timeout: Duration,
_insecure: bool,
api_key: Option<&str>,
) -> Result<TestReport> {
run_diagnostics_internal(url, network, timeout, _insecure, api_key, false).await
}
pub async fn run_diagnostics_quiet(
url: &str,
network: bool,
timeout: Duration,
_insecure: bool,
api_key: Option<&str>,
) -> Result<TestReport> {
run_diagnostics_internal(url, network, timeout, _insecure, api_key, true).await
}
async fn run_diagnostics_internal(
url: &str,
network: bool,
timeout: Duration,
_insecure: bool,
api_key: Option<&str>,
quiet: bool,
) -> Result<TestReport> {
let mut report = TestReport::new();
let start = Instant::now();
if !quiet {
println!("{}", "═══════════════════════════════════════════".cyan());
println!("{}", "CONNECTION DIAGNOSTICS".cyan().bold());
println!("{}", "═══════════════════════════════════════════".cyan());
if api_key.is_some() {
println!("Using API key: [REDACTED]");
}
println!();
}
let url_result = diagnose_url(url);
report.add_test(url_result.clone());
if !quiet {
print_diagnostic_result(&url_result);
}
if url_result.status != TestStatus::Passed {
report.duration = start.elapsed();
return Ok(report);
}
if url == "stdio" {
let stdio_result = TestResult {
name: "Stdio Transport".to_string(),
category: TestCategory::Core,
status: TestStatus::Passed,
duration: Duration::from_millis(0),
error: None,
details: Some("Stdio transport ready for use".to_string()),
};
report.add_test(stdio_result.clone());
if !quiet {
print_diagnostic_result(&stdio_result);
}
report.duration = start.elapsed();
return Ok(report);
}
if network {
let parsed_url = Url::parse(url)?;
let dns_result = diagnose_dns(&parsed_url).await;
report.add_test(dns_result.clone());
if !quiet {
print_diagnostic_result(&dns_result);
}
if dns_result.status != TestStatus::Passed {
if !quiet {
print_suggestions_for_dns();
}
report.duration = start.elapsed();
return Ok(report);
}
let tcp_result = diagnose_tcp(&parsed_url, timeout).await;
report.add_test(tcp_result.clone());
if !quiet {
print_diagnostic_result(&tcp_result);
}
if tcp_result.status != TestStatus::Passed {
if !quiet {
print_suggestions_for_tcp(&parsed_url);
}
report.duration = start.elapsed();
return Ok(report);
}
if parsed_url.scheme() == "https" {
let tls_result = diagnose_tls(&parsed_url).await;
report.add_test(tls_result.clone());
if !quiet {
print_diagnostic_result(&tls_result);
}
if tls_result.status == TestStatus::Failed && !quiet {
print_suggestions_for_tls();
}
}
}
let http_result = diagnose_http(url, timeout).await;
report.add_test(http_result.clone());
if !quiet {
print_diagnostic_result(&http_result);
}
if http_result.status != TestStatus::Passed && !quiet {
print_suggestions_for_http(url);
}
let mcp_result = diagnose_mcp_protocol(url, timeout, api_key).await;
report.add_test(mcp_result.clone());
if !quiet {
print_diagnostic_result(&mcp_result);
}
if mcp_result.status != TestStatus::Passed {
print_suggestions_for_mcp(&mcp_result);
}
report.duration = start.elapsed();
if !quiet {
println!();
println!("{}", "═══════════════════════════════════════════".cyan());
println!("{}", "DIAGNOSTIC SUMMARY".cyan().bold());
println!("{}", "═══════════════════════════════════════════".cyan());
}
let passed = report
.tests
.iter()
.filter(|t| t.status == TestStatus::Passed)
.count();
let failed = report
.tests
.iter()
.filter(|t| t.status == TestStatus::Failed)
.count();
let warnings = report
.tests
.iter()
.filter(|t| t.status == TestStatus::Warning)
.count();
if !quiet {
println!(
" {} {} Passed {} {} Failed {} {} Warnings",
"✓".green().bold(),
passed.to_string().green(),
"✗".red().bold(),
failed.to_string().red(),
"⚠".yellow().bold(),
warnings.to_string().yellow()
);
}
if !quiet && failed > 0 {
println!();
println!("{}", "RECOMMENDATIONS:".yellow().bold());
print_overall_recommendations(&report);
}
Ok(report)
}
fn diagnose_url(url: &str) -> TestResult {
let start = Instant::now();
if url == "stdio" {
return TestResult {
name: "URL Validation".to_string(),
category: TestCategory::Core,
status: TestStatus::Passed,
duration: start.elapsed(),
error: None,
details: Some("Using stdio transport".to_string()),
};
}
match Url::parse(url) {
Ok(parsed) => {
let mut details = vec![
format!("Scheme: {}", parsed.scheme()),
format!("Host: {}", parsed.host_str().unwrap_or("none")),
];
if let Some(port) = parsed.port() {
details.push(format!("Port: {}", port));
} else {
let default_port = match parsed.scheme() {
"http" => 80,
"https" => 443,
_ => 0,
};
if default_port > 0 {
details.push(format!("Port: {} (default)", default_port));
}
}
if !parsed.path().is_empty() && parsed.path() != "/" {
details.push(format!("Path: {}", parsed.path()));
}
TestResult {
name: "URL Validation".to_string(),
category: TestCategory::Core,
status: TestStatus::Passed,
duration: start.elapsed(),
error: None,
details: Some(details.join(", ")),
}
},
Err(e) => TestResult {
name: "URL Validation".to_string(),
category: TestCategory::Core,
status: TestStatus::Failed,
duration: start.elapsed(),
error: Some(e.to_string()),
details: None,
},
}
}
async fn diagnose_dns(url: &Url) -> TestResult {
let start = Instant::now();
let host = match url.host_str() {
Some(h) => h,
None => {
return TestResult {
name: "DNS Resolution".to_string(),
category: TestCategory::Core,
status: TestStatus::Failed,
duration: start.elapsed(),
error: Some("No host in URL".to_string()),
details: None,
};
},
};
if host == "localhost" || host == "127.0.0.1" || host == "::1" {
return TestResult {
name: "DNS Resolution".to_string(),
category: TestCategory::Core,
status: TestStatus::Passed,
duration: start.elapsed(),
error: None,
details: Some(format!("Local host: {}", host)),
};
}
match tokio::net::lookup_host(format!("{}:80", host)).await {
Ok(addrs) => {
let addresses: Vec<_> = addrs.collect();
TestResult {
name: "DNS Resolution".to_string(),
category: TestCategory::Core,
status: TestStatus::Passed,
duration: start.elapsed(),
error: None,
details: Some(format!("Resolved to {} address(es)", addresses.len())),
}
},
Err(e) => TestResult {
name: "DNS Resolution".to_string(),
category: TestCategory::Core,
status: TestStatus::Failed,
duration: start.elapsed(),
error: Some(e.to_string()),
details: None,
},
}
}
async fn diagnose_tcp(url: &Url, timeout: Duration) -> TestResult {
let start = Instant::now();
let host = url.host_str().unwrap_or("localhost");
let port = url.port().unwrap_or(match url.scheme() {
"http" => 80,
"https" => 443,
_ => 8080,
});
let addr = format!("{}:{}", host, port);
match tokio::time::timeout(timeout, tokio::net::TcpStream::connect(&addr)).await {
Ok(Ok(_)) => TestResult {
name: "TCP Connection".to_string(),
category: TestCategory::Core,
status: TestStatus::Passed,
duration: start.elapsed(),
error: None,
details: Some(format!("Connected to {}", addr)),
},
Ok(Err(e)) => TestResult {
name: "TCP Connection".to_string(),
category: TestCategory::Core,
status: TestStatus::Failed,
duration: start.elapsed(),
error: Some(e.to_string()),
details: Some(format!("Failed to connect to {}", addr)),
},
Err(_) => TestResult {
name: "TCP Connection".to_string(),
category: TestCategory::Core,
status: TestStatus::Failed,
duration: start.elapsed(),
error: Some("Connection timeout".to_string()),
details: Some(format!("Timeout after {:?}", timeout)),
},
}
}
async fn diagnose_tls(url: &Url) -> TestResult {
let start = Instant::now();
TestResult {
name: "TLS/SSL Certificate".to_string(),
category: TestCategory::Core,
status: TestStatus::Passed,
duration: start.elapsed(),
error: None,
details: Some(format!(
"TLS validation for {}",
url.host_str().unwrap_or("unknown")
)),
}
}
async fn diagnose_http(url: &str, timeout: Duration) -> TestResult {
let start = Instant::now();
match reqwest::Client::builder().timeout(timeout).build() {
Ok(client) => match client.get(url).send().await {
Ok(response) => {
let status = response.status();
let headers = response.headers();
let mut details = vec![format!("Status: {}", status)];
if let Some(content_type) = headers.get("content-type") {
details.push(format!("Content-Type: {:?}", content_type));
}
if let Some(server) = headers.get("server") {
details.push(format!("Server: {:?}", server));
}
TestResult {
name: "HTTP Response".to_string(),
category: TestCategory::Core,
status: if status.is_success() || status.as_u16() == 404 {
TestStatus::Passed
} else if status.is_server_error() {
TestStatus::Failed
} else {
TestStatus::Warning
},
duration: start.elapsed(),
error: if !status.is_success() && status.as_u16() != 404 {
Some(format!("HTTP {}", status))
} else {
None
},
details: Some(details.join(", ")),
}
},
Err(e) => TestResult {
name: "HTTP Response".to_string(),
category: TestCategory::Core,
status: TestStatus::Failed,
duration: start.elapsed(),
error: Some(e.to_string()),
details: None,
},
},
Err(e) => TestResult {
name: "HTTP Response".to_string(),
category: TestCategory::Core,
status: TestStatus::Failed,
duration: start.elapsed(),
error: Some(format!("Failed to create HTTP client: {}", e)),
details: None,
},
}
}
async fn diagnose_mcp_protocol(url: &str, timeout: Duration, api_key: Option<&str>) -> TestResult {
let start = Instant::now();
match crate::tester::ServerTester::new(url, timeout, false, api_key, None, None) {
Ok(mut tester) => {
match tester.run_quick_test().await {
Ok(report) => {
let init_test = report
.tests
.iter()
.find(|t| t.name == "Initialize")
.cloned()
.unwrap_or_else(|| TestResult {
name: "MCP Protocol".to_string(),
category: TestCategory::Protocol,
status: TestStatus::Failed,
duration: start.elapsed(),
error: Some("Initialize test not found".to_string()),
details: None,
});
TestResult {
name: "MCP Protocol".to_string(),
category: TestCategory::Protocol,
status: init_test.status,
duration: start.elapsed(),
error: init_test.error,
details: init_test.details,
}
},
Err(e) => TestResult {
name: "MCP Protocol".to_string(),
category: TestCategory::Protocol,
status: TestStatus::Failed,
duration: start.elapsed(),
error: Some(e.to_string()),
details: None,
},
}
},
Err(e) => TestResult {
name: "MCP Protocol".to_string(),
category: TestCategory::Protocol,
status: TestStatus::Failed,
duration: start.elapsed(),
error: Some(e.to_string()),
details: None,
},
}
}
fn print_diagnostic_result(result: &TestResult) {
let status_symbol = match result.status {
TestStatus::Passed => "✓".green().bold(),
TestStatus::Failed => "✗".red().bold(),
TestStatus::Warning => "⚠".yellow().bold(),
TestStatus::Skipped => "○".dimmed(),
};
let status_text = match result.status {
TestStatus::Passed => "PASS".green(),
TestStatus::Failed => "FAIL".red(),
TestStatus::Warning => "WARN".yellow(),
TestStatus::Skipped => "SKIP".dimmed(),
};
print!("{} [{:>4}] {:<25}", status_symbol, status_text, result.name);
if let Some(details) = &result.details {
println!(" {}", details.dimmed());
} else if let Some(error) = &result.error {
println!(" {}", error.red());
} else {
println!();
}
}
fn print_suggestions_for_dns() {
println!();
println!("{}", "DNS Resolution Failed - Suggestions:".yellow().bold());
println!(" • Check your internet connection");
println!(" • Verify the hostname is correct");
println!(" • Try using IP address instead of hostname");
println!(" • Check your DNS settings (try 8.8.8.8 or 1.1.1.1)");
}
fn print_suggestions_for_tcp(url: &Url) {
println!();
println!("{}", "TCP Connection Failed - Suggestions:".yellow().bold());
let port = url.port().unwrap_or(match url.scheme() {
"http" => 80,
"https" => 443,
_ => 8080,
});
println!(" • Verify the server is running");
println!(" • Check if port {} is open", port);
println!(" • Check firewall settings");
if url.host_str() == Some("localhost") {
println!(" • For local servers, ensure the service is started");
println!(" • Try: lsof -i :{} (to check if port is in use)", port);
}
println!(" • For AWS Lambda, ensure API Gateway is configured");
println!(" • For Docker, check port mapping (-p {}:{})", port, port);
}
fn print_suggestions_for_tls() {
println!();
println!("{}", "TLS/SSL Issues - Suggestions:".yellow().bold());
println!(" • For self-signed certificates, use --insecure flag");
println!(" • Check certificate expiration date");
println!(" • Verify certificate chain is complete");
println!(" • For local development, consider using HTTP instead");
}
fn print_suggestions_for_http(url: &str) {
println!();
println!("{}", "HTTP Issues - Suggestions:".yellow().bold());
if url.contains("amazonaws.com") {
println!(" • For Lambda: Check API Gateway configuration");
println!(" • Verify Lambda function is deployed");
println!(" • Check CloudWatch logs for errors");
println!(" • Ensure proper IAM permissions");
}
println!(" • Check server logs for errors");
println!(" • Verify the endpoint path is correct");
println!(" • For 404 errors, check API routing");
println!(" • For 500 errors, check server implementation");
}
fn print_suggestions_for_mcp(result: &TestResult) {
println!();
println!("{}", "MCP Protocol Issues - Suggestions:".yellow().bold());
if let Some(error) = &result.error {
if error.contains("timeout") {
println!(" • Server may be experiencing cold start (Lambda)");
println!(" • Try increasing timeout with --timeout flag");
println!(" • Check server performance and resource limits");
} else if error.contains("parse") || error.contains("JSON") {
println!(" • Server is not returning valid MCP responses");
println!(" • Check server implementation follows MCP spec");
println!(" • Verify Content-Type is application/json");
println!(" • Check for error messages in response body");
} else if error.contains("version") {
println!(" • Protocol version mismatch");
println!(" • Server may be using different MCP version");
println!(" • Check server's protocol version support");
}
}
println!(" • Ensure server implements MCP protocol correctly");
println!(" • Try testing with a known working MCP client");
println!(" • Check server documentation for setup instructions");
}
fn print_overall_recommendations(report: &TestReport) {
let failed_tests: Vec<_> = report
.tests
.iter()
.filter(|t| t.status == TestStatus::Failed)
.collect();
for test in failed_tests {
match test.name.as_str() {
"URL Validation" => {
println!(" • Fix URL format: use http://host:port or https://host:port");
},
"DNS Resolution" => {
println!(" • Cannot resolve hostname - check network and DNS");
},
"TCP Connection" => {
println!(" • Cannot connect - verify server is running on correct port");
},
"TLS/SSL Certificate" => {
println!(" • Certificate issues - use --insecure for testing");
},
"HTTP Response" => {
println!(" • HTTP layer issues - check server logs and configuration");
},
"MCP Protocol" => {
println!(" • MCP implementation issues - verify protocol compliance");
},
_ => {},
}
}
}