Skip to content

Commit f3b0e78

Browse files
committed
feat(mcp): support JSON with comments parsing
1 parent 33a4cd7 commit f3b0e78

File tree

2 files changed

+97
-14
lines changed

2 files changed

+97
-14
lines changed

cmd/mcp/mcp.go

Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
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{
2627
type Target string
2728

2829
const (
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
115195
func 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)

cmd/mcp/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func runServer(cmd *cobra.Command, args []string) {
4848

4949
pterm.Println()
5050
pterm.DefaultSection.Println("Documentation")
51-
pterm.Println(" https://docs.onkernel.com/reference/mcp-server")
51+
pterm.Println(" https://onkernel.com/docs/reference/mcp-server")
5252

5353
pterm.Println()
5454
pterm.Info.Println("Use 'kernel mcp install --target <tool>' to configure your AI tool automatically.")

0 commit comments

Comments
 (0)