Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cmd/mcp/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
44 changes: 36 additions & 8 deletions cmd/mcp/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -46,6 +47,7 @@ func AllTargets() []Target {
TargetCursor,
TargetClaude,
TargetClaudeCode,
TargetAntigravity,
TargetWindsurf,
TargetVSCode,
TargetGoose,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
92 changes: 92 additions & 0 deletions cmd/mcp/mcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Loading