library: Pi's mcp.json is backed up to ~/.config/magpie/backups before magpie moves it whole to mcp-adapter.json, as it already was before a merge (#1097, heliar-k)

piMove renamed mcp.json to mcp-adapter.json when the adapter's file wasn't
there yet, with no copy kept. The user's file is now copied aside first;
one that can't be copied isn't moved.

Verified: TestPiMCPWholeMoveKeepsABackup (the reporter's layout: adapter
only in npm/node_modules, packages: [], a user-owned mcp.json) fails
without the change ("backups of mcp.json: []") and passes with it.
go test -race ./internal/library green under a temp HOME.
This commit is contained in:
yetone committed 2026-10-07 16:49:30 +08:00
1 parent c83c0d5fef
commit d40fee800d
2 files changed
+37 -3

No files matched your search

+7 -3
View File
@@ -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
+30
View File
@@ -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))
}
}