mcp_sync/targets/
claude.rs1use crate::{
4 canon::{canon_names, Canon},
5 target::{SyncOptions, Target},
6 utils::{home, json_get_obj_mut, log_verbose, read_json_file, write_json_file},
7};
8use anyhow::{anyhow, Result};
9use serde_json::Value as JsonValue;
10use std::path::{Path, PathBuf};
11
12pub struct ClaudeTarget;
16
17impl Target for ClaudeTarget {
18 fn name(&self) -> &'static str {
19 "Claude"
20 }
21
22 fn global_path(&self) -> Result<PathBuf> {
23 let home = home()?;
24
25 Ok(home.join(".claude.json"))
27 }
28
29 fn project_path(&self, project_root: &Path) -> PathBuf {
30 project_root.join(".mcp.json")
31 }
32
33 fn sync(&self, path: &Path, canon: &Canon, opts: &SyncOptions) -> Result<()> {
34 let mut root = read_json_file(path)?;
35 let mcp = json_get_obj_mut(&mut root, "mcpServers")?;
36
37 for (name, s) in &canon.servers {
38 let cfg = if s.kind() == "http" {
39 let url = s.url.clone().ok_or_else(|| anyhow!("server {}: http missing url", name))?;
40 let mut obj = serde_json::Map::new();
41 obj.insert("type".into(), JsonValue::String("http".into()));
42 obj.insert("url".into(), JsonValue::String(url));
43 if let Some(h) = &s.headers {
44 obj.insert("headers".into(), serde_json::to_value(h)?);
45 }
46 JsonValue::Object(obj)
47 } else {
48 let cmd = s.command.clone().ok_or_else(|| anyhow!("server {}: stdio missing command", name))?;
49 let mut obj = serde_json::Map::new();
50 obj.insert("command".into(), JsonValue::String(cmd));
51 obj.insert(
52 "args".into(),
53 JsonValue::Array(
54 s.args.clone().unwrap_or_default().into_iter().map(JsonValue::String).collect(),
55 ),
56 );
57 if let Some(env) = &s.env {
58 obj.insert("env".into(), serde_json::to_value(env)?);
59 }
60 JsonValue::Object(obj)
61 };
62 mcp.insert(name.clone(), cfg);
63 }
64
65 if opts.clean {
66 let canon_set = canon_names(canon);
67 let keys: Vec<String> = mcp.keys().cloned().collect();
68 for k in keys {
69 if !canon_set.contains(&k) {
70 mcp.remove(&k);
71 }
72 }
73 }
74
75 let label = if path.starts_with(home().unwrap_or_default()) {
76 "global"
77 } else {
78 "project"
79 };
80 log_verbose(opts.verbose, format!("{} {} -> {:?}", self.name(), label, path));
81 write_json_file(path, &root, opts.dry_run)
82 }
83}