66 "os"
77 "path/filepath"
88 "runtime"
9+ "strings"
910
1011 "github.com/pterm/pterm"
1112 "github.com/spf13/cobra"
@@ -26,13 +27,13 @@ var MCPCmd = &cobra.Command{
2627type Target string
2728
2829const (
29- TargetCursor Target = "cursor"
30- TargetClaude Target = "claude"
30+ TargetCursor Target = "cursor"
31+ TargetClaude Target = "claude"
3132 TargetClaudeCode Target = "claude-code"
32- TargetWindsurf Target = "windsurf"
33- TargetVSCode Target = "vscode"
34- TargetGoose Target = "goose"
35- TargetZed Target = "zed"
33+ TargetWindsurf Target = "windsurf"
34+ TargetVSCode Target = "vscode"
35+ TargetGoose Target = "goose"
36+ TargetZed Target = "zed"
3637)
3738
3839// KernelMCPURL is the URL for the Kernel MCP server
@@ -67,33 +68,37 @@ func getConfigPath(target Target) (string, error) {
6768 case TargetCursor :
6869 return filepath .Join (homeDir , ".cursor" , "mcp.json" ), nil
6970 case TargetClaude :
70- if runtime .GOOS == "darwin" {
71+ switch runtime .GOOS {
72+ case "darwin" :
7173 return filepath .Join (homeDir , "Library" , "Application Support" , "Claude" , "claude_desktop_config.json" ), nil
72- } else if runtime . GOOS == "windows" {
74+ case "windows" :
7375 appData := os .Getenv ("APPDATA" )
7476 if appData == "" {
7577 appData = filepath .Join (homeDir , "AppData" , "Roaming" )
7678 }
7779 return filepath .Join (appData , "Claude" , "claude_desktop_config.json" ), nil
80+ default :
81+ // Linux - Claude Desktop doesn't officially support Linux, but use XDG config
82+ return filepath .Join (homeDir , ".config" , "Claude" , "claude_desktop_config.json" ), nil
7883 }
79- // Linux - Claude Desktop doesn't officially support Linux, but use XDG config
80- return filepath .Join (homeDir , ".config" , "Claude" , "claude_desktop_config.json" ), nil
8184 case TargetClaudeCode :
8285 // Claude Code uses the ~/.claude.json file
8386 return filepath .Join (homeDir , ".claude.json" ), nil
8487 case TargetWindsurf :
8588 return filepath .Join (homeDir , ".codeium" , "windsurf" , "mcp_config.json" ), nil
8689 case TargetVSCode :
87- if runtime .GOOS == "darwin" {
90+ switch runtime .GOOS {
91+ case "darwin" :
8892 return filepath .Join (homeDir , "Library" , "Application Support" , "Code" , "User" , "settings.json" ), nil
89- } else if runtime . GOOS == "windows" {
93+ case "windows" :
9094 appData := os .Getenv ("APPDATA" )
9195 if appData == "" {
9296 appData = filepath .Join (homeDir , "AppData" , "Roaming" )
9397 }
9498 return filepath .Join (appData , "Code" , "User" , "settings.json" ), nil
99+ default :
100+ return filepath .Join (homeDir , ".config" , "Code" , "User" , "settings.json" ), nil
95101 }
96- return filepath .Join (homeDir , ".config" , "Code" , "User" , "settings.json" ), nil
97102 case TargetGoose :
98103 return filepath .Join (homeDir , ".config" , "goose" , "config.yaml" ), nil
99104 case TargetZed :
@@ -111,6 +116,81 @@ type MCPServerConfig struct {
111116 Type string `json:"type,omitempty"`
112117}
113118
119+ // stripJSONComments removes single-line (//) and multi-line (/* */) comments from JSON
120+ // It properly handles strings to avoid removing // or /* */ that appear inside string literals
121+ func stripJSONComments (data []byte ) []byte {
122+ content := string (data )
123+ var result strings.Builder
124+ i := 0
125+ inString := false
126+ inMultiLineComment := false
127+ escapeNext := false
128+
129+ for i < len (content ) {
130+ char := content [i ]
131+
132+ if escapeNext {
133+ result .WriteByte (char )
134+ escapeNext = false
135+ i ++
136+ continue
137+ }
138+
139+ if char == '\\' && inString {
140+ escapeNext = true
141+ result .WriteByte (char )
142+ i ++
143+ continue
144+ }
145+
146+ if char == '"' {
147+ inString = ! inString
148+ result .WriteByte (char )
149+ i ++
150+ continue
151+ }
152+
153+ if inString {
154+ result .WriteByte (char )
155+ i ++
156+ continue
157+ }
158+
159+ if inMultiLineComment {
160+ if i + 1 < len (content ) && char == '*' && content [i + 1 ] == '/' {
161+ inMultiLineComment = false
162+ i += 2
163+ continue
164+ }
165+ i ++
166+ continue
167+ }
168+
169+ if i + 1 < len (content ) && char == '/' && content [i + 1 ] == '/' {
170+ // Single-line comment - skip to end of line
171+ for i < len (content ) && content [i ] != '\n' {
172+ i ++
173+ }
174+ if i < len (content ) {
175+ result .WriteByte ('\n' )
176+ i ++
177+ }
178+ continue
179+ }
180+
181+ if i + 1 < len (content ) && char == '/' && content [i + 1 ] == '*' {
182+ inMultiLineComment = true
183+ i += 2
184+ continue
185+ }
186+
187+ result .WriteByte (char )
188+ i ++
189+ }
190+
191+ return []byte (result .String ())
192+ }
193+
114194// readJSONFile reads and parses a JSON config file
115195func readJSONFile (path string ) (map [string ]interface {}, error ) {
116196 data , err := os .ReadFile (path )
@@ -126,6 +206,9 @@ func readJSONFile(path string) (map[string]interface{}, error) {
126206 return make (map [string ]interface {}), nil
127207 }
128208
209+ // Strip comments to support JSON5 format (used by Zed)
210+ data = stripJSONComments (data )
211+
129212 var config map [string ]interface {}
130213 if err := json .Unmarshal (data , & config ); err != nil {
131214 return nil , fmt .Errorf ("failed to parse JSON: %w" , err )
0 commit comments