fix(doctor): detect MCP server in project-level settings (#1104)

Apply same fix as #1091 to isMCPServerInstalled(): check all three
settings locations (user-level, project-level, and project-local).

- Extract checkMCPInSettings() helper function
- Check ~/.claude/settings.json, .claude/settings.json, and
  .claude/settings.local.json
- Add TestIsMCPServerInstalledProjectLevel with positive and negative cases

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2026-01-14 20:53:51 -08:00
committed by GitHub
parent 511129693e
commit 43ce905896
2 changed files with 85 additions and 3 deletions

View File

@@ -142,15 +142,39 @@ func checkPluginInSettings(settingsPath string) bool {
return false
}
// isMCPServerInstalled checks if MCP server is configured
// isMCPServerInstalled checks if MCP server is configured.
// It checks user-level (~/.claude/settings.json) and project-level settings
// (.claude/settings.json and .claude/settings.local.json).
func isMCPServerInstalled() bool {
home, err := os.UserHomeDir()
if err != nil {
return false
}
settingsPath := filepath.Join(home, ".claude/settings.json")
data, err := os.ReadFile(settingsPath) // #nosec G304 -- settingsPath is constructed from user home dir, not user input
// Check user-level settings
userSettings := filepath.Join(home, ".claude", "settings.json")
if checkMCPInSettings(userSettings) {
return true
}
// Check project-level settings
projectSettings := filepath.Join(".claude", "settings.json")
if checkMCPInSettings(projectSettings) {
return true
}
// Check project-level local settings (gitignored)
projectLocalSettings := filepath.Join(".claude", "settings.local.json")
if checkMCPInSettings(projectLocalSettings) {
return true
}
return false
}
// checkMCPInSettings checks if beads MCP server is configured in a settings file
func checkMCPInSettings(settingsPath string) bool {
data, err := os.ReadFile(settingsPath) // #nosec G304 -- settingsPath is constructed from known safe locations, not user input
if err != nil {
return false
}

View File

@@ -169,6 +169,64 @@ func TestIsMCPServerInstalled(t *testing.T) {
}
}
func TestIsMCPServerInstalledProjectLevel(t *testing.T) {
mcpContent := `{"mcpServers":{"beads":{"command":"beads-mcp"}}}`
// Test that MCP server is detected in each project-level settings file
for _, filename := range []string{"settings.json", "settings.local.json"} {
t.Run(filename, func(t *testing.T) {
tmpDir := t.TempDir()
t.Chdir(tmpDir)
if err := os.MkdirAll(".claude", 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(".claude", filename), []byte(mcpContent), 0o644); err != nil {
t.Fatal(err)
}
if !isMCPServerInstalled() {
t.Errorf("expected to detect MCP server in .claude/%s", filename)
}
})
}
// Test negative cases
t.Run("no mcpServers section", func(t *testing.T) {
tmpDir := t.TempDir()
t.Chdir(tmpDir)
if err := os.MkdirAll(".claude", 0o755); err != nil {
t.Fatal(err)
}
content := `{"hooks":{}}`
if err := os.WriteFile(filepath.Join(".claude", "settings.json"), []byte(content), 0o644); err != nil {
t.Fatal(err)
}
if isMCPServerInstalled() {
t.Error("expected NOT to detect MCP server when mcpServers section missing")
}
})
t.Run("mcpServers but not beads", func(t *testing.T) {
tmpDir := t.TempDir()
t.Chdir(tmpDir)
if err := os.MkdirAll(".claude", 0o755); err != nil {
t.Fatal(err)
}
content := `{"mcpServers":{"other-server":{"command":"other"}}}`
if err := os.WriteFile(filepath.Join(".claude", "settings.json"), []byte(content), 0o644); err != nil {
t.Fatal(err)
}
if isMCPServerInstalled() {
t.Error("expected NOT to detect MCP server when beads not present")
}
})
}
func TestIsBeadsPluginInstalled(t *testing.T) {
// Similar sanity check for plugin detection
result := isBeadsPluginInstalled()