diff --git a/internal/library/pimcp.go b/internal/library/pimcp.go index fd054d45..ad6c70cb 100644 --- a/internal/library/pimcp.go +++ b/internal/library/pimcp.go @@ -267,8 +267,8 @@ var piKey = regexp.MustCompile(`^[A-Za-z0-9_-]+$`) // piMove moves what pi-mcp-adapter read from mcp.json into // mcp-adapter.json: the whole file when there is none yet, as the adapter's -// own warning says; into one there, each server and setting it hasn't got -// (the file backed up first). A server or setting the two have +// own warning says; into one there, each server and setting it hasn't got. +// Either way the files are backed up first. A server or setting the two have // differently stays in mcp.json for the user to merge, as does anything // the adapter never read; mcp.json goes once nothing is left in it. // @@ -285,7 +285,11 @@ func piMove(from, to string, serversOnly bool) { return } if !serversOnly && !exists(to) { - os.Rename(from, to) + // the user's mcp.json is copied aside before it is moved, as it is + // before a merge (#1097); one that can't be kept stays where it is + if newBackups().keep("pi", from) == nil { + os.Rename(from, to) + } return } var have map[string]json.RawMessage diff --git a/internal/library/pimcp_test.go b/internal/library/pimcp_test.go index c6a530bf..be0e587c 100644 --- a/internal/library/pimcp_test.go +++ b/internal/library/pimcp_test.go @@ -313,3 +313,33 @@ func TestPiNativeVersions(t *testing.T) { } } } + +// heliar-k on #1097: the adapter found, mcp.json was moved whole to +// mcp-adapter.json with no backup, the servers the user added with +// `pi mcp add` leaving the file Pi reads. Whatever moves it, the user's +// mcp.json is copied aside first, byte for byte, and its servers survive. +func TestPiMCPWholeMoveKeepsABackup(t *testing.T) { + h := sandbox(t) + d := filepath.Join(h, ".pi/agent") + write(t, filepath.Join(d, "settings.json"), `{ "quietStartup": false, "packages": [] }`) + write(t, filepath.Join(d, "npm/node_modules/pi-mcp-adapter/package.json"), `{"name": "pi-mcp-adapter", "version": "4.0.0"}`) + old, adapter := filepath.Join(d, "mcp.json"), filepath.Join(d, "mcp-adapter.json") + mine := `{ "mcpServers": { "demo": { "url": "https://mcp.exa.ai/mcp" } } } +` + write(t, old, mine) + targetByID("pi") + bs, _ := filepath.Glob(filepath.Join(BackupDir(), "*", "pi", "mcp.json")) + if len(bs) != 1 { + t.Fatalf("backups of mcp.json: %v", bs) + } + if got := read(t, bs[0]); got != mine { + t.Errorf("backup: %q, want the user's %q", got, mine) + } + if exists(old) { + if piServers(t, old)["demo"] == nil { + t.Errorf("mcp.json lost demo: %s", read(t, old)) + } + } else if piServers(t, adapter)["demo"] == nil { + t.Errorf("demo neither in mcp.json nor in mcp-adapter.json: %s", read(t, adapter)) + } +}