Skip to main content

mcp_multiplexer/
cache.rs

1pub use crate::model::ToolInfo;
2use serde::{Deserialize, Serialize};
3use std::collections::BTreeMap;
4use std::collections::hash_map::DefaultHasher;
5use std::hash::{Hash, Hasher};
6use std::path::{Path, PathBuf};
7
8#[derive(Debug, Default, Serialize, Deserialize)]
9struct CacheFile {
10    config_hash: u64,
11    servers: BTreeMap<String, Vec<ToolInfo>>,
12    instructions: BTreeMap<String, String>,
13}
14
15#[derive(Debug, Default)]
16pub struct Cache {
17    pub servers: BTreeMap<String, Vec<ToolInfo>>,
18    pub instructions: BTreeMap<String, String>,
19}
20
21pub fn config_hash(text: &str) -> u64 {
22    // ponytail: not cryptographic — cache key only, collisions just cause a cold start
23    let mut h = DefaultHasher::new();
24    text.hash(&mut h);
25    h.finish()
26}
27
28pub fn cache_dir() -> PathBuf {
29    let base = std::env::var_os("XDG_CACHE_HOME")
30        .map(PathBuf::from)
31        .or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".cache")))
32        .unwrap_or_else(|| PathBuf::from("."));
33    base.join("mcp-multiplexer")
34}
35
36pub fn cache_path() -> PathBuf {
37    cache_dir().join("index.json")
38}
39
40impl Cache {
41    pub fn load(config_hash: u64) -> Cache {
42        Self::load_from(&cache_path(), config_hash)
43    }
44    pub fn save(&self, config_hash: u64) -> anyhow::Result<()> {
45        self.save_to(&cache_path(), config_hash)
46    }
47
48    pub fn load_from(path: &Path, config_hash: u64) -> Cache {
49        let Ok(text) = std::fs::read_to_string(path) else {
50            return Cache::default();
51        };
52        let Ok(f) = serde_json::from_str::<CacheFile>(&text) else {
53            return Cache::default();
54        };
55        if f.config_hash != config_hash {
56            return Cache::default();
57        }
58        Cache {
59            servers: f.servers,
60            instructions: f.instructions,
61        }
62    }
63
64    pub fn save_to(&self, path: &Path, config_hash: u64) -> anyhow::Result<()> {
65        if let Some(dir) = path.parent() {
66            std::fs::create_dir_all(dir)?;
67        }
68        let f = CacheFile {
69            config_hash,
70            servers: self.servers.clone(),
71            instructions: self.instructions.clone(),
72        };
73        let tmp = path.with_extension("json.tmp");
74        std::fs::write(&tmp, serde_json::to_string(&f)?)?;
75        std::fs::rename(&tmp, path)?;
76        Ok(())
77    }
78}