Skip to content

Commit cb0ee0a

Browse files
authored
MCP setup bug on empty JSON file (#1451)
1 parent 3df55c4 commit cb0ee0a

File tree

3 files changed

+98
-18
lines changed

3 files changed

+98
-18
lines changed

src/cmd/cli/command/mcp.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ var mcpSetupCmd = &cobra.Command{
8181
client, _ := cmd.Flags().GetString("client")
8282

8383
if client != "" {
84-
8584
// Aliases mapping
8685
switch client {
8786
case "code":

src/pkg/mcp/setup.go

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package mcp
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"errors"
67
"fmt"
@@ -41,13 +42,13 @@ type VSCodeConfig struct {
4142

4243
// VSCodeMCPServerConfig represents the configuration for a VSCode MCP server
4344
type VSCodeMCPServerConfig struct {
44-
Type string `json:"type"` // Required: "stdio" or "sse"
45-
Command string `json:"command"` // Required for stdio
46-
Args []string `json:"args"` // Required for stdio
47-
URL string `json:"url,omitempty"` // Required for sse
48-
Env map[string]string `json:"env,omitempty"`
45+
Args []string `json:"args,omitempty"` // Required for stdio
46+
Command string `json:"command,omitempty"` // Required for stdio
47+
Env map[string]any `json:"env,omitempty"`
4948
EnvFile string `json:"envFile,omitempty"`
5049
Headers map[string]string `json:"headers,omitempty"` // For sse
50+
Type string `json:"type,omitempty"` // Required: "stdio" or "sse"
51+
URL string `json:"url,omitempty"` // Required for sse
5152
}
5253

5354
// MCPClient represents the supported MCP clients as an enum
@@ -236,18 +237,32 @@ func getVSCodeDefangMCPConfig() (*VSCodeMCPServerConfig, error) {
236237
}
237238

238239
// getVSCodeServerConfig returns a map with the VSCode-specific MCP server config
239-
func getVSCodeServerConfig() (map[string]any, error) {
240+
func getVSCodeServerConfig() (*VSCodeMCPServerConfig, error) {
240241
config, err := getVSCodeDefangMCPConfig()
241242
if err != nil {
242243
return nil, err
243244
}
244-
return map[string]any{
245-
"type": config.Type,
246-
"command": config.Command,
247-
"args": config.Args,
245+
return &VSCodeMCPServerConfig{
246+
Args: config.Args,
247+
Command: config.Command,
248+
Type: config.Type,
248249
}, nil
249250
}
250251

252+
func parseExistingConfig(data []byte, existingData *map[string]any) error {
253+
// Check if file is empty or only contains whitespace
254+
if len(bytes.TrimSpace(data)) == 0 {
255+
// File is empty, treat as new config
256+
*existingData = make(map[string]any)
257+
} else {
258+
// Parse the JSON into a generic map to preserve all settings
259+
if err := json.Unmarshal(data, &existingData); err != nil {
260+
return fmt.Errorf("failed to unmarshal existing config: %w", err)
261+
}
262+
}
263+
return nil
264+
}
265+
251266
// handleVSCodeConfig handles the special case for VSCode mcp.json
252267
func handleVSCodeConfig(configPath string) error {
253268
// Create or update the config file
@@ -259,9 +274,8 @@ func handleVSCodeConfig(configPath string) error {
259274

260275
// Check if the file exists
261276
if data, err := os.ReadFile(configPath); err == nil {
262-
// File exists, parse it
263-
if err := json.Unmarshal(data, &existingData); err != nil {
264-
return fmt.Errorf("failed to unmarshal existing vscode config %w", err)
277+
if err := parseExistingConfig(data, &existingData); err != nil {
278+
return err
265279
}
266280

267281
// Check if "servers" section exists
@@ -310,9 +324,8 @@ func handleStandardConfig(configPath string) error {
310324

311325
// Check if the file exists
312326
if data, err := os.ReadFile(configPath); err == nil {
313-
// Parse the JSON into a generic map to preserve all settings
314-
if err := json.Unmarshal(data, &existingData); err != nil {
315-
return fmt.Errorf("failed to unmarshal existing config: %w", err)
327+
if err := parseExistingConfig(data, &existingData); err != nil {
328+
return err
316329
}
317330

318331
// Try to extract MCPServers from existing data
@@ -399,7 +412,7 @@ func SetupClient(clientStr string) error {
399412
}
400413
}
401414

402-
term.Infof("Restart %s for the changes to take effect.\n", client)
415+
term.Infof("Ensure %s is upgraded to the latest version and restarted for mcp settings to take effect.\n", client)
403416

404417
return nil
405418
}

src/pkg/mcp/setup_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,42 @@ func TestWriteConfig(t *testing.T) {
444444
existingData: `{"servers": "not an object}`,
445445
expectedError: true,
446446
},
447+
{
448+
name: "vscode_config_new_file_empty",
449+
fileExists: true,
450+
vscodeConfig: true,
451+
existingData: "",
452+
expectedData: `{
453+
"servers": {
454+
"defang": {
455+
"args": [
456+
"mcp",
457+
"serve"
458+
],
459+
"command": %s,
460+
"type": "stdio"
461+
}
462+
}
463+
}`,
464+
},
465+
{
466+
name: "vscode_config_new_file_with_whitespace",
467+
fileExists: true,
468+
vscodeConfig: true,
469+
existingData: " \t \n ",
470+
expectedData: `{
471+
"servers": {
472+
"defang": {
473+
"args": [
474+
"mcp",
475+
"serve"
476+
],
477+
"command": %s,
478+
"type": "stdio"
479+
}
480+
}
481+
}`,
482+
},
447483
{
448484
name: "standard_config_new_file",
449485
fileExists: false,
@@ -593,6 +629,38 @@ func TestWriteConfig(t *testing.T) {
593629
existingData: `{"mcpServers": "not an object}`,
594630
expectedError: true,
595631
},
632+
{
633+
name: "standard_config_new_file_empty",
634+
fileExists: true,
635+
existingData: "",
636+
expectedData: `{
637+
"mcpServers": {
638+
"defang": {
639+
"command": %s,
640+
"args": [
641+
"mcp",
642+
"serve"
643+
]
644+
}
645+
}
646+
}`,
647+
},
648+
{
649+
name: "standard_config_new_file_with_whitespace",
650+
fileExists: true,
651+
existingData: " \t \n ",
652+
expectedData: `{
653+
"mcpServers": {
654+
"defang": {
655+
"command": %s,
656+
"args": [
657+
"mcp",
658+
"serve"
659+
]
660+
}
661+
}
662+
}`,
663+
},
596664
}
597665
for _, tt := range test {
598666
t.Run(tt.name, func(t *testing.T) {

0 commit comments

Comments
 (0)