mcp_sync/targets/
antigravity.rs

1//! Antigravity (VS Code) target implementation.
2
3use 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
12/// Antigravity target for VS Code MCP extension.
13///
14/// File format: `{ "servers": { name: {command,args,env} OR {url,headers} } }`
15pub struct AntigravityTarget;
16
17impl Target for AntigravityTarget {
18    fn name(&self) -> &'static str {
19        "Antigravity"
20    }
21
22    fn global_path(&self) -> Result<PathBuf> {
23        let home = home()?;
24        
25        #[cfg(target_os = "macos")]
26        let path = home
27            .join("Library")
28            .join("Application Support")
29            .join("Antigravity")
30            .join("User")
31            .join("mcp.json");
32        
33        #[cfg(target_os = "windows")]
34        let path = home
35            .join("AppData")
36            .join("Roaming")
37            .join("Antigravity")
38            .join("User")
39            .join("mcp.json");
40        
41        #[cfg(target_os = "linux")]
42        let path = home
43            .join(".config")
44            .join("Antigravity")
45            .join("User")
46            .join("mcp.json");
47        
48        #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
49        let path = home.join(".antigravity").join("mcp.json");
50        
51        Ok(path)
52    }
53
54    fn project_path(&self, project_root: &Path) -> PathBuf {
55        project_root.join(".vscode").join("mcp.json")
56    }
57
58    fn sync(&self, path: &Path, canon: &Canon, opts: &SyncOptions) -> Result<()> {
59        let mut root = read_json_file(path)?;
60        let servers_map = json_get_obj_mut(&mut root, "servers")?;
61
62        for (name, s) in &canon.servers {
63            let cfg = if s.kind() == "http" {
64                let url = s.url.clone().ok_or_else(|| anyhow!("server {}: http missing url", name))?;
65                let mut obj = serde_json::Map::new();
66                obj.insert("url".into(), JsonValue::String(url));
67                if let Some(h) = &s.headers {
68                    obj.insert("headers".into(), serde_json::to_value(h)?);
69                }
70                JsonValue::Object(obj)
71            } else {
72                let cmd = s.command.clone().ok_or_else(|| anyhow!("server {}: stdio missing command", name))?;
73                let mut obj = serde_json::Map::new();
74                obj.insert("command".into(), JsonValue::String(cmd));
75                obj.insert(
76                    "args".into(),
77                    JsonValue::Array(
78                        s.args
79                            .clone()
80                            .unwrap_or_default()
81                            .into_iter()
82                            .map(JsonValue::String)
83                            .collect(),
84                    ),
85                );
86                if let Some(env) = &s.env {
87                    obj.insert("env".into(), serde_json::to_value(env)?);
88                }
89                JsonValue::Object(obj)
90            };
91
92            servers_map.insert(name.clone(), cfg);
93        }
94
95        if opts.clean {
96            let canon_set = canon_names(canon);
97            let existing: Vec<String> = servers_map.keys().cloned().collect();
98            for k in existing {
99                if !canon_set.contains(&k) {
100                    servers_map.remove(&k);
101                }
102            }
103        }
104
105        let label = if path.starts_with(home().unwrap_or_default()) {
106            "global"
107        } else {
108            "project"
109        };
110        log_verbose(opts.verbose, format!("{} {} -> {:?}", self.name(), label, path));
111        write_json_file(path, &root, opts.dry_run)
112    }
113}