1use crate::errors::{MCSError, Result};
2use crate::Transport;
3use std::sync::Arc;
4
5#[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 pub auth_token: Option<Arc<str>>,
49 pub mmap_size: i64,
50 pub lru_cache_size: usize,
51 pub read_pool_size: usize,
53 pub tls_cert: Option<std::path::PathBuf>,
57 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 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 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