use crate::mcp::models::{AgpmMetadata, McpConfig, McpServerConfig};
use anyhow::{Context, Result};
use chrono::Utc;
use std::collections::HashMap;
use std::path::Path;
pub async fn merge_mcp_servers(
mcp_config_path: &Path,
agpm_servers: HashMap<String, McpServerConfig>,
) -> Result<usize> {
if agpm_servers.is_empty() {
return Ok(0);
}
let mut mcp_config = McpConfig::load_or_default(mcp_config_path)?;
let conflicts = mcp_config.check_conflicts(&agpm_servers);
if !conflicts.is_empty() {
return Err(anyhow::anyhow!(
"The following MCP servers already exist and are not managed by AGPM: {}\n\
Please rename your servers or remove the existing ones from .mcp.json",
conflicts.join(", ")
));
}
let mut changed_count = 0;
for (name, new_config) in &agpm_servers {
match mcp_config.mcp_servers.get(name) {
Some(existing_config) => {
let mut existing_without_time = existing_config.clone();
let mut new_without_time = new_config.clone();
if let Some(ref mut meta) = existing_without_time.agpm_metadata {
meta.installed_at = String::new();
}
if let Some(ref mut meta) = new_without_time.agpm_metadata {
meta.installed_at = String::new();
}
if existing_without_time != new_without_time {
changed_count += 1;
}
}
None => {
changed_count += 1;
}
}
}
mcp_config.update_managed_servers(agpm_servers)?;
mcp_config.save(mcp_config_path)?;
Ok(changed_count)
}
pub async fn configure_mcp_servers(project_root: &Path, mcp_servers_dir: &Path) -> Result<()> {
if !mcp_servers_dir.exists() {
return Ok(());
}
let mcp_config_path = project_root.join(".mcp.json");
let mut agpm_servers = HashMap::new();
let mut entries = tokio::fs::read_dir(mcp_servers_dir).await?;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if path.extension().is_some_and(|ext| ext == "json")
&& let Some(name) = path.file_stem().and_then(|s| s.to_str())
{
let config: McpServerConfig = crate::utils::read_json_file(&path)
.with_context(|| format!("Failed to parse MCP server file: {}", path.display()))?;
let mut config_with_metadata = config;
if config_with_metadata.agpm_metadata.is_none() {
config_with_metadata.agpm_metadata = Some(AgpmMetadata {
managed: true,
source: Some("agpm".to_string()),
version: None,
installed_at: Utc::now().to_rfc3339(),
dependency_name: Some(name.to_string()),
});
}
agpm_servers.insert(name.to_string(), config_with_metadata);
}
}
merge_mcp_servers(&mcp_config_path, agpm_servers).await?;
Ok(())
}
pub fn clean_mcp_servers(project_root: &Path) -> Result<()> {
let claude_dir = project_root.join(".claude");
let agpm_dir = claude_dir.join("agpm");
let mcp_servers_dir = agpm_dir.join("mcp-servers");
let mcp_config_path = project_root.join(".mcp.json");
let mut removed_count = 0;
if mcp_servers_dir.exists() {
for entry in std::fs::read_dir(&mcp_servers_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().is_some_and(|ext| ext == "json") {
std::fs::remove_file(&path).with_context(|| {
format!("Failed to remove MCP server file: {}", path.display())
})?;
removed_count += 1;
}
}
}
if mcp_config_path.exists() {
let mut mcp_config = McpConfig::load_or_default(&mcp_config_path)?;
mcp_config.remove_all_managed();
mcp_config.save(&mcp_config_path)?;
}
if removed_count == 0 {
println!("No AGPM-managed MCP servers found");
} else {
println!("✓ Removed {removed_count} AGPM-managed MCP server(s)");
}
Ok(())
}
pub fn list_mcp_servers(project_root: &Path) -> Result<()> {
let mcp_config_path = project_root.join(".mcp.json");
if !mcp_config_path.exists() {
println!("No .mcp.json file found");
return Ok(());
}
let mcp_config = McpConfig::load_or_default(&mcp_config_path)?;
if mcp_config.mcp_servers.is_empty() {
println!("No MCP servers configured");
return Ok(());
}
let servers = &mcp_config.mcp_servers;
println!("MCP Servers:");
println!("╭─────────────────────┬──────────┬───────────╮");
println!("│ Name │ Managed │ Version │");
println!("├─────────────────────┼──────────┼───────────┤");
for (name, server) in servers {
let (managed, version) = if let Some(meta) = &server.agpm_metadata {
if meta.managed {
("✓ (agpm)", meta.version.as_deref().unwrap_or("-"))
} else {
("✗", "-")
}
} else {
("✗", "-")
};
println!("│ {name:<19} │ {managed:<8} │ {version:<9} │");
}
println!("╰─────────────────────┴──────────┴───────────╯");
Ok(())
}