fix(mcp): read .claude.json + mcp_servers.json, handle type/transport + array commands

This commit is contained in:
2026-05-23 11:41:57 +01:00
parent 9633f9ad4c
commit 8f5200a7f2
6 changed files with 186 additions and 68 deletions
+28 -24
View File
@@ -42,25 +42,28 @@ func TestRenderProducesExpectedTargets(t *testing.T) {
assert.Equal(t, adapter.RenderedFile, seen[claudemdDest].Kind)
assert.Contains(t, string(seen[claudemdDest].Content), "# global")
settingsDest := filepath.Join(home, ".claude", "settings.json")
assert.Equal(t, adapter.RenderedFile, seen[settingsDest].Kind)
settingsBody := string(seen[settingsDest].Content)
assert.Contains(t, settingsBody, "filepuff")
assert.Contains(t, settingsBody, "/bin/filepuff")
mcpDest := filepath.Join(home, ".claude", "mcp_servers.json")
assert.Equal(t, adapter.RenderedFile, seen[mcpDest].Kind)
body := string(seen[mcpDest].Content)
assert.Contains(t, body, "filepuff")
assert.Contains(t, body, "/bin/filepuff")
assert.Contains(t, body, "\"type\": \"stdio\"")
}
func TestRenderMergesExistingSettings(t *testing.T) {
func TestRenderMergesLiveClaudeJSON(t *testing.T) {
home := t.TempDir()
base := filepath.Join(home, ".claude")
require.NoError(t, os.MkdirAll(base, 0o750))
// Existing settings.json with user-managed keys.
// Existing ~/.claude.json with user-managed top-level keys.
existing := map[string]any{
"hooks": map[string]any{"preToolUse": []string{"/bin/hook"}},
"permissions": map[string]any{"allow": []string{"Bash"}},
"env": map[string]any{"ANTHROPIC_DEFAULT_MODEL": "opus"},
"projects": map[string]any{"/path": map[string]any{"x": 1}},
"shellIntegration": true,
"mcpServers": map[string]any{
"old": map[string]any{"command": "/bin/old", "type": "stdio"},
},
}
body, _ := json.Marshal(existing)
require.NoError(t, os.WriteFile(filepath.Join(base, "settings.json"), body, 0o600))
require.NoError(t, os.WriteFile(filepath.Join(home, ".claude.json"), body, 0o600))
ad := New(WithHome(home))
b := &canonical.Bundle{
@@ -75,20 +78,20 @@ func TestRenderMergesExistingSettings(t *testing.T) {
seen := map[string]adapter.File{}
fs.ForEach(func(f adapter.File) { seen[f.Dest] = f })
settingsDest := filepath.Join(base, "settings.json")
livePath := filepath.Join(home, ".claude.json")
var parsed map[string]any
require.NoError(t, json.Unmarshal(seen[settingsDest].Content, &parsed))
require.NoError(t, json.Unmarshal(seen[livePath].Content, &parsed))
// User keys preserved.
assert.Contains(t, parsed, "hooks")
assert.Contains(t, parsed, "permissions")
assert.Contains(t, parsed, "env")
// MCP updated.
// User-managed top-level keys preserved.
assert.Contains(t, parsed, "projects")
assert.Contains(t, parsed, "shellIntegration")
// mcpServers replaced with canonical content.
mcp := parsed["mcpServers"].(map[string]any)
assert.Contains(t, mcp, "filepuff")
// MCP entries must NOT have a "type" field (claude-code style).
assert.NotContains(t, mcp, "old")
// Each entry uses Claude's "type" key.
fp := mcp["filepuff"].(map[string]any)
assert.NotContains(t, fp, "type")
assert.Equal(t, "stdio", fp["type"])
}
func TestImport(t *testing.T) {
@@ -107,16 +110,17 @@ func TestImport(t *testing.T) {
filepath.Join(base, "CLAUDE.md"),
[]byte("# global"), 0o600))
settings, _ := json.Marshal(map[string]any{
// Live ~/.claude.json with mcpServers (the primary location).
liveBody, _ := json.Marshal(map[string]any{
"mcpServers": map[string]any{
"filepuff": map[string]any{
"command": "/bin/filepuff",
"transport": "stdio",
"command": "/bin/filepuff",
"type": "stdio",
},
},
})
require.NoError(t, os.WriteFile(
filepath.Join(base, "settings.json"), settings, 0o600))
filepath.Join(home, ".claude.json"), liveBody, 0o600))
ad := New(WithHome(home))
res, err := ad.Import(home)