mod bridge;
mod server;
mod tools;
use clap::Parser;
use rmcp::ServiceExt;
use tracing_subscriber::EnvFilter;
use crate::bridge::EmbedderBackend;
#[derive(Parser, Debug)]
#[command(name = "semantic-memory-mcp")]
#[command(about = "MCP server for semantic-memory — local-first knowledge management")]
struct Cli {
#[arg(long)]
memory_dir: String,
#[arg(long, default_value = "candle")]
embedder: EmbedderBackend,
#[arg(long)]
embedding_url: Option<String>,
#[arg(long)]
embedding_model: Option<String>,
#[arg(long)]
embedding_dims: Option<usize>,
}
fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.with_writer(std::io::stderr)
.init();
let cli = Cli::parse();
eprintln!("semantic-memory-mcp starting...");
eprintln!(" memory_dir: {}", cli.memory_dir);
eprintln!(
" embedder: {:?}",
cli.embedder
);
match cli.embedder {
EmbedderBackend::Candle => {
eprintln!(
" model: {} ({}d) — in-process Candle, CPU-only",
cli.embedding_model.as_deref().unwrap_or("nomic-embed-text"),
cli.embedding_dims.unwrap_or(768)
);
}
EmbedderBackend::Ollama => {
eprintln!(
" embedding: {} @ {} ({}d)",
cli.embedding_model.as_deref().unwrap_or("nomic-embed-text"),
cli.embedding_url.as_deref().unwrap_or("http://localhost:11434"),
cli.embedding_dims.unwrap_or(768)
);
}
EmbedderBackend::Mock => {
eprintln!(
" mock embedder ({}d) — for testing only",
cli.embedding_dims.unwrap_or(768)
);
}
}
let bridge_config = bridge::BridgeConfig::from_args(
&cli.memory_dir,
cli.embedder,
cli.embedding_url.as_deref(),
cli.embedding_model.as_deref(),
cli.embedding_dims,
);
let bridge = bridge::MemoryBridge::open(bridge_config)?;
let server = server::SemanticMemoryServer::new(bridge);
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.worker_threads(4)
.build()?;
rt.block_on(async {
let service = server.serve(rmcp::transport::stdio()).await?;
service.waiting().await?;
Ok::<(), anyhow::Error>(())
})?;
Ok(())
}