diff --git a/internal/aisettings/claudemem.go b/internal/aisettings/claudemem.go index 5817eb3..39f1ef8 100644 --- a/internal/aisettings/claudemem.go +++ b/internal/aisettings/claudemem.go @@ -944,12 +944,15 @@ func ensureMCPEntry(path, dotPath string, variant mcpEntryVariant) (bool, error) func ensureScopedMCPEntry(path, dotPath string, variant mcpEntryVariant, home, pluginRoot string) (bool, error) { doc := map[string]json.RawMessage{} - if raw, err := os.ReadFile(path); err == nil { + raw, err := os.ReadFile(path) + if err != nil && !os.IsNotExist(err) { + return false, err + } + // agy ships its MCP config as an empty file; treat that as no servers. + if len(bytes.TrimSpace(raw)) > 0 { if err := json.Unmarshal(raw, &doc); err != nil { return false, fmt.Errorf("parse MCP config %s: %w", path, err) } - } else if !os.IsNotExist(err) { - return false, err } servers := map[string]json.RawMessage{} if raw := doc["mcpServers"]; len(raw) > 0 { diff --git a/internal/aisettings/claudemem_selection_test.go b/internal/aisettings/claudemem_selection_test.go index 46bcd50..3e3a736 100644 --- a/internal/aisettings/claudemem_selection_test.go +++ b/internal/aisettings/claudemem_selection_test.go @@ -204,3 +204,20 @@ func TestSelectedAntigravityGetsMCPRecall(t *testing.T) { t.Fatal("unselected qwen was wired") } } + +// agy creates mcp_config.json as an empty file; the entry still lands, while +// a non-empty broken file is still rejected. +func TestMCPEntryAcceptsEmptyConfigFile(t *testing.T) { + path := filepath.Join(t.TempDir(), "mcp_config.json") + mustWriteFile(t, path, "") + if _, err := ensureMCPEntry(path, "/opt/bin/dot", mcpVariantStandard); err != nil { + t.Fatal(err) + } + if !hasManagedMCPEntry(path, "/opt/bin/dot") { + t.Fatal("entry missing after empty config") + } + mustWriteFile(t, path, "{broken") + if _, err := ensureMCPEntry(path, "/opt/bin/dot", mcpVariantStandard); err == nil { + t.Fatal("broken config accepted") + } +}