mod command;
mod globals;
mod meta;
mod response;
mod rmcp_server;
mod serde_utils;
mod tool;
mod tools;
mod version;
mod workspace;
use anyhow::Context;
use clap::Parser;
use command::execute_command;
use response::Response;
use rmcp::ServiceExt;
use rmcp::service::QuitReason;
use tool::Tool;
use tracing_appender::rolling;
use tracing_subscriber::{EnvFilter, fmt};
use version::AppVersion;
const RMCP_VERSION: &str = env!("RMCP_VERSION");
#[derive(Parser, Debug)]
#[command(author, version = AppVersion, about = "Rust MCP Server", long_about = None)]
struct Args {
#[arg(long, default_value = "info")]
log_level: String,
#[arg(long)]
log_file: Option<String>,
#[arg(long = "disable-tool")]
disabled_tools: Vec<String>,
#[arg(long)]
workspace: Option<String>,
#[arg(long)]
registry: Option<String>,
#[arg(long)]
generate_docs: Option<String>,
#[arg(long)]
no_recommendations: bool,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Args::parse();
let env_filter = EnvFilter::new(&args.log_level);
if let Some(ref path) = args.log_file {
use std::path::Path;
let log_path = Path::new(path);
let (dir, file_name) = match (log_path.parent(), log_path.file_name()) {
(Some(d), Some(f)) => (d, f),
_ => (Path::new("."), log_path.as_os_str()),
};
let file_appender = rolling::daily(dir, file_name);
fmt()
.with_env_filter(env_filter)
.with_writer(file_appender)
.with_ansi(false)
.init();
}
tracing::info!("Starting Rust MCP Server: {args:?}");
tracing::info!("Server version: {}", AppVersion::version());
tracing::info!("RMCP crate version: {RMCP_VERSION}");
let detect_workspace = args.workspace.is_none();
if let Some(workspace) = args.workspace {
tracing::info!("Workspace root has been overridden: {workspace}");
globals::set_workspace_root(workspace);
} else {
tracing::info!("No workspace root specified, workspace auto-detection enabled");
}
if let Some(registry) = args.registry {
tracing::info!("Default cargo registry has been set: {registry}");
globals::set_default_registry(registry);
}
let server = rmcp_server::Server::new(
&args.disabled_tools,
args.no_recommendations,
detect_workspace,
);
if let Some(output_file) = args.generate_docs {
tracing::info!("Generating documentation to: {output_file}");
let docs = server.generate_markdown_docs();
std::fs::write(&output_file, docs).context("Failed to write documentation file")?;
println!("Documentation generated successfully: {output_file}");
return Ok(());
}
let service = server
.serve(rmcp::transport::stdio())
.await
.context("Failed to start server")?;
eprintln!("Rust MCP Server started on stdio");
let result = service.waiting().await;
match result {
Ok(QuitReason::Closed) => tracing::info!("Server closed normally"),
Ok(QuitReason::Cancelled) => tracing::info!("Server was cancelled"),
Ok(QuitReason::JoinError(error)) => {
tracing::error!("Server join error: {error}");
return Err(error.into());
}
Ok(reason) => {
tracing::info!("Server exited with reason: {reason:?}");
}
Err(error) => {
tracing::error!("Server encountered an error: {error}");
return Err(error.into());
}
}
Ok(())
}