use aicx::mcp::{self, McpTransport};
use std::io::Write as _;
use std::panic;
use std::process::ExitCode;
use clap::Parser;
#[derive(Parser)]
#[command(name = "aicx-mcp")]
#[command(author = "M&K (c)2026 VetCoders")]
#[command(version)]
struct Args {
#[arg(long, value_enum, default_value_t = McpTransport::Stdio)]
transport: McpTransport,
#[arg(long, default_value = "8044")]
port: u16,
}
fn safe_stderr_log(line: &str) {
let mut stderr = std::io::stderr().lock();
let _ = stderr.write_all(line.as_bytes());
let _ = stderr.write_all(b"\n");
let _ = stderr.flush();
}
fn install_panic_hook() {
panic::set_hook(Box::new(|panic_info| {
let msg = if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = panic_info.payload().downcast_ref::<String>() {
s.clone()
} else {
"Unknown panic".to_string()
};
if msg.contains("Broken pipe") || msg.contains("os error 32") {
safe_stderr_log("[aicx-mcp] Client disconnected (broken pipe), shutting down");
std::process::exit(0);
} else {
let location = panic_info
.location()
.map(|loc| format!(" at {}:{}:{}", loc.file(), loc.line(), loc.column()))
.unwrap_or_default();
safe_stderr_log(&format!("[aicx-mcp] Panic{}: {}", location, msg));
}
}));
}
#[cfg(unix)]
fn ignore_sigpipe() {
unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_IGN);
}
}
#[cfg(not(unix))]
fn ignore_sigpipe() {}
fn main() -> ExitCode {
ignore_sigpipe();
install_panic_hook();
let args = Args::parse();
let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(e) => {
safe_stderr_log(&format!("[aicx-mcp] Failed to create runtime: {e}"));
return ExitCode::FAILURE;
}
};
match rt.block_on(async { mcp::run_transport(args.transport, args.port).await }) {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
let err_str = format!("{e:?}");
if err_str.contains("Broken pipe") || err_str.contains("os error 32") {
safe_stderr_log("[aicx-mcp] Client disconnected, shutting down");
ExitCode::SUCCESS
} else {
safe_stderr_log(&format!("[aicx-mcp] Error: {e:#}"));
ExitCode::FAILURE
}
}
}
}