Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/infigraph-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,4 @@ libc = "0.2"

[dev-dependencies]
tempfile = "3"
toml = "0.8"
87 changes: 59 additions & 28 deletions crates/infigraph-cli/src/config_targets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,14 @@ pub(crate) fn install_toml_target(config_path: &std::path::Path, mcp_path_str: &
String::new()
};

let mcp_block = format!(
"[mcp]\ninfigraph = {{ command = \"{}\", args = [\"--mcp\"] }}\n",
mcp_path_str
);
let escaped_path = mcp_path_str.replace('\\', "\\\\").replace('"', "\\\"");
let section_header = "[mcp_servers.infigraph]";
let mcp_block = format!("{section_header}\ncommand = \"{escaped_path}\"\nargs = [\"--mcp\"]\n");

let new_content = if existing.is_empty() {
mcp_block
} else if let Some(start) = existing.find("[mcp]") {
let after_header = start + "[mcp]".len();
} else if let Some(start) = existing.find(section_header) {
let after_header = start + section_header.len();
let section_end = existing[after_header..]
.find("\n[")
.map(|pos| after_header + pos + 1)
Expand Down Expand Up @@ -194,35 +193,34 @@ pub(crate) fn uninstall_toml_target<'a>(
let content = std::fs::read_to_string(config_path)
.with_context(|| format!("Failed to read {}", config_path.display()))?;

if let Some(start) = content.find("[mcp]") {
let after_header = start + "[mcp]".len();
let section_header = "[mcp_servers.infigraph]";
if let Some(start) = content.find(section_header) {
let after_header = start + section_header.len();
let section_end = content[after_header..]
.find("\n[")
.map(|pos| after_header + pos + 1)
.unwrap_or(content.len());

let section_text = &content[start..section_end];
if section_text.contains("infigraph") {
let new_content = format!("{}{}", &content[..start], &content[section_end..]);
let trimmed = new_content.trim_end().to_string();
let final_content = if trimmed.is_empty() {
String::new()
} else {
format!("{}\n", trimmed)
};
std::fs::write(config_path, final_content.as_bytes())
.with_context(|| format!("Failed to write {}", config_path.display()))?;
println!(
" Removed infigraph from {} ({})",
label,
config_path.display()
);
return Ok(Some(label));
let new_content = format!("{}{}", &content[..start], &content[section_end..]);
let trimmed = new_content.trim_end().to_string();
let final_content = if trimmed.is_empty() {
String::new()
} else {
println!(" Skipping {} (infigraph entry not found in [mcp])", label);
}
format!("{}\n", trimmed)
};
std::fs::write(config_path, final_content.as_bytes())
.with_context(|| format!("Failed to write {}", config_path.display()))?;
println!(
" Removed infigraph from {} ({})",
label,
config_path.display()
);
return Ok(Some(label));
} else {
println!(" Skipping {} (no [mcp] section in config)", label);
println!(
" Skipping {} (no [mcp_servers.infigraph] section in config)",
label
);
}

Ok(None)
Expand Down Expand Up @@ -287,6 +285,11 @@ mod tests {
install_toml_target(&config, "/usr/bin/infigraph-mcp").unwrap();

let content = std::fs::read_to_string(&config).unwrap();
assert!(
content.contains("[mcp_servers.infigraph]"),
"Codex expects [mcp_servers.infigraph], got: {}",
content
);
assert!(
content.contains(r#"args = ["--mcp"]"#),
"toml args must be [\"--mcp\"], got: {}",
Expand All @@ -296,6 +299,34 @@ mod tests {
assert!(!content.contains("--port"), "must not contain --port");
}

#[test]
fn install_toml_escapes_windows_path() {
let dir = tempfile::tempdir().unwrap();
let config = dir.path().join("config.toml");
install_toml_target(&config, r"C:\Users\foo\infigraph-mcp.exe").unwrap();

let content = std::fs::read_to_string(&config).unwrap();
let parsed: toml::Value = toml::from_str(&content)
.unwrap_or_else(|e| panic!("generated TOML must parse, got error {e}: {content}"));
assert_eq!(
parsed["mcp_servers"]["infigraph"]["command"].as_str(),
Some(r"C:\Users\foo\infigraph-mcp.exe")
);
}

#[test]
fn install_toml_preserves_existing_sections() {
let dir = tempfile::tempdir().unwrap();
let config = dir.path().join("config.toml");
std::fs::write(&config, "[mcp_servers.other]\ncommand = \"other\"\n").unwrap();

install_toml_target(&config, "/usr/bin/infigraph-mcp").unwrap();

let content = std::fs::read_to_string(&config).unwrap();
assert!(content.contains("[mcp_servers.other]"));
assert!(content.contains("[mcp_servers.infigraph]"));
}

#[test]
fn uninstall_json_removes_infigraph() {
let dir = tempfile::tempdir().unwrap();
Expand Down
1 change: 1 addition & 0 deletions crates/infigraph-mcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ hmac = "0.12"
hex = "0.4"
fs2 = "0.4"
toml = "0.8"
dirs-next = "2"
ort = { version = "2.0.0-rc.12", features = ["download-binaries"], optional = true }
ndarray = { version = "0.16", optional = true }
tokenizers = { version = "0.21", default-features = false, features = ["onig"], optional = true }
Expand Down
12 changes: 7 additions & 5 deletions crates/infigraph-mcp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -592,9 +592,10 @@ pub fn mcp_log(level: &str, msg: &str) {
}

fn mcp_log_file_path() -> std::path::PathBuf {
std::env::var("HOME")
std::env::var_os("HOME")
.map(std::path::PathBuf::from)
.unwrap_or_else(|_| std::path::PathBuf::from("."))
.or_else(dirs_next::home_dir)
.unwrap_or_else(|| std::path::PathBuf::from("."))
.join(".infigraph")
.join("mcp.log")
}
Expand Down Expand Up @@ -728,9 +729,10 @@ pub fn handle_tools_call(id: &Value, request: &Value) -> Value {
.and_then(|p| p.as_str())
.map(|p| std::path::PathBuf::from(p).join(".infigraph"))
.or_else(|| {
std::env::var("HOME")
.ok()
.map(|h| std::path::PathBuf::from(h).join(".infigraph"))
std::env::var_os("HOME")
.map(std::path::PathBuf::from)
.or_else(dirs_next::home_dir)
.map(|h| h.join(".infigraph"))
})
{
let _ = std::fs::create_dir_all(&dir);
Expand Down
20 changes: 10 additions & 10 deletions crates/infigraph-mcp/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,10 @@ fn auto_reindex_all() {
}

// Reindex group combined graphs
let groups_dir = std::env::var("HOME")
.map(|h| {
std::path::PathBuf::from(h)
.join(".infigraph")
.join("groups")
})
.ok();
let groups_dir = std::env::var_os("HOME")
.map(std::path::PathBuf::from)
.or_else(dirs_next::home_dir)
.map(|h| h.join(".infigraph").join("groups"));
if let Some(ref gd) = groups_dir {
if let Ok(entries) = std::fs::read_dir(gd) {
for entry in entries.flatten() {
Expand Down Expand Up @@ -149,7 +146,9 @@ fn find_infigraph_cli_for_reindex() -> Option<std::path::PathBuf> {
}
}
// Check common install locations
let home = std::env::var("HOME").ok().map(std::path::PathBuf::from);
let home = std::env::var_os("HOME")
.map(std::path::PathBuf::from)
.or_else(dirs_next::home_dir);
if let Some(ref h) = home {
let local_bin = h.join(".local").join("bin").join(bin_name);
if local_bin.exists() {
Expand Down Expand Up @@ -198,9 +197,10 @@ fn install_panic_hook() {
}

fn acquire_instance_lock() -> Option<std::fs::File> {
let lock_path = std::env::var("HOME")
let lock_path = std::env::var_os("HOME")
.map(std::path::PathBuf::from)
.unwrap_or_else(|_| std::path::PathBuf::from("."))
.or_else(dirs_next::home_dir)
.unwrap_or_else(|| std::path::PathBuf::from("."))
.join(".infigraph")
.join("mcp.lock");
if let Some(parent) = lock_path.parent() {
Expand Down
5 changes: 3 additions & 2 deletions crates/infigraph-mcp/src/tools/watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ pub fn watchers_disabled() -> bool {

fn watch_log(level: &str, msg: &str) {
use std::io::Write;
let path = std::env::var("HOME")
let path = std::env::var_os("HOME")
.map(std::path::PathBuf::from)
.unwrap_or_else(|_| std::path::PathBuf::from("."))
.or_else(dirs_next::home_dir)
.unwrap_or_else(|| std::path::PathBuf::from("."))
.join(".infigraph")
.join("mcp.log");
if let Ok(mut f) = std::fs::OpenOptions::new()
Expand Down
12 changes: 8 additions & 4 deletions crates/infigraph-mcp/src/web/handlers_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,10 @@ pub(crate) fn api_routes(params: &Value) -> Value {
}

pub(crate) fn api_groups(_params: &Value) -> Value {
let registry_path = std::env::var("HOME")
.map(|h| PathBuf::from(h).join(".infigraph").join("registry.json"))
let registry_path = std::env::var_os("HOME")
.map(PathBuf::from)
.or_else(dirs_next::home_dir)
.map(|h| h.join(".infigraph").join("registry.json"))
.unwrap_or_default();
if !registry_path.exists() {
return json!({"groups": [], "count": 0});
Expand Down Expand Up @@ -216,8 +218,10 @@ pub(crate) fn api_groups(_params: &Value) -> Value {

pub(crate) fn api_contracts(params: &Value) -> Value {
let group_name = params.get("group").and_then(|g| g.as_str()).unwrap_or("");
let registry_path = std::env::var("HOME")
.map(|h| PathBuf::from(h).join(".infigraph").join("registry.json"))
let registry_path = std::env::var_os("HOME")
.map(PathBuf::from)
.or_else(dirs_next::home_dir)
.map(|h| h.join(".infigraph").join("registry.json"))
.unwrap_or_default();
if !registry_path.exists() {
return json!({"contracts": [], "count": 0});
Expand Down
Loading