diff --git a/cmd/mcp/install.go b/cmd/mcp/install.go index f1cd880e..1ce4a4e1 100644 --- a/cmd/mcp/install.go +++ b/cmd/mcp/install.go @@ -20,6 +20,7 @@ Supported targets: cursor - Cursor editor claude - Claude Desktop app claude-code - Claude Code CLI + antigravity - Google Antigravity windsurf - Windsurf editor vscode - Visual Studio Code goose - Goose AI @@ -119,6 +120,12 @@ func printPostInstallInstructions(target Target) { pterm.Println(" 1. Run '/mcp' in the Claude Code REPL to authenticate") pterm.Println(" 2. The Kernel tools will then be available") + case TargetAntigravity: + pterm.Info.Println("Next steps:") + pterm.Println(" 1. Reload the Antigravity window") + pterm.Println(" 2. Open agent settings and click 'Authenticate' next to kernel on the Customizations tab") + pterm.Println(" 3. Paste the authorization code from your browser back into the settings panel") + case TargetWindsurf: pterm.Info.Println("Next steps:") pterm.Println(" 1. Open Windsurf settings and navigate to MCP servers") diff --git a/cmd/mcp/mcp.go b/cmd/mcp/mcp.go index 0b871d3c..06c9ce8d 100644 --- a/cmd/mcp/mcp.go +++ b/cmd/mcp/mcp.go @@ -27,14 +27,15 @@ var MCPCmd = &cobra.Command{ type Target string const ( - TargetCursor Target = "cursor" - TargetClaude Target = "claude" - TargetClaudeCode Target = "claude-code" - TargetWindsurf Target = "windsurf" - TargetVSCode Target = "vscode" - TargetGoose Target = "goose" - TargetZed Target = "zed" - TargetFx Target = "fx" + TargetCursor Target = "cursor" + TargetClaude Target = "claude" + TargetClaudeCode Target = "claude-code" + TargetAntigravity Target = "antigravity" + TargetWindsurf Target = "windsurf" + TargetVSCode Target = "vscode" + TargetGoose Target = "goose" + TargetZed Target = "zed" + TargetFx Target = "fx" ) // KernelMCPURL is the URL for the Kernel MCP server @@ -46,6 +47,7 @@ func AllTargets() []Target { TargetCursor, TargetClaude, TargetClaudeCode, + TargetAntigravity, TargetWindsurf, TargetVSCode, TargetGoose, @@ -86,6 +88,8 @@ func getConfigPath(target Target) (string, error) { case TargetClaudeCode: // Claude Code uses the ~/.claude.json file return filepath.Join(homeDir, ".claude.json"), nil + case TargetAntigravity: + return filepath.Join(homeDir, ".gemini", "config", "mcp_config.json"), nil case TargetWindsurf: return filepath.Join(homeDir, ".codeium", "windsurf", "mcp_config.json"), nil case TargetVSCode: @@ -327,6 +331,28 @@ func installForClaudeCode(configPath string) error { return writeJSONFile(configPath, config) } +// installForAntigravity installs MCP config for Google Antigravity +func installForAntigravity(configPath string) error { + config, err := readJSONFile(configPath) + if err != nil { + return err + } + + // Get or create mcpServers section + mcpServers, ok := config["mcpServers"].(map[string]interface{}) + if !ok { + mcpServers = make(map[string]interface{}) + } + + // Antigravity keys remote servers off serverUrl; it ignores url and httpUrl + mcpServers["kernel"] = map[string]interface{}{ + "serverUrl": KernelMCPURL, + } + config["mcpServers"] = mcpServers + + return writeJSONFile(configPath, config) +} + // installForWindsurf installs MCP config for Windsurf func installForWindsurf(configPath string) error { config, err := readJSONFile(configPath) @@ -453,6 +479,8 @@ func Install(target Target) error { return installForClaude(configPath) case TargetClaudeCode: return installForClaudeCode(configPath) + case TargetAntigravity: + return installForAntigravity(configPath) case TargetWindsurf: return installForWindsurf(configPath) case TargetVSCode: diff --git a/cmd/mcp/mcp_test.go b/cmd/mcp/mcp_test.go index 86d1befb..59257c87 100644 --- a/cmd/mcp/mcp_test.go +++ b/cmd/mcp/mcp_test.go @@ -119,3 +119,95 @@ func TestInstallForFxClean(t *testing.T) { t.Fatalf("config directory permissions = %o, want 700", got) } } + +func TestInstallForAntigravity(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + t.Setenv("USERPROFILE", home) + + configPath := filepath.Join(home, ".gemini", "config", "mcp_config.json") + if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil { + t.Fatal(err) + } + + existing := `{ + "mcpServers": { + "existing": { + "serverUrl": "https://example.com/mcp" + } + }, + "setting": "preserved" +}` + if err := os.WriteFile(configPath, []byte(existing), 0644); err != nil { + t.Fatal(err) + } + + if err := Install(TargetAntigravity); err != nil { + t.Fatal(err) + } + + data, err := os.ReadFile(configPath) + if err != nil { + t.Fatal(err) + } + + var config map[string]interface{} + if err := json.Unmarshal(data, &config); err != nil { + t.Fatal(err) + } + if config["setting"] != "preserved" { + t.Fatalf("setting = %v, want preserved", config["setting"]) + } + + servers, ok := config["mcpServers"].(map[string]interface{}) + if !ok { + t.Fatalf("mcpServers = %#v, want object", config["mcpServers"]) + } + if _, ok := servers["existing"]; !ok { + t.Fatal("existing MCP server was removed") + } + + kernel, ok := servers["kernel"].(map[string]interface{}) + if !ok { + t.Fatalf("kernel = %#v, want object", servers["kernel"]) + } + if kernel["serverUrl"] != KernelMCPURL { + t.Fatalf("serverUrl = %v, want %s", kernel["serverUrl"], KernelMCPURL) + } + // Antigravity ignores url and httpUrl, so writing either would silently do nothing + if _, ok := kernel["url"]; ok { + t.Fatalf("kernel = %#v, want no url key", kernel) + } +} + +func TestInstallForAntigravityClean(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + t.Setenv("USERPROFILE", home) + + if err := Install(TargetAntigravity); err != nil { + t.Fatal(err) + } + + data, err := os.ReadFile(filepath.Join(home, ".gemini", "config", "mcp_config.json")) + if err != nil { + t.Fatal(err) + } + + var config map[string]interface{} + if err := json.Unmarshal(data, &config); err != nil { + t.Fatal(err) + } + + servers, ok := config["mcpServers"].(map[string]interface{}) + if !ok { + t.Fatalf("mcpServers = %#v, want object", config["mcpServers"]) + } + kernel, ok := servers["kernel"].(map[string]interface{}) + if !ok { + t.Fatalf("kernel = %#v, want object", servers["kernel"]) + } + if kernel["serverUrl"] != KernelMCPURL { + t.Fatalf("serverUrl = %v, want %s", kernel["serverUrl"], KernelMCPURL) + } +}