1use std::collections::HashMap;
13use std::path::{Path, PathBuf};
14use std::sync::atomic::{AtomicU64, Ordering};
15use std::sync::{Mutex, Once, OnceLock};
16use std::time::Duration;
17
18use anyhow::{Context, Result};
19use rusqlite::backup::Backup;
20use rusqlite::{Connection, OpenFlags, Row};
21use serde::Serialize;
22
23static REGISTER_VEC_EXTENSION: Once = Once::new();
24
25pub const VERSION_STORE_FILES: &[(&str, &str)] = &[
27 ("gh-2026-03-10", "mcp_store.db"),
28 ("ghec-2026-03-10", "mcp_store_vghec-2026-03-10.db"),
29 ("ghes-3.21", "mcp_store_vghes-3.21.db"),
30 ("ghes-3.20", "mcp_store_vghes-3.20.db"),
31 ("ghes-3.19", "mcp_store_vghes-3.19.db"),
32];
33
34const VERSION_STORE_BYTES: &[(&str, &[u8])] = &[
35 ("gh-2026-03-10", include_bytes!("../../mcp_store.db.zst")),
36 (
37 "ghec-2026-03-10",
38 include_bytes!("../../mcp_store_vghec-2026-03-10.db.zst"),
39 ),
40 (
41 "ghes-3.21",
42 include_bytes!("../../mcp_store_vghes-3.21.db.zst"),
43 ),
44 (
45 "ghes-3.20",
46 include_bytes!("../../mcp_store_vghes-3.20.db.zst"),
47 ),
48 (
49 "ghes-3.19",
50 include_bytes!("../../mcp_store_vghes-3.19.db.zst"),
51 ),
52];
53pub fn resolve_store_path(api_version: &str) -> Result<PathBuf> {
72 static EXTRACTED: OnceLock<Mutex<HashMap<String, PathBuf>>> = OnceLock::new();
84 let extracted = EXTRACTED.get_or_init(|| Mutex::new(HashMap::new()));
85 let mut extracted = extracted.lock().unwrap();
86 if let Some(path) = extracted.get(api_version) {
87 return Ok(path.clone());
88 }
89
90 let file = VERSION_STORE_FILES
91 .iter()
92 .find(|(label, _)| *label == api_version)
93 .map(|(_, file)| *file)
94 .with_context(|| format!("unknown api_version '{api_version}' — run the 'versions' command to see what's available"))?;
95 let bytes = VERSION_STORE_BYTES
96 .iter()
97 .find(|(label, _)| *label == api_version)
98 .map(|(_, bytes)| *bytes)
99 .with_context(|| format!("no embedded store data for api_version '{api_version}'"))?;
100
101 let mut dir = std::env::temp_dir();
102 dir.push(concat!(env!("CARGO_PKG_NAME"), "-store"));
103 std::fs::create_dir_all(&dir)
104 .with_context(|| format!("failed to create temp dir '{}'", dir.display()))?;
105
106 let path = dir.join(file);
107 let decompressed = zstd::stream::decode_all(bytes).with_context(|| {
125 format!("failed to decompress embedded store data for api_version '{api_version}'")
126 })?;
127 static UNIQUE: AtomicU64 = AtomicU64::new(0);
128 let tmp_path = dir.join(format!(
129 "{file}.{}.{}.tmp",
130 std::process::id(),
131 UNIQUE.fetch_add(1, Ordering::Relaxed)
132 ));
133 std::fs::write(&tmp_path, decompressed).with_context(|| {
134 format!(
135 "failed to extract embedded store data to '{}'",
136 tmp_path.display()
137 )
138 })?;
139 let mut rename_attempts = 0u32;
149 loop {
150 match std::fs::rename(&tmp_path, &path) {
151 Ok(()) => break,
152 Err(_) if rename_attempts < 5 => {
153 rename_attempts += 1;
154 std::thread::sleep(Duration::from_millis(50 * u64::from(rename_attempts)));
155 }
156 Err(err) => {
157 return Err(err).with_context(|| {
158 format!(
159 "failed to move extracted store data into place at '{}'",
160 path.display()
161 )
162 });
163 }
164 }
165 }
166
167 extracted.insert(api_version.to_string(), path.clone());
168 Ok(path)
169}
170
171const ENDPOINT_COLUMNS: &str = "operation_id, path, method, summary, description, input_schema, output_schema, auth_scheme_ref";
172
173fn register_vec_extension() {
177 REGISTER_VEC_EXTENSION.call_once(|| unsafe {
178 #[allow(clippy::missing_transmute_annotations)]
179 rusqlite::ffi::sqlite3_auto_extension(Some(std::mem::transmute(
180 sqlite_vec::sqlite3_vec_init as *const (),
181 )));
182 });
183}
184
185pub fn open_store(path: &Path) -> Result<Connection> {
189 register_vec_extension();
190 Connection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_ONLY)
191 .with_context(|| format!("failed to open '{}'", path.display()))
192}
193
194pub fn open_store_read_write(path: &Path) -> Result<Connection> {
199 register_vec_extension();
200 Connection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_WRITE)
201 .with_context(|| format!("failed to open '{}'", path.display()))
202}
203
204pub fn cached_store_connection(api_version: &str) -> Result<&'static Mutex<Connection>> {
222 let path = resolve_store_path(api_version)?;
223 cached_in_memory_connection(api_version, &path)
224}
225
226fn cached_in_memory_connection(cache_key: &str, path: &Path) -> Result<&'static Mutex<Connection>> {
233 static CACHE: OnceLock<Mutex<HashMap<String, &'static Mutex<Connection>>>> = OnceLock::new();
234 let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
235
236 if let Some(conn) = cache.lock().unwrap().get(cache_key) {
237 return Ok(conn);
238 }
239
240 let disk_conn = open_store(path)?;
241 let mut mem_conn =
242 Connection::open_in_memory().context("failed to open an in-memory SQLite connection")?;
243 Backup::new(&disk_conn, &mut mem_conn)
244 .context("failed to start SQLite backup into memory")?
245 .run_to_completion(i32::MAX, Duration::from_millis(0), None)
246 .with_context(|| format!("failed to back up '{}' into memory", path.display()))?;
247 drop(disk_conn);
248
249 let leaked: &'static Mutex<Connection> = Box::leak(Box::new(Mutex::new(mem_conn)));
250 let mut cache = cache.lock().unwrap();
251 Ok(*cache.entry(cache_key.to_string()).or_insert(leaked))
252}
253
254#[derive(Debug, Clone, Serialize)]
255pub struct EndpointRecord {
256 pub operation_id: String,
257 pub path: String,
258 pub method: String,
259 pub summary: Option<String>,
260 pub description: Option<String>,
261 pub input_schema: serde_json::Value,
262 pub output_schema: serde_json::Value,
263 pub auth_scheme_ref: Option<String>,
264}
265
266fn row_to_endpoint(row: &Row) -> rusqlite::Result<EndpointRecord> {
267 let input_schema: String = row.get(5)?;
268 let output_schema: String = row.get(6)?;
269 Ok(EndpointRecord {
270 operation_id: row.get(0)?,
271 path: row.get(1)?,
272 method: row.get(2)?,
273 summary: row.get(3)?,
274 description: row.get(4)?,
275 input_schema: serde_json::from_str(&input_schema).unwrap_or(serde_json::Value::Null),
276 output_schema: serde_json::from_str(&output_schema).unwrap_or(serde_json::Value::Null),
277 auth_scheme_ref: row.get(7)?,
278 })
279}
280
281pub fn get_endpoint(conn: &Connection, operation_id: &str) -> Result<Option<EndpointRecord>> {
282 let mut stmt = conn.prepare(&format!(
283 "SELECT {ENDPOINT_COLUMNS} FROM endpoints WHERE operation_id = ?1"
284 ))?;
285 match stmt.query_row([operation_id], row_to_endpoint) {
286 Ok(record) => Ok(Some(record)),
287 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
288 Err(err) => Err(err.into()),
289 }
290}
291
292pub fn list_endpoints(conn: &Connection) -> Result<Vec<EndpointRecord>> {
293 let mut stmt = conn.prepare(&format!("SELECT {ENDPOINT_COLUMNS} FROM endpoints"))?;
294 let rows = stmt.query_map([], row_to_endpoint)?;
295 Ok(rows.collect::<rusqlite::Result<Vec<_>>>()?)
296}
297
298#[derive(Debug, Clone, Serialize)]
299pub struct SearchResult {
300 pub operation_id: String,
301 pub summary: Option<String>,
302 pub similarity: f64,
303}
304
305pub fn search_endpoints(
320 conn: &Connection,
321 query_embedding: &[f32],
322 limit: usize,
323) -> Result<Vec<SearchResult>> {
324 let blob: Vec<u8> = query_embedding
325 .iter()
326 .flat_map(|value| value.to_le_bytes())
327 .collect();
328
329 let mut stmt = conn.prepare(
330 "SELECT e.operation_id, e.summary, s.distance
331 FROM semantic_endpoints s
332 JOIN endpoints e ON e.operation_id = s.operation_id
333 WHERE s.embedding MATCH ?1 AND k = ?2
334 ORDER BY s.distance",
335 )?;
336 let rows = stmt.query_map(rusqlite::params![blob, limit], |row| {
337 let distance: f64 = row.get(2)?;
338 Ok(SearchResult {
339 operation_id: row.get(0)?,
340 summary: row.get(1)?,
341 similarity: 1.0 - distance,
342 })
343 })?;
344 Ok(rows.collect::<rusqlite::Result<Vec<_>>>()?)
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350
351 #[test]
357 fn every_version_store_file_has_embedded_bytes() {
358 let file_labels: std::collections::HashSet<_> = VERSION_STORE_FILES
359 .iter()
360 .map(|(label, _)| *label)
361 .collect();
362 let byte_labels: std::collections::HashSet<_> = VERSION_STORE_BYTES
363 .iter()
364 .map(|(label, _)| *label)
365 .collect();
366 assert_eq!(file_labels, byte_labels);
367 }
368
369 #[test]
377 fn resolve_store_path_survives_concurrent_calls() {
378 let api_version = VERSION_STORE_FILES[0].0.to_string();
379 let handles: Vec<_> = (0..16)
380 .map(|_| {
381 let api_version = api_version.clone();
382 std::thread::spawn(move || {
383 let path = resolve_store_path(&api_version).unwrap();
384 open_store(&path).unwrap();
385 })
386 })
387 .collect();
388 for handle in handles {
389 handle.join().unwrap();
390 }
391 }
392
393 fn seeded_store(path: &Path) -> Connection {
399 unsafe {
400 #[allow(clippy::missing_transmute_annotations)]
401 rusqlite::ffi::sqlite3_auto_extension(Some(std::mem::transmute(
402 sqlite_vec::sqlite3_vec_init as *const (),
403 )));
404 }
405 let conn = Connection::open(path).unwrap();
406 conn.execute(
407 "CREATE TABLE endpoints (
408 operation_id TEXT PRIMARY KEY,
409 path TEXT NOT NULL,
410 method TEXT NOT NULL,
411 summary TEXT,
412 description TEXT,
413 input_schema TEXT NOT NULL,
414 output_schema TEXT NOT NULL,
415 auth_scheme_ref TEXT
416 )",
417 [],
418 )
419 .unwrap();
420 conn.execute(
421 "CREATE VIRTUAL TABLE semantic_endpoints USING vec0(
422 operation_id TEXT PRIMARY KEY,
423 embedding FLOAT[4]
424 )",
425 [],
426 )
427 .unwrap();
428 conn.execute(
429 "INSERT INTO endpoints (operation_id, path, method, summary, description, input_schema, output_schema, auth_scheme_ref)
430 VALUES ('listWidgets', '/widgets', 'GET', 'List widgets', NULL, '{}', '[]', NULL)",
431 [],
432 )
433 .unwrap();
434 let embedding: Vec<u8> = [1.0f32, 0.0, 0.0, 0.0]
435 .iter()
436 .flat_map(|v| v.to_le_bytes())
437 .collect();
438 conn.execute(
439 "INSERT INTO semantic_endpoints (operation_id, embedding) VALUES ('listWidgets', ?1)",
440 rusqlite::params![embedding],
441 )
442 .unwrap();
443 conn
444 }
445
446 #[test]
447 fn get_endpoint_returns_a_seeded_row() {
448 let dir = tempfile::tempdir().unwrap();
449 let path = dir.path().join("mcp_store.db");
450 let _conn = seeded_store(&path);
451
452 let store = open_store(&path).unwrap();
453 let endpoint = get_endpoint(&store, "listWidgets").unwrap().unwrap();
454 assert_eq!(endpoint.path, "/widgets");
455 assert_eq!(endpoint.method, "GET");
456 assert_eq!(endpoint.summary.as_deref(), Some("List widgets"));
457 }
458
459 #[test]
460 fn get_endpoint_returns_none_for_an_unknown_operation() {
461 let dir = tempfile::tempdir().unwrap();
462 let path = dir.path().join("mcp_store.db");
463 let _conn = seeded_store(&path);
464
465 let store = open_store(&path).unwrap();
466 assert!(get_endpoint(&store, "unknownOp").unwrap().is_none());
467 }
468
469 #[test]
470 fn list_endpoints_returns_every_row() {
471 let dir = tempfile::tempdir().unwrap();
472 let path = dir.path().join("mcp_store.db");
473 let _conn = seeded_store(&path);
474
475 let store = open_store(&path).unwrap();
476 let endpoints = list_endpoints(&store).unwrap();
477 assert_eq!(endpoints.len(), 1);
478 assert_eq!(endpoints[0].operation_id, "listWidgets");
479 }
480
481 #[test]
482 fn search_endpoints_finds_the_nearest_neighbor() {
483 let dir = tempfile::tempdir().unwrap();
484 let path = dir.path().join("mcp_store.db");
485 let _conn = seeded_store(&path);
486
487 let store = open_store(&path).unwrap();
488 let results = search_endpoints(&store, &[1.0, 0.0, 0.0, 0.0], 5).unwrap();
489 assert_eq!(results.len(), 1);
490 assert_eq!(results[0].operation_id, "listWidgets");
491 assert!(results[0].similarity > 0.99);
492 }
493
494 #[test]
495 fn cached_in_memory_connection_serves_seeded_data() {
496 let dir = tempfile::tempdir().unwrap();
497 let path = dir.path().join("mcp_store.db");
498 let _conn = seeded_store(&path);
499
500 let cached = cached_in_memory_connection("cached-serves-seeded-data", &path).unwrap();
501 let endpoint = get_endpoint(&cached.lock().unwrap(), "listWidgets")
502 .unwrap()
503 .unwrap();
504 assert_eq!(endpoint.path, "/widgets");
505
506 let results = search_endpoints(&cached.lock().unwrap(), &[1.0, 0.0, 0.0, 0.0], 5).unwrap();
510 assert_eq!(results.len(), 1);
511 assert_eq!(results[0].operation_id, "listWidgets");
512 }
513
514 #[test]
515 fn cached_in_memory_connection_holds_no_lingering_lock_on_the_disk_file() {
516 let dir = tempfile::tempdir().unwrap();
517 let path = dir.path().join("mcp_store.db");
518 drop(seeded_store(&path));
524
525 let cached = cached_in_memory_connection("cached-releases-disk-handle", &path).unwrap();
526
527 std::fs::remove_file(&path).unwrap();
531
532 let endpoint = get_endpoint(&cached.lock().unwrap(), "listWidgets")
533 .unwrap()
534 .unwrap();
535 assert_eq!(endpoint.path, "/widgets");
536 }
537
538 #[test]
539 fn cached_in_memory_connection_reuses_the_same_connection_for_the_same_key() {
540 let dir = tempfile::tempdir().unwrap();
541 let path = dir.path().join("mcp_store.db");
542 let _conn = seeded_store(&path);
543
544 let first = cached_in_memory_connection("cached-reuses-same-key", &path).unwrap()
545 as *const Mutex<Connection>;
546 let second = cached_in_memory_connection("cached-reuses-same-key", &path).unwrap()
547 as *const Mutex<Connection>;
548 assert_eq!(
549 first, second,
550 "expected the same cached connection, not a fresh backup"
551 );
552 }
553}