Skip to main content

mcp_memory/
lib.rs

1pub mod actions;
2pub mod config;
3pub mod errors;
4pub mod http;
5pub mod ivf;
6pub mod kg;
7pub mod protocol;
8pub mod server;
9pub mod tls;
10pub mod tools;
11pub mod types;
12pub mod vector_actions;
13pub mod vector_store;
14
15use clap::{Parser, ValueEnum};
16use usearch::{MetricKind, ScalarKind};
17use vector_store::{IndexKind, VectorConfig};
18
19/// ANN index backend selectable from the CLI.
20#[derive(Clone, Copy, Debug, ValueEnum)]
21pub enum VecIndex {
22    /// usearch HNSW graph index (default): best recall/latency.
23    Hnsw,
24    /// IVF-Flat: k-means partitioned, lighter memory, fast to build/rebuild.
25    Ivf,
26}
27
28impl From<VecIndex> for IndexKind {
29    fn from(v: VecIndex) -> Self {
30        match v {
31            VecIndex::Hnsw => IndexKind::Hnsw,
32            VecIndex::Ivf => IndexKind::Ivf,
33        }
34    }
35}
36
37/// Distance metric for the vector index.
38#[derive(Clone, Copy, Debug, ValueEnum)]
39pub enum VecMetric {
40    /// Cosine similarity (default; good for normalized embeddings).
41    Cos,
42    /// Inner / dot product.
43    Ip,
44    /// Squared Euclidean (L2) distance.
45    L2sq,
46}
47
48impl From<VecMetric> for MetricKind {
49    fn from(m: VecMetric) -> Self {
50        match m {
51            VecMetric::Cos => MetricKind::Cos,
52            VecMetric::Ip => MetricKind::IP,
53            VecMetric::L2sq => MetricKind::L2sq,
54        }
55    }
56}
57
58/// Scalar representation stored in the index (lower precision = less memory).
59#[derive(Clone, Copy, Debug, ValueEnum)]
60pub enum VecQuant {
61    /// 32-bit float (default; full precision).
62    F32,
63    /// 16-bit half-precision IEEE float (half the memory, slight recall loss).
64    F16,
65    /// 16-bit brain float (half the memory, wider range than f16).
66    Bf16,
67    /// 8-bit integer quantization (quarter the memory).
68    I8,
69}
70
71impl From<VecQuant> for ScalarKind {
72    fn from(q: VecQuant) -> Self {
73        match q {
74            VecQuant::F32 => ScalarKind::F32,
75            VecQuant::F16 => ScalarKind::F16,
76            VecQuant::Bf16 => ScalarKind::BF16,
77            VecQuant::I8 => ScalarKind::I8,
78        }
79    }
80}
81
82/// Wire transport the server listens on. The JSON-RPC/MCP semantics are
83/// identical across all three — only the framing differs.
84#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
85#[clap(rename_all = "lowercase")]
86pub enum Transport {
87    /// Newline-delimited JSON-RPC over stdin/stdout (default; for Claude
88    /// Desktop / Claude Code and other process-spawning clients).
89    Stdio,
90    /// Newline-delimited JSON-RPC over a TCP socket (one message per line),
91    /// accepting many concurrent connections.
92    Tcp,
93    /// MCP Streamable HTTP: POST JSON-RPC to `/mcp` (responses as JSON or, when
94    /// the client `Accept`s it, an SSE stream), plus `GET /mcp` for a standalone
95    /// server→client SSE stream.
96    Http,
97}
98
99#[derive(Parser, Debug)]
100#[command(name = "MCP Memory Server")]
101#[command(about = "Knowledge graph memory server for MCP — entities, relations, and observations persisted in SQLite with FTS5 search", long_about = None)]
102pub struct Args {
103    /// Path to the memory file
104    #[arg(short = 'f', long = "memory-file")]
105    pub memory_file: Option<String>,
106
107    /// Transport to listen on: stdio, tcp, or http
108    #[arg(short = 't', long = "transport", value_enum, default_value_t = Transport::Stdio)]
109    pub transport: Transport,
110
111    /// Address to bind for the `tcp` and `http` transports
112    #[arg(short = 'b', long = "bind", default_value = "127.0.0.1:8080")]
113    pub bind: String,
114
115    /// Log level
116    #[arg(short, long, default_value = "info")]
117    pub log_level: String,
118
119    /// Bearer token required on the `tcp` (first line) and `http`
120    /// (`Authorization` header) transports. Overrides `--auth-token-file` and
121    /// the `MCP_MEMORY_AUTH_TOKEN` env var. stdio is never authenticated.
122    #[arg(long = "auth-token")]
123    pub auth_token: Option<String>,
124
125    /// Path to a file whose trimmed contents are the bearer token. An empty
126    /// file is rejected (fail closed). Ignored if `--auth-token` is set.
127    #[arg(long = "auth-token-file")]
128    pub auth_token_file: Option<String>,
129
130    /// SQLite mmap size in bytes (default: 256 MiB).
131    #[arg(long = "mmap-size", default_value_t = 268435456)]
132    pub mmap_size: i64,
133
134    /// SQLite page size in bytes; power of two (default: 4096, matches the Linux
135    /// page / filesystem block size). Only applies to a freshly-created database.
136    #[arg(long = "page-size", default_value_t = 4096)]
137    pub page_size: i64,
138
139    /// SQLite page cache size in MiB (default: 50).
140    #[arg(long = "cache-size-mb", default_value_t = 50)]
141    pub cache_size_mb: i64,
142
143    /// SQLite busy timeout in milliseconds (default: 5000).
144    #[arg(long = "busy-timeout-ms", default_value_t = 5000)]
145    pub busy_timeout_ms: u64,
146
147    /// Interval in milliseconds for a background `wal_checkpoint(PASSIVE)` that
148    /// bounds the durability window in async mode (default: 250). 0 disables it.
149    #[arg(long = "wal-flush-ms", default_value_t = 250)]
150    pub wal_flush_ms: u64,
151
152    /// Entity-metadata LRU cache capacity (0 falls back to 10000).
153    #[arg(long = "lru-cache-size", default_value_t = 10000)]
154    pub lru_cache_size: usize,
155
156    /// Number of read-only SQLite connections backing concurrent reads. WAL
157    /// mode allows readers to run in parallel with each other and the single
158    /// writer; a larger pool raises read concurrency at the cost of a little
159    /// memory (each connection carries its own page cache). `0` (default)
160    /// auto-scales to the CPU count, clamped to [1, 32].
161    #[arg(long = "read-pool-size", default_value_t = 0)]
162    pub read_pool_size: usize,
163
164    /// Path to a PEM certificate chain to serve the `http` transport over TLS
165    /// (HTTPS). Requires --tls-key. Falls back to the MCP_TLS_CERT env var.
166    /// When unset, the `http` transport stays plaintext.
167    #[arg(long = "tls-cert")]
168    pub tls_cert: Option<String>,
169
170    /// Path to the PEM private key matching --tls-cert. Falls back to the
171    /// MCP_TLS_KEY env var.
172    #[arg(long = "tls-key")]
173    pub tls_key: Option<String>,
174
175    /// Enable vector / semantic search: exposes the `vector_*` and
176    /// `hybrid_search` tools backed by a usearch HNSW index. Off by default
177    /// (a pure knowledge-graph server). The `--embedding-dims` / `--vec-*` flags
178    /// only take effect when this is set.
179    #[arg(long = "vectors", default_value_t = false)]
180    pub vectors: bool,
181
182    /// Embedding dimension for vector search (default: 384). Requires --vectors.
183    #[arg(long = "embedding-dims", default_value_t = 384)]
184    pub embedding_dims: u32,
185
186    /// Distance metric for the vector index. Requires --vectors.
187    #[arg(long = "vec-metric", value_enum, default_value_t = VecMetric::Cos)]
188    pub vec_metric: VecMetric,
189
190    /// Scalar quantization for the vector index (lower = less memory). Requires --vectors.
191    #[arg(long = "vec-quantization", value_enum, default_value_t = VecQuant::F32)]
192    pub vec_quantization: VecQuant,
193
194    /// HNSW graph degree `M` (higher = better recall, more memory). Requires --vectors.
195    #[arg(long = "vec-connectivity", default_value_t = 16)]
196    pub vec_connectivity: usize,
197
198    /// HNSW `efConstruction` (higher = better index quality, slower inserts). Requires --vectors.
199    #[arg(long = "vec-expansion-add", default_value_t = 200)]
200    pub vec_expansion_add: usize,
201
202    /// HNSW `efSearch` (higher = better recall, slower queries). Requires --vectors.
203    #[arg(long = "vec-expansion-search", default_value_t = 50)]
204    pub vec_expansion_search: usize,
205
206    /// ANN index backend: `hnsw` (default) or `ivf` (IVF-Flat). Requires --vectors.
207    #[arg(long = "vec-index", value_enum, default_value_t = VecIndex::Hnsw)]
208    pub vec_index: VecIndex,
209
210    /// IVF: number of Voronoi cells / centroids (default: 256). Requires --vec-index ivf.
211    #[arg(long = "ivf-nlist", default_value_t = 256)]
212    pub ivf_nlist: usize,
213
214    /// IVF: cells probed per query — higher = better recall, slower (default: 8).
215    /// Requires --vec-index ivf.
216    #[arg(long = "ivf-nprobe", default_value_t = 8)]
217    pub ivf_nprobe: usize,
218}
219
220impl Args {
221    /// Build the vector index configuration from the `--embedding-dims` /
222    /// `--vec-*` / `--ivf-*` flags. Only meaningful when `--vectors` is set.
223    pub fn vector_config(&self) -> VectorConfig {
224        VectorConfig {
225            dims: self.embedding_dims,
226            index_kind: self.vec_index.into(),
227            metric: self.vec_metric.into(),
228            quantization: self.vec_quantization.into(),
229            connectivity: self.vec_connectivity,
230            expansion_add: self.vec_expansion_add,
231            expansion_search: self.vec_expansion_search,
232            ivf_nlist: self.ivf_nlist,
233            ivf_nprobe: self.ivf_nprobe,
234        }
235    }
236}