use std::fs;
use std::path::PathBuf;
#[derive(Debug, Clone, Default)]
pub struct RegisterOptions {
pub agents: Option<Vec<String>>,
pub command: Option<String>,
pub global: bool,
}
#[derive(Debug, Clone)]
pub struct RegisterResult {
pub agents: Vec<String>,
pub command: String,
}
pub async fn register(
name: &str,
options: &RegisterOptions,
) -> Result<RegisterResult, crate::errors::Error> {
let command = options.command.clone().unwrap_or_else(|| {
let exe = std::env::current_exe()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| name.to_string());
format!("{} --mcp", exe)
});
let target_agents = options.agents.clone().unwrap_or_default();
let mut registered_agents: Vec<String> = Vec::new();
if target_agents.is_empty() || target_agents.iter().any(|a| a == "amp") {
if register_amp(name, &command) {
registered_agents.push("Amp".to_string());
}
}
if target_agents.is_empty() || target_agents.iter().any(|a| a == "claude-code" || a == "claude") {
if register_claude_code(name, &command, options.global) {
registered_agents.push("Claude Code".to_string());
}
}
Ok(RegisterResult {
command,
agents: registered_agents,
})
}
fn register_amp(name: &str, command: &str) -> bool {
let config_path = amp_config_path();
let mut config: serde_json::Map<String, serde_json::Value> = if config_path.exists() {
match fs::read_to_string(&config_path) {
Ok(content) => match serde_json::from_str(&content) {
Ok(c) => c,
Err(_) => return false,
},
Err(_) => return false,
}
} else {
serde_json::Map::new()
};
let parts: Vec<&str> = command.splitn(2, ' ').collect();
let cmd = match parts.first() {
Some(c) => *c,
None => return false,
};
let args: Vec<&str> = if parts.len() > 1 {
parts[1].split_whitespace().collect()
} else {
vec![]
};
let mut servers = config
.remove("amp.mcpServers")
.and_then(|v| match v {
serde_json::Value::Object(m) => Some(m),
_ => None,
})
.unwrap_or_default();
let mut entry = serde_json::Map::new();
entry.insert(
"command".to_string(),
serde_json::Value::String(cmd.to_string()),
);
entry.insert(
"args".to_string(),
serde_json::Value::Array(
args.iter()
.map(|a| serde_json::Value::String(a.to_string()))
.collect(),
),
);
servers.insert(name.to_string(), serde_json::Value::Object(entry));
config.insert(
"amp.mcpServers".to_string(),
serde_json::Value::Object(servers),
);
if let Some(dir) = config_path.parent() {
if !dir.exists() {
let _ = fs::create_dir_all(dir);
}
}
match serde_json::to_string_pretty(&serde_json::Value::Object(config)) {
Ok(json) => fs::write(&config_path, format!("{}\n", json)).is_ok(),
Err(_) => false,
}
}
fn register_claude_code(name: &str, command: &str, global: bool) -> bool {
let config_path = if global {
let home = dirs::home_dir().unwrap_or_default();
home.join(".claude.json")
} else {
PathBuf::from(".claude.json")
};
let mut config: serde_json::Map<String, serde_json::Value> = if config_path.exists() {
match fs::read_to_string(&config_path) {
Ok(content) => serde_json::from_str(&content).unwrap_or_default(),
Err(_) => serde_json::Map::new(),
}
} else {
serde_json::Map::new()
};
let parts: Vec<&str> = command.splitn(2, ' ').collect();
let cmd = match parts.first() {
Some(c) => *c,
None => return false,
};
let args: Vec<&str> = if parts.len() > 1 {
parts[1].split_whitespace().collect()
} else {
vec![]
};
let mut servers = config
.remove("mcpServers")
.and_then(|v| match v {
serde_json::Value::Object(m) => Some(m),
_ => None,
})
.unwrap_or_default();
let mut entry = serde_json::Map::new();
entry.insert(
"command".to_string(),
serde_json::Value::String(cmd.to_string()),
);
entry.insert(
"args".to_string(),
serde_json::Value::Array(
args.iter()
.map(|a| serde_json::Value::String(a.to_string()))
.collect(),
),
);
servers.insert(name.to_string(), serde_json::Value::Object(entry));
config.insert(
"mcpServers".to_string(),
serde_json::Value::Object(servers),
);
if let Some(dir) = config_path.parent() {
if !dir.exists() {
let _ = fs::create_dir_all(dir);
}
}
match serde_json::to_string_pretty(&serde_json::Value::Object(config)) {
Ok(json) => fs::write(&config_path, format!("{}\n", json)).is_ok(),
Err(_) => false,
}
}
fn amp_config_path() -> PathBuf {
let home = dirs::home_dir().unwrap_or_default();
home.join(".config").join("amp").join("settings.json")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_amp_config_path() {
let path = amp_config_path();
assert!(path.to_string_lossy().contains("amp"));
assert!(path.to_string_lossy().ends_with("settings.json"));
}
#[test]
fn test_register_options_default() {
let opts = RegisterOptions::default();
assert!(opts.agents.is_none());
assert!(opts.command.is_none());
assert!(!opts.global);
}
}