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