use std::path::PathBuf;
use std::sync::Arc;
use agentstatedeveloper_core::Engine;
use agentstatedeveloper_mcp::AsdMcpServer;
use anyhow::{Context, Result};
use rmcp::ServiceExt;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() -> Result<()> {
let _telemetry = oxtel::builder()
.with_default_service_name("asd-mcp")
.with_resource_attribute(oxtel::KeyValue::new(
"service.version",
env!("CARGO_PKG_VERSION"),
))
.install()?;
let (db_path, track_registry) = match std::env::var("ASD_DB") {
Ok(p) if !p.is_empty() => (PathBuf::from(p), false),
_ => match agentstatedeveloper_core::registry::find_db_upwards() {
Some(db) => {
tracing::info!(?db, "resolved db via cwd walk-up (per-session isolation)");
(db, false)
}
None => (resolve_db_from_registry()?, true),
},
};
tracing::info!(?db_path, track_registry, "starting asd-mcp stdio server");
let mut engine = Engine::open_sqlite(&db_path)
.with_context(|| format!("failed to open ASD db at {}", db_path.display()))?;
if let Ok(policy_path) = std::env::var("ASD_POLICY") {
let path = PathBuf::from(&policy_path);
engine.load_policy_file(&path).with_context(|| {
format!(
"failed to load ASD_POLICY policy file at {}",
path.display()
)
})?;
tracing::info!(policy = %path.display(), "loaded ASD policy file");
}
let audit_log_path: Option<PathBuf> = std::env::var("ASD_AUDIT_LOG").ok().map(PathBuf::from);
if audit_log_path.is_some() {
tracing::warn!(
"ASD_AUDIT_LOG set but asd-mcp is OSS — no new events \
will be written (tamper-evident sink is commercial). \
Existing logs are readable via audit_tail."
);
}
let shared = Arc::new(Mutex::new(engine));
let server = AsdMcpServer::with_registry_tracking(
shared,
db_path.clone(),
audit_log_path,
track_registry,
);
let service = oxtel_rmcp::Instrumented::new(server)
.with_transport("pipe")
.serve(rmcp::transport::stdio())
.await
.map_err(|e| anyhow::anyhow!("MCP server error: {}", e))?;
service.waiting().await?;
tracing::info!("asd-mcp shut down");
Ok(())
}
fn resolve_db_from_registry() -> Result<PathBuf> {
use agentstatedeveloper_core::registry::Registry;
let cwd_hint = cwd_db_hint();
let reg = Registry::load().context(cwd_aware_error(
"ASD_DB not set and could not read repo registry.",
&cwd_hint,
))?;
if let Some(active) = reg.active() {
tracing::info!(name = %active.name, "resolved db from registry active repo");
return Ok(active.path.clone());
}
let known: Vec<String> = reg.list().iter().map(|e| e.name.clone()).collect();
let base = if known.is_empty() {
"Registry is empty.".to_string()
} else {
format!(
"No active repo. Run `asd repo use <name>` (known: {}).",
known.join(", ")
)
};
Err(anyhow::anyhow!(cwd_aware_error(&base, &cwd_hint)))
}
fn cwd_db_hint() -> Option<PathBuf> {
let candidate = std::env::current_dir().ok()?.join(".asd-state.db");
if candidate.is_file() {
Some(candidate)
} else {
None
}
}
fn cwd_aware_error(base: &str, cwd_hint: &Option<PathBuf>) -> String {
match cwd_hint {
Some(p) => format!(
"{base} Detected ./.asd-state.db at the current working \
directory — start with `ASD_DB={}` to use it, or run \
`asd repo add` then `asd repo use <name>` to register it.",
p.display()
),
None => format!(
"{base} Run `asd repo add` then `asd repo use <name>`, \
or start with ASD_DB=<path>."
),
}
}