use std::path::Path;
use serde_json::{Value, json};
use super::{ChangeKind, InstallScope};
pub fn add_mcp_entry(
root: &mut Value,
scope: InstallScope,
dotenv: Option<&Path>,
force: bool,
) -> ChangeKind {
let entry = build_entry(dotenv);
match scope {
InstallScope::User => {
ensure_object(root);
ensure_object_key(root, "mcpServers");
upsert_agentsec(root["mcpServers"].as_object_mut().unwrap(), entry, force)
}
InstallScope::Project => {
let cwd = std::env::current_dir()
.unwrap_or_else(|_| std::path::PathBuf::from("."))
.display()
.to_string();
ensure_object(root);
ensure_object_key(root, "projects");
root["projects"]
.as_object_mut()
.unwrap()
.entry(cwd.clone())
.or_insert_with(|| json!({}));
ensure_object_key(&mut root["projects"][&cwd], "mcpServers");
upsert_agentsec(
root["projects"][&cwd]["mcpServers"]
.as_object_mut()
.unwrap(),
entry,
force,
)
}
}
}
pub fn remove_mcp_entry(root: &mut Value, scope: InstallScope) -> ChangeKind {
match scope {
InstallScope::User => {
let removed = root
.get_mut("mcpServers")
.and_then(|v| v.as_object_mut())
.is_some_and(|m| m.remove("agentsec").is_some());
if removed {
ChangeKind::Removed
} else {
ChangeKind::NoOp
}
}
InstallScope::Project => {
let cwd = std::env::current_dir()
.unwrap_or_else(|_| std::path::PathBuf::from("."))
.display()
.to_string();
let removed = root
.get_mut("projects")
.and_then(|v| v.as_object_mut())
.and_then(|m| m.get_mut(&cwd))
.and_then(|p| p.get_mut("mcpServers"))
.and_then(|v| v.as_object_mut())
.is_some_and(|m| m.remove("agentsec").is_some());
if removed {
ChangeKind::Removed
} else {
ChangeKind::NoOp
}
}
}
}
fn ensure_object(v: &mut Value) {
if v.as_object().is_none() {
*v = json!({});
}
}
fn ensure_object_key(root: &mut Value, key: &str) {
let obj = root.as_object_mut().unwrap();
match obj.get(key) {
None => {
obj.insert(key.to_string(), json!({}));
}
Some(v) if !v.is_object() => {
obj.insert(key.to_string(), json!({}));
}
_ => {}
}
}
fn upsert_agentsec(
servers_obj: &mut serde_json::Map<String, Value>,
entry: Value,
force: bool,
) -> ChangeKind {
if servers_obj.contains_key("agentsec") {
if force {
servers_obj.insert("agentsec".to_string(), entry);
ChangeKind::Updated
} else {
ChangeKind::NoOp
}
} else {
servers_obj.insert("agentsec".to_string(), entry);
ChangeKind::Added
}
}
fn build_entry(dotenv: Option<&Path>) -> Value {
if let Some(d) = dotenv {
json!({
"command": "agentsec",
"args": ["mcp"],
"env": {
"AGENTSEC_DOTENV": d.display().to_string()
}
})
} else {
json!({
"command": "agentsec",
"args": ["mcp"]
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn add_mcp_entry_user_scope_adds_entry() {
let mut root = json!({});
let kind = add_mcp_entry(&mut root, InstallScope::User, None, false);
assert_eq!(kind, ChangeKind::Added);
assert!(root["mcpServers"]["agentsec"].is_object());
assert_eq!(root["mcpServers"]["agentsec"]["command"], "agentsec");
}
#[test]
fn add_mcp_entry_idempotent_without_force() {
let mut root = json!({});
add_mcp_entry(&mut root, InstallScope::User, None, false);
let kind = add_mcp_entry(&mut root, InstallScope::User, None, false);
assert_eq!(kind, ChangeKind::NoOp);
let count = root["mcpServers"].as_object().unwrap().len();
assert_eq!(count, 1);
}
#[test]
fn add_mcp_entry_force_overwrites() {
let mut root = json!({
"mcpServers": {
"agentsec": {"command": "old"}
}
});
let kind = add_mcp_entry(&mut root, InstallScope::User, None, true);
assert_eq!(kind, ChangeKind::Updated);
assert_eq!(root["mcpServers"]["agentsec"]["command"], "agentsec");
}
#[test]
fn add_mcp_entry_dotenv_adds_env_field() {
let mut root = json!({});
let dotenv = std::path::Path::new("/tmp/.env");
add_mcp_entry(&mut root, InstallScope::User, Some(dotenv), false);
assert_eq!(
root["mcpServers"]["agentsec"]["env"]["AGENTSEC_DOTENV"],
"/tmp/.env"
);
}
#[test]
fn remove_mcp_entry_removes_agentsec_only() {
let mut root = json!({
"mcpServers": {
"agentsec": {"command": "agentsec"},
"other": {"command": "other"}
}
});
let kind = remove_mcp_entry(&mut root, InstallScope::User);
assert_eq!(kind, ChangeKind::Removed);
assert!(root["mcpServers"]["agentsec"].is_null());
assert_eq!(root["mcpServers"]["other"]["command"], "other");
}
#[test]
fn remove_mcp_entry_noop_when_absent() {
let mut root = json!({"mcpServers": {}});
let kind = remove_mcp_entry(&mut root, InstallScope::User);
assert_eq!(kind, ChangeKind::NoOp);
}
}