1use std::collections::HashMap;
7
8const PATH_SEP: char = if cfg!(windows) { ';' } else { ':' };
10
11const MIRROR_ENV_KEYS: &[&str] = &[
13 "npm_config_registry",
14 "UV_INDEX_URL",
15 "UV_EXTRA_INDEX_URL",
16 "UV_INSECURE_HOST",
17 "PIP_INDEX_URL",
18];
19
20pub fn format_path_summary(max_segments: usize) -> String {
26 match std::env::var("PATH") {
27 Ok(path) => {
28 let segments: Vec<&str> = path.split(PATH_SEP).collect();
29 let preview: String = segments
30 .iter()
31 .take(max_segments)
32 .copied()
33 .collect::<Vec<_>>()
34 .join(&PATH_SEP.to_string());
35 if segments.len() > max_segments {
36 format!("{} ... ({} entries total)", preview, segments.len())
37 } else {
38 preview
39 }
40 }
41 Err(_) => "(unset)".to_string(),
42 }
43}
44
45pub fn collect_mirror_env_vars() -> Vec<(&'static str, String)> {
49 MIRROR_ENV_KEYS
50 .iter()
51 .filter_map(|&key| std::env::var(key).ok().map(|val| (key, val)))
52 .collect()
53}
54
55pub fn format_spawn_error(
57 mcp_id: &str,
58 command: &str,
59 args: &Option<Vec<String>>,
60 inner: impl std::fmt::Display,
61) -> String {
62 let path_val = std::env::var("PATH").unwrap_or_else(|_| "(unset)".to_string());
63 format!(
64 "Failed to spawn child process - MCP ID: {}, command: {}, \
65 args: {:?}, PATH: {}, error: {}",
66 mcp_id,
67 command,
68 args.as_ref().unwrap_or(&Vec::new()),
69 path_val,
70 inner
71 )
72}
73
74pub fn log_stdio_spawn_context(tag: &str, mcp_id: &str, env: &Option<HashMap<String, String>>) {
81 tracing::debug!(
82 "[{}] MCP ID: {}, PATH: {}",
83 tag,
84 mcp_id,
85 format_path_summary(3),
86 );
87
88 for (key, val) in collect_mirror_env_vars() {
89 tracing::debug!("[{}] MCP ID: {}, {}={}", tag, mcp_id, key, val);
90 }
91
92 if let Some(env_vars) = env {
93 let keys: Vec<&String> = env_vars.keys().collect();
94 tracing::debug!("[{}] MCP ID: {}, config env keys: {:?}", tag, mcp_id, keys);
95 }
96}
97
98pub fn eprint_env_summary() {
100 eprintln!(" - PATH: {}", format_path_summary(3));
101
102 for (key, val) in collect_mirror_env_vars() {
103 eprintln!(" - {}={}", key, val);
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_format_path_summary_not_empty() {
113 let summary = format_path_summary(3);
114 assert!(!summary.is_empty());
115 }
116
117 #[test]
118 fn test_collect_mirror_env_vars() {
119 let _ = collect_mirror_env_vars();
121 }
122
123 #[test]
124 fn test_format_spawn_error() {
125 let msg = format_spawn_error(
126 "test-id",
127 "npx",
128 &Some(vec!["-y".into(), "server".into()]),
129 "file not found",
130 );
131 assert!(msg.contains("test-id"));
132 assert!(msg.contains("npx"));
133 assert!(msg.contains("file not found"));
134 }
135
136 #[test]
137 fn test_log_stdio_spawn_context_no_panic() {
138 let mut env = HashMap::new();
139 env.insert("FOO".to_string(), "bar".to_string());
140 log_stdio_spawn_context("Test", "test-id", &Some(env));
141 log_stdio_spawn_context("Test", "test-id", &None);
142 }
143}