use rmcp::ServiceExt;
use velesdb_memory::mcp::McpServer;
use velesdb_memory::{DynEmbedder, HashEmbedder, MemoryService, DEFAULT_DIMENSION};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let store_path = std::env::var("VELESDB_MEMORY_PATH")
.unwrap_or_else(|_| "./velesdb-memory-store".to_owned());
let embedder = build_embedder()?;
let service = MemoryService::open(&store_path, embedder)?;
let server = apply_default_ttl(build_server(service)?)?;
tokio::runtime::Runtime::new()?.block_on(async move {
let running = server
.serve((tokio::io::stdin(), tokio::io::stdout()))
.await?;
running.waiting().await?;
Ok::<(), Box<dyn std::error::Error>>(())
})
}
fn apply_default_ttl(server: McpServer) -> Result<McpServer, Box<dyn std::error::Error>> {
match std::env::var("VELESDB_MEMORY_DEFAULT_TTL") {
Ok(raw) => {
let ttl_seconds: u64 = raw.trim().parse().map_err(|_| {
format!(
"VELESDB_MEMORY_DEFAULT_TTL must be a non-negative integer (seconds), got '{raw}'"
)
})?;
Ok(server.with_default_ttl(ttl_seconds))
}
Err(_) => Ok(server),
}
}
#[cfg(feature = "extract")]
fn build_server(
service: MemoryService<DynEmbedder>,
) -> Result<McpServer, Box<dyn std::error::Error>> {
let server = McpServer::new(service);
match std::env::var("VELESDB_MEMORY_EXTRACTOR").as_deref() {
Ok("ollama") => Ok(server.with_extractor(build_ollama_extractor()?)),
Ok("none") | Err(_) => Ok(server),
Ok(other) => {
Err(format!("unknown VELESDB_MEMORY_EXTRACTOR '{other}' (expected 'ollama')").into())
}
}
}
#[cfg(not(feature = "extract"))]
#[allow(clippy::unnecessary_wraps)]
fn build_server(
service: MemoryService<DynEmbedder>,
) -> Result<McpServer, Box<dyn std::error::Error>> {
Ok(McpServer::new(service))
}
#[cfg(feature = "extract")]
fn build_ollama_extractor() -> Result<velesdb_memory::DynExtractor, Box<dyn std::error::Error>> {
use std::sync::Arc;
use velesdb_memory::extract::DEFAULT_OLLAMA_URL;
use velesdb_memory::OllamaExtractor;
let url = std::env::var("VELESDB_MEMORY_EXTRACTOR_URL")
.unwrap_or_else(|_| DEFAULT_OLLAMA_URL.to_owned());
let model = std::env::var("VELESDB_MEMORY_EXTRACTOR_MODEL").map_err(|_| {
"VELESDB_MEMORY_EXTRACTOR=ollama requires VELESDB_MEMORY_EXTRACTOR_MODEL \
(e.g. qwen3.6:35b-mlx)"
})?;
Ok(Arc::new(OllamaExtractor::new(url, model)))
}
fn build_embedder() -> Result<DynEmbedder, Box<dyn std::error::Error>> {
match std::env::var("VELESDB_MEMORY_EMBEDDER").as_deref() {
Ok("ollama") => build_ollama_embedder(),
Ok("hash") | Err(_) => Ok(Box::new(HashEmbedder::new(DEFAULT_DIMENSION))),
Ok(other) => Err(format!(
"unknown VELESDB_MEMORY_EMBEDDER '{other}' (expected 'hash' or 'ollama')"
)
.into()),
}
}
#[cfg(feature = "ollama")]
fn build_ollama_embedder() -> Result<DynEmbedder, Box<dyn std::error::Error>> {
use velesdb_memory::{OllamaEmbedder, DEFAULT_OLLAMA_MODEL, DEFAULT_OLLAMA_URL};
let url = std::env::var("VELESDB_MEMORY_OLLAMA_URL")
.unwrap_or_else(|_| DEFAULT_OLLAMA_URL.to_owned());
let model = std::env::var("VELESDB_MEMORY_OLLAMA_MODEL")
.unwrap_or_else(|_| DEFAULT_OLLAMA_MODEL.to_owned());
Ok(Box::new(OllamaEmbedder::new(url, model)?))
}
#[cfg(not(feature = "ollama"))]
fn build_ollama_embedder() -> Result<DynEmbedder, Box<dyn std::error::Error>> {
Err("the 'ollama' embedder requires building with `--features ollama`".into())
}