obsidian-mcp 1.0.2

MCP server for Obsidian vaults — direct filesystem access for AI agents
Documentation
use tracing_subscriber::EnvFilter;

use obsidian_mcp::daemon::home::{self, semantic_home_paths};
use obsidian_mcp::daemon::server::{self, DaemonServerConfig, IpcEndpoint};

const DEFAULT_SEMANTIC_MODEL: &str = "BAAI/bge-small-en-v1.5";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let log_level = std::env::var("OBSIDIAN_LOG_LEVEL").unwrap_or_else(|_| "info".to_string());
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::new(log_level))
        .with_writer(std::io::stderr)
        .init();

    let semantic_home = home::resolve_semantic_home()?;
    let paths = semantic_home_paths(&semantic_home);
    home::ensure_home_layout(&paths)?;

    let endpoint =
        resolve_endpoint_from_env().unwrap_or_else(|| home::default_ipc_endpoint(&paths));
    let model_name = std::env::var("OBSIDIAN_SEMANTIC_MODEL")
        .unwrap_or_else(|_| DEFAULT_SEMANTIC_MODEL.to_string());

    tracing::info!(
        endpoint = %endpoint.endpoint_string(),
        semantic_home = %semantic_home.display(),
        "starting obsidian-semanticd"
    );

    let config = DaemonServerConfig {
        endpoint,
        model_name,
        semantic_home,
    };
    server::run(config).await?;
    Ok(())
}

fn resolve_endpoint_from_env() -> Option<IpcEndpoint> {
    let raw = std::env::var("OBSIDIAN_SEMANTIC_ENDPOINT").ok()?;
    if raw.trim().is_empty() {
        return None;
    }
    #[cfg(unix)]
    {
        Some(IpcEndpoint::UnixSocket(std::path::PathBuf::from(raw)))
    }
    #[cfg(windows)]
    {
        Some(IpcEndpoint::NamedPipe(raw))
    }
}