use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::info;
use is_terminal::IsTerminal;
use bevy_fairy::server::brp_client::BrpClient;
use bevy_fairy::server::config::Config;
use bevy_fairy::server::error::Result;
use bevy_fairy::server::mcp_server_v2;
#[tokio::main]
async fn main() -> Result<()> {
std::panic::set_hook(Box::new(|panic_info| {
let payload = 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 payload".to_string()
};
let location = if let Some(location) = panic_info.location() {
format!("{}:{}:{}", location.file(), location.line(), location.column())
} else {
"Unknown location".to_string()
};
eprintln!("FATAL PANIC in bevy-fairy: {} at {}", payload, location);
eprintln!("This indicates a critical bug that should be reported.");
eprintln!("Stack trace should appear above this message.");
std::io::Write::flush(&mut std::io::stderr()).unwrap_or(());
}));
let args: Vec<String> = std::env::args().collect();
const LEGACY_STDIO_ALIASES: &[&str] = &["stdio", "serve"];
const KNOWN_ARGS: &[&str] = &["--stdio", "--help", "-h", "--version", "-V"];
if let Some(unknown) = args.iter().skip(1).find(|arg| {
!KNOWN_ARGS.contains(&arg.as_str()) && !LEGACY_STDIO_ALIASES.contains(&arg.as_str())
}) {
eprintln!("bevy-fairy: unknown argument `{unknown}`");
eprintln!("valid arguments: {}", KNOWN_ARGS.join(", "));
eprintln!("try `bevy-fairy --help`");
std::process::exit(2);
}
if args.iter().any(|arg| arg == "--version" || arg == "-V") {
println!("bevy-fairy {}", env!("CARGO_PKG_VERSION"));
return Ok(());
}
if args.iter().any(|arg| arg == "--help" || arg == "-h") {
println!("bevy-fairy {}", env!("CARGO_PKG_VERSION"));
println!("\nUsage: {} [OPTIONS]", args[0]);
println!("\nOptions:");
println!(" --stdio Run in stdio mode (default for Claude Code)");
println!(" --help, -h Show this help message");
println!(" --version, -V Print the version and exit");
println!("\nEnvironment variables:");
println!(" BEVY_BRP_HOST Bevy Remote Protocol host (default: localhost)");
println!(" BEVY_BRP_PORT Bevy Remote Protocol port (default: 15702)");
println!(" RUST_LOG Logging level (default: info)");
return Ok(());
}
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.with_writer(std::io::stderr)
.with_ansi(false)
.init();
let config = Config::from_env()?;
let explicit = args.iter().skip(1).any(|arg| arg == "--stdio" || LEGACY_STDIO_ALIASES.contains(&arg.as_str()))
|| std::env::var("MCP_TRANSPORT").map(|t| t == "stdio").unwrap_or(false);
if !explicit && std::io::stdin().is_terminal() {
eprintln!("bevy-fairy speaks MCP over stdio and was started from a terminal.");
eprintln!("Register it in an MCP client as `bevy-fairy --stdio`, or pass --stdio to run it by hand.");
std::process::exit(2);
}
info!("Starting bevy-fairy in stdio mode");
run_stdio_mode(config).await
}
async fn run_stdio_mode(config: Config) -> Result<()> {
let brp_client = Arc::new(RwLock::new(BrpClient::new(&config)));
{
let client = brp_client.read().await;
client.init().await?;
}
let server = mcp_server_v2::McpServerV2::new(config, brp_client)?;
server.run_stdio().await
}