Skip to main content

mcp_memory/
config.rs

1use crate::errors::{MCSError, Result};
2use crate::Transport;
3use std::sync::Arc;
4
5/// How aggressively to push WAL writes to durable storage before acknowledging
6/// the client.
7///
8/// The default [`Async`](Durability::Async) flushes to the kernel page cache
9/// and returns immediately; the background sync thread calls `fsync` within
10/// ~1 second. Journal-mode filesystems (ext4, APFS, NTFS) typically absorb a
11/// power loss within that window.
12///
13/// [`Sync`](Durability::Sync) calls `fsync` before returning, confirming the
14/// data is on stable media. Use this when every write must survive an immediate
15/// power failure, at the cost of higher write latency.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Durability {
18    Async,
19    Sync,
20}
21
22impl Durability {
23    pub const fn is_sync(self) -> bool {
24        matches!(self, Durability::Sync)
25    }
26}
27
28impl std::str::FromStr for Durability {
29    type Err = String;
30    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
31        match s {
32            "async" | "Async" => Ok(Durability::Async),
33            "sync" | "Sync" => Ok(Durability::Sync),
34            _ => Err(format!("unknown durability '{s}'; expected 'async' or 'sync'")),
35        }
36    }
37}
38
39#[derive(Debug, Clone)]
40pub struct Config {
41    pub memory_file_path: String,
42    pub transport: Transport,
43    pub bind_addr: String,
44    pub durability: Durability,
45    /// Optional bearer token required on the `tcp` and `http` transports. When
46    /// `None`, those transports accept unauthenticated connections (stdio is
47    /// always local and never authenticated).
48    pub auth_token: Option<Arc<str>>,
49    pub mmap_size: i64,
50    pub lru_cache_size: usize,
51    /// Size of the read-only connection pool (concurrent reads). Always >= 1.
52    pub read_pool_size: usize,
53    /// PEM certificate chain for serving the `http` transport over TLS (HTTPS).
54    /// `None` (the default) keeps the transport plaintext. Engaged only when
55    /// both `tls_cert` and `tls_key` are set.
56    pub tls_cert: Option<std::path::PathBuf>,
57    /// PEM private key matching `tls_cert`.
58    pub tls_key: Option<std::path::PathBuf>,
59}
60
61impl Config {
62    pub fn from_args(args: &super::Args) -> Result<Self> {
63        let memory_file_path = args
64            .memory_file
65            .clone()
66            .or_else(|| std::env::var("MEMORY_FILE_PATH").ok())
67            .unwrap_or_else(|| "memory.mcpmem".to_string());
68
69        // Resolve the auth token from --auth-token, then --auth-token-file, then
70        // the MCP_MEMORY_AUTH_TOKEN env var. A configured-but-empty token file
71        // is a hard error: fail closed rather than silently disabling auth.
72        let auth_token: Option<Arc<str>> = if let Some(t) = args.auth_token.clone() {
73            Some(Arc::from(t.as_str()))
74        } else if let Some(path) = args.auth_token_file.clone() {
75            let contents = std::fs::read_to_string(&path).map_err(|e| {
76                MCSError::InvalidParams(format!("failed to read --auth-token-file '{path}': {e}"))
77            })?;
78            let token = contents.trim();
79            if token.is_empty() {
80                return Err(MCSError::InvalidParams(format!(
81                    "--auth-token-file '{path}' is empty; refusing to start with auth disabled"
82                )));
83            }
84            Some(Arc::from(token))
85        } else {
86            std::env::var("MCP_MEMORY_AUTH_TOKEN")
87                .ok()
88                .filter(|t| !t.is_empty())
89                .map(|t| Arc::from(t.as_str()))
90        };
91
92        let durability = if let Ok(env) = std::env::var("MCP_MEMORY_DURABILITY") {
93            env.parse().unwrap_or_else(|e| {
94                tracing::warn!("MCP_MEMORY_DURABILITY parse failed: {e}; falling back to Async");
95                Durability::Async
96            })
97        } else {
98            Durability::Async
99        };
100
101        // TLS cert/key for the `http` transport, from CLI flags or env vars.
102        // Both must be supplied together; one without the other is a hard error.
103        let tls_cert = args
104            .tls_cert
105            .clone()
106            .or_else(|| std::env::var("MCP_TLS_CERT").ok())
107            .filter(|s| !s.is_empty())
108            .map(std::path::PathBuf::from);
109        let tls_key = args
110            .tls_key
111            .clone()
112            .or_else(|| std::env::var("MCP_TLS_KEY").ok())
113            .filter(|s| !s.is_empty())
114            .map(std::path::PathBuf::from);
115        if tls_cert.is_some() != tls_key.is_some() {
116            return Err(MCSError::InvalidParams(
117                "--tls-cert and --tls-key must be provided together (or both omitted for plaintext HTTP)"
118                    .to_string(),
119            ));
120        }
121
122        Ok(Config {
123            memory_file_path,
124            transport: args.transport,
125            bind_addr: args.bind.clone(),
126            durability,
127            auth_token,
128            mmap_size: args.mmap_size,
129            lru_cache_size: args.lru_cache_size,
130            read_pool_size: args.read_pool_size.max(1),
131            tls_cert,
132            tls_key,
133        })
134    }
135}
136
137impl Default for Config {
138    fn default() -> Self {
139        Self {
140            memory_file_path: "memory.mcpmem".to_string(),
141            transport: Transport::Stdio,
142            bind_addr: "127.0.0.1:8080".to_string(),
143            durability: Durability::Async,
144            auth_token: None,
145            mmap_size: 268435456,
146            lru_cache_size: 10000,
147            read_pool_size: 4,
148            tls_cert: None,
149            tls_key: None,
150        }
151    }
152}
153
154