use anyhow::Context as _;
use clap::Parser as _;
use rmcp::ServiceExt as _;
use rmcp::transport::stdio;
use tailscale_mcp::config::{Cli, Command, Config, HttpConfig};
use tailscale_mcp::server::{self, Backends, Startup};
use tailscale_mcp::subcommands;
#[tokio::main]
async fn main() -> anyhow::Result<std::process::ExitCode> {
let cli = Cli::parse();
let asked = cli.command.clone();
if matches!(asked, Some(Command::Version)) {
return Ok(subcommands::version().emit());
}
let config = Config::resolve(cli)?;
init_tracing(&config.log_filter);
if let Some(command) = asked {
return Ok(match command {
Command::Diagnose { json } => {
subcommands::diagnose(&config, Backends::discover(&config), json).await
}
Command::Tools { json } => subcommands::tools(&config, json),
Command::Version => subcommands::version(),
Command::Setup { client } => subcommands::setup(client, &config),
Command::Policy { action } => {
subcommands::policy(&config, Backends::discover(&config), &action).await
}
}
.emit());
}
let backends = Backends::discover(&config);
let startup = server::build(&config, tailscale_mcp::tools::entries(), backends).await?;
for note in &startup.notes {
eprintln!("tailscale-mcp: {note}");
}
match &config.http {
Some(http) => serve_http(http, startup).await,
None => serve_stdio(startup).await,
}?;
Ok(std::process::ExitCode::SUCCESS)
}
async fn serve_stdio(startup: Startup) -> anyhow::Result<()> {
let service = startup
.server
.serve(stdio())
.await
.context("could not start the stdio transport")?;
service
.waiting()
.await
.context("the session ended abnormally")?;
Ok(())
}
async fn serve_http(settings: &HttpConfig, startup: Startup) -> anyhow::Result<()> {
let guard = tailscale_mcp::http::Guard::for_session(
settings,
&startup.server.context().identity.last_known(),
startup.peers.clone(),
);
eprintln!(
"tailscale-mcp: serving HTTP on {} at {}, health at {}",
settings.bind,
tailscale_mcp::http::MCP_PATH,
tailscale_mcp::http::HEALTH_PATH
);
tailscale_mcp::http::serve(settings, guard, startup.server.clone())
.await
.with_context(|| format!("could not serve HTTP on {}", settings.bind))
}
fn init_tracing(filter: &str) {
use tracing_subscriber::EnvFilter;
let filter = EnvFilter::try_new(filter).unwrap_or_else(|_| EnvFilter::new("warn"));
tracing_subscriber::fmt()
.with_env_filter(filter)
.with_writer(std::io::stderr)
.with_ansi(false)
.init();
}