use clap::Parser;
use turbovault_core::VaultConfig;
use turbovault_core::cache::VaultCache;
use turbovault::ObsidianMcpServer;
use turbovault_tools::OutputFormat;
use std::path::PathBuf;
use turbomcp_server::observability::ObservabilityConfig;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(short, long, env = "OBSIDIAN_VAULT_PATH")]
vault: Option<PathBuf>,
#[arg(short, long, default_value = "development")]
profile: String,
#[arg(short, long, default_value = "stdio")]
transport: String,
#[arg(long, default_value = "3000")]
port: u16,
#[arg(long, default_value = "json")]
output_format: String,
#[arg(long, action = clap::ArgAction::SetTrue)]
init: bool,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let output_format = if args.transport == "stdio" {
OutputFormat::Json
} else {
args.output_format.parse::<OutputFormat>()?
};
let _observability_guard = if args.transport == "stdio" {
let obs_config = ObservabilityConfig::default()
.with_service_name("turbovault")
.with_service_version(env!("CARGO_PKG_VERSION"))
.with_log_level(if args.profile == "production" {
"info,turbo_vault=debug".to_string()
} else {
"debug".to_string()
})
.enable_security_auditing()
.enable_performance_monitoring();
Some(obs_config.init()?)
} else {
use simple_logger::SimpleLogger;
match output_format {
OutputFormat::Json => {
let obs_config = ObservabilityConfig::default()
.with_service_name("turbovault")
.with_service_version(env!("CARGO_PKG_VERSION"))
.with_log_level(if args.profile == "production" {
"info,turbo_vault=debug".to_string()
} else {
"debug".to_string()
})
.enable_security_auditing()
.enable_performance_monitoring();
Some(obs_config.init()?)
}
OutputFormat::Human | OutputFormat::Text => {
SimpleLogger::new()
.with_level(if args.profile == "production" {
log::LevelFilter::Info
} else {
log::LevelFilter::Debug
})
.with_utc_timestamps()
.init()
.map_err(|e| format!("Failed to initialize logger: {}", e))?;
None
}
}
};
log::info!("Turbo Vault MCP Server v{}", env!("CARGO_PKG_VERSION"));
log::info!("Transport: {} | Log format: {:?}", args.transport, output_format);
let server =
ObsidianMcpServer::new().map_err(|e| format!("Failed to create MCP server: {}", e))?;
log::info!("MCP Server created (vault-agnostic mode)");
if let Err(e) = server.init_cache().await {
log::warn!("Failed to initialize server cache: {}. Cache persistence will be unavailable.", e);
}
match VaultCache::init().await {
Ok(cache) => {
log::info!(
"Project cache initialized: {} | Cache dir: {}",
cache.project_id(),
cache.project_cache_dir().display()
);
let cached_vaults = cache
.load_vaults()
.await
.unwrap_or_else(|e| {
log::warn!("Failed to load cached vaults: {}", e);
vec![]
});
if !cached_vaults.is_empty() {
log::info!(
"Recovering {} cached vaults for project {}",
cached_vaults.len(),
cache.project_id()
);
for vault_config in cached_vaults {
match server.multi_vault().add_vault(vault_config.clone()).await {
Ok(_) => {
log::info!(
"Restored vault from cache: '{}' -> {}",
vault_config.name,
vault_config.path.display()
);
}
Err(e) => {
log::warn!(
"Failed to restore vault '{}': {}. Skipping.",
vault_config.name, e
);
}
}
}
let metadata = cache
.load_metadata()
.await
.unwrap_or_else(|e| {
log::warn!("Failed to load cache metadata: {}", e);
turbovault_core::cache::CacheMetadata {
active_vault: String::new(),
last_updated: 0,
version: 1,
project_id: cache.project_id().to_string(),
working_dir: cache.working_dir().to_string_lossy().to_string(),
}
});
if !metadata.active_vault.is_empty() {
match server
.multi_vault()
.set_active_vault(&metadata.active_vault)
.await
{
Ok(_) => {
log::info!(
"Restored active vault from cache: '{}'",
metadata.active_vault
);
}
Err(e) => {
log::warn!(
"Failed to restore active vault '{}': {}",
metadata.active_vault, e
);
}
}
}
} else {
log::info!("No cached vaults found for this project");
}
}
Err(e) => {
log::warn!("Failed to initialize cache: {}. Continuing without cache recovery.", e);
}
}
if let Some(vault_path) = args.vault {
log::info!("Adding vault from CLI argument: {:?}", vault_path);
let vault_config = VaultConfig::builder("default", &vault_path)
.build()
.map_err(|e| format!("Failed to create vault config: {}", e))?;
server
.multi_vault()
.add_vault(vault_config)
.await
.map_err(|e| format!("Failed to add vault: {}", e))?;
log::info!("Vault registered: default -> {:?}", vault_path);
if args.init {
log::info!("Scanning vault and building link graph...");
log::info!("Vault ready for operations");
}
} else {
log::info!("No vault path provided. Use add_vault MCP tool to register a vault.");
log::info!("Available tools: add_vault, list_vaults, set_active_vault");
}
log::info!("Starting TurboVault Server");
match args.transport.as_str() {
"stdio" => {
log::info!("Running in STDIO mode for MCP protocol");
server.run_stdio().await?;
}
#[cfg(feature = "http")]
"http" => {
let addr = format!("127.0.0.1:{}", args.port);
log::info!("Running HTTP server on {}", addr);
log::info!("Output format: {:?}", output_format);
server.run_http(&addr).await?;
}
#[cfg(feature = "websocket")]
"websocket" => {
let addr = format!("127.0.0.1:{}", args.port);
log::info!("Running WebSocket server on {}", addr);
log::info!("Output format: {:?}", output_format);
server.run_websocket(&addr).await?;
}
#[cfg(feature = "tcp")]
"tcp" => {
let addr = format!("127.0.0.1:{}", args.port);
log::info!("Running TCP server on {}", addr);
log::info!("Output format: {:?}", output_format);
server.run_tcp(&addr).await?;
}
#[cfg(feature = "unix")]
"unix" => {
let socket_path = "/tmp/turbovault.sock".to_string();
log::info!("Running Unix socket server on {}", socket_path);
log::info!("Output format: {:?}", output_format);
server.run_unix(&socket_path).await?;
}
transport => {
#[cfg(not(feature = "http"))]
if transport == "http" {
return Err("HTTP transport not enabled. Rebuild with --features http".into());
}
#[cfg(not(feature = "websocket"))]
if transport == "websocket" {
return Err("WebSocket transport not enabled. Rebuild with --features websocket".into());
}
#[cfg(not(feature = "tcp"))]
if transport == "tcp" {
return Err("TCP transport not enabled. Rebuild with --features tcp".into());
}
#[cfg(not(feature = "unix"))]
if transport == "unix" {
return Err("Unix socket transport not enabled. Rebuild with --features unix".into());
}
return Err(format!(
"Unknown transport '{}'. Valid options: stdio{}{}{}{}",
transport,
if cfg!(feature = "http") { ", http" } else { "" },
if cfg!(feature = "websocket") { ", websocket" } else { "" },
if cfg!(feature = "tcp") { ", tcp" } else { "" },
if cfg!(feature = "unix") { ", unix" } else { "" },
)
.into());
}
}
Ok(())
}