//! beamdev - Main entry point. //! //! This binary starts the MCP server with stdio transport. use beamdev::cookie::{DEFAULT_COOKIE_FILE, read_cookie}; use beamdev::server::{BeamdevServer, FormatterMode}; use clap::Parser; use rmcp::ServiceExt; use rmcp::transport::stdio; use std::path::PathBuf; use tracing_subscriber::EnvFilter; /// beamdev - a BEAM development MCP server for coding agents /// /// Connects to one Erlang/BEAM node, named with --node, and runs code on it. /// The connection is re-established whenever the node restarts. Code runs /// unsandboxed, so point this at development nodes only. #[derive(Parser, Debug)] #[command(name = "beamdev")] #[command(version, about, long_about = None)] struct Args { /// Output format mode for displaying Erlang terms. #[arg(long, default_value = "erlang", value_parser = parse_mode)] mode: FormatterMode, /// Log level (trace, debug, info, warn, error). #[arg(long, default_value = "info")] log_level: String, /// The node to work against, e.g. "myapp@localhost". #[arg(long, value_parser = parse_node)] node: String, /// File holding the Erlang cookie the node authenticates with. #[arg(long, default_value = DEFAULT_COOKIE_FILE)] cookie_file: PathBuf, } fn parse_mode(s: &str) -> Result { s.parse() } fn parse_node(s: &str) -> Result { if !s.contains('@') { return Err(format!( "invalid node name '{}'. Expected format: 'name@host'", s )); } Ok(s.to_string()) } #[tokio::main] async fn main() -> Result<(), Box> { let args = Args::parse(); let filter = EnvFilter::try_new(&args.log_level).unwrap_or_else(|_| EnvFilter::new("info")); tracing_subscriber::fmt() .with_env_filter(filter) .with_writer(std::io::stderr) .init(); let cookie = read_cookie(&args.cookie_file).map_err(|e| { format!( "cannot read the cookie from {}: {e}", args.cookie_file.display() ) })?; tracing::info!( "Starting beamdev against {} (mode: {})", args.node, args.mode ); // The node is often still booting when the client launches us, so the // connection is the supervisor's job rather than a precondition for // serving. let server = BeamdevServer::new(args.mode, args.node, cookie); server.start(); let service = server.serve(stdio()).await?; service.waiting().await?; Ok(()) }