claude-code-ultimate-guide/examples/scripts/audit-scan.sh
Florian BRUNIAUX 0833e1ca65 fix(audit): correct MCP detection to check ~/.claude.json
The audit script was looking for MCP config in ~/.claude/mcp.json which
doesn't exist. Claude Code actually stores MCP config in ~/.claude.json
under projects.<path>.mcpServers.

Changes:
- audit-scan.sh: Multi-source MCP detection (3 locations with priority)
- audit-scan.sh: Fixed count_pattern() bug causing "0\n0" output
- claude-setup-audit-prompt.md: Updated bash commands for MCP detection
- Version bump: 2.8 → 2.9

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 08:37:01 +01:00

276 lines
8.6 KiB
Bash
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# claude-audit-scan.sh - Fast Claude Code setup scanner
#
# Scans your Claude Code configuration (global + project) and outputs
# a structured report of what's configured, what's missing, and quality patterns.
#
# Usage:
# ./audit-scan.sh # Human-readable output (default)
# ./audit-scan.sh --json # JSON output for Claude processing
# ./audit-scan.sh --help # Show this help
set -euo pipefail
# Colors for human output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Output mode (default: human)
OUTPUT_MODE="human"
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--json)
OUTPUT_MODE="json"
shift
;;
--help|-h)
grep '^#' "$0" | sed 's/^# \?//'
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
# Helper functions
check_file() {
[[ -f "$1" ]] && echo "true" || echo "false"
}
count_files() {
if [[ -d "$1" ]]; then
find "$1" -maxdepth 1 -type f 2>/dev/null | wc -l | tr -d ' '
else
echo "0"
fi
}
check_pattern() {
local file="$1"
local pattern="$2"
if [[ -f "$file" ]]; then
grep -q "$pattern" "$file" 2>/dev/null && echo "true" || echo "false"
else
echo "false"
fi
}
get_file_lines() {
[[ -f "$1" ]] && wc -l < "$1" | tr -d ' ' || echo "0"
}
count_pattern() {
if [[ -f "$1" ]]; then
local count
count=$(grep -c "$2" "$1" 2>/dev/null) || count="0"
echo "$count"
else
echo "0"
fi
}
# Expand home directory
GLOBAL_DIR="${HOME}/.claude"
# === DATA COLLECTION ===
# Global config
GLOBAL_CLAUDE_MD=$(check_file "${GLOBAL_DIR}/CLAUDE.md")
GLOBAL_SETTINGS=$(check_file "${GLOBAL_DIR}/settings.json")
GLOBAL_MCP=$(check_file "${GLOBAL_DIR}/mcp.json")
# Project config
PROJECT_CLAUDE_MD=$(check_file "./CLAUDE.md")
LOCAL_CLAUDE_MD=$(check_file "./.claude/CLAUDE.md")
PROJECT_SETTINGS=$(check_file "./.claude/settings.json")
LOCAL_SETTINGS=$(check_file "./.claude/settings.local.json")
# Extensions
AGENTS_COUNT=$(count_files "./.claude/agents")
COMMANDS_COUNT=$(count_files "./.claude/commands")
SKILLS_COUNT=$(count_files "./.claude/skills")
HOOKS_COUNT=$(count_files "./.claude/hooks")
RULES_COUNT=$(count_files "./.claude/rules")
# Tech stack detection
TECH_STACK="unknown"
if [[ -f "package.json" ]]; then
TECH_STACK="nodejs"
elif [[ -f "pyproject.toml" ]] || [[ -f "requirements.txt" ]]; then
TECH_STACK="python"
elif [[ -f "go.mod" ]]; then
TECH_STACK="go"
elif [[ -f "Cargo.toml" ]]; then
TECH_STACK="rust"
elif [[ -f "composer.json" ]]; then
TECH_STACK="php"
fi
# Quality patterns
HAS_SECURITY_HOOKS="false"
if [[ -d "./.claude/hooks" ]]; then
grep -l "PreToolUse" ./.claude/hooks/* 2>/dev/null >/dev/null && HAS_SECURITY_HOOKS="true"
fi
# MCP servers detection
# Claude Code stores MCP config in multiple locations:
# 1. ~/.claude.json under projects.<cwd>.mcpServers (per-project, most common)
# 2. ~/.claude/mcp.json (legacy global)
# 3. ./.claude/mcp.json (project-level)
MCP_SERVERS=""
MCP_SOURCE=""
CURRENT_DIR=$(pwd)
# Check 1: Project-specific MCP in ~/.claude.json
if [[ -f "${HOME}/.claude.json" ]]; then
if command -v jq &> /dev/null; then
# Get MCP servers for current project path
MCP_SERVERS=$(jq -r --arg path "$CURRENT_DIR" '.projects[$path].mcpServers // {} | keys[]' "${HOME}/.claude.json" 2>/dev/null | tr '\n' ',' | sed 's/,$//')
if [[ -n "$MCP_SERVERS" ]]; then
MCP_SOURCE="~/.claude.json (project)"
fi
fi
fi
# Check 2: Project-level .claude/mcp.json
if [[ -z "$MCP_SERVERS" && -f "./.claude/mcp.json" ]]; then
if command -v jq &> /dev/null; then
MCP_SERVERS=$(jq -r '.mcpServers // {} | keys[]' "./.claude/mcp.json" 2>/dev/null | tr '\n' ',' | sed 's/,$//')
if [[ -n "$MCP_SERVERS" ]]; then
MCP_SOURCE=".claude/mcp.json (project)"
fi
else
MCP_SERVERS=$(grep -oE '"[a-zA-Z0-9_-]+"[[:space:]]*:' "./.claude/mcp.json" 2>/dev/null | head -20 | sed 's/"//g;s/://g' | tr '\n' ',' | sed 's/,$//')
if [[ -n "$MCP_SERVERS" ]]; then
MCP_SOURCE=".claude/mcp.json (project)"
fi
fi
fi
# Check 3: Legacy global ~/.claude/mcp.json
if [[ -z "$MCP_SERVERS" && -f "${GLOBAL_DIR}/mcp.json" ]]; then
if command -v jq &> /dev/null; then
MCP_SERVERS=$(jq -r '.mcpServers // {} | keys[]' "${GLOBAL_DIR}/mcp.json" 2>/dev/null | tr '\n' ',' | sed 's/,$//')
if [[ -n "$MCP_SERVERS" ]]; then
MCP_SOURCE="~/.claude/mcp.json (global)"
fi
else
MCP_SERVERS=$(grep -oE '"[a-zA-Z0-9_-]+"[[:space:]]*:' "${GLOBAL_DIR}/mcp.json" 2>/dev/null | head -20 | sed 's/"//g;s/://g' | tr '\n' ',' | sed 's/,$//')
if [[ -n "$MCP_SERVERS" ]]; then
MCP_SOURCE="~/.claude/mcp.json (global)"
fi
fi
fi
# Count MCP servers
MCP_COUNT=0
if [[ -n "$MCP_SERVERS" ]]; then
MCP_COUNT=$(echo "$MCP_SERVERS" | tr ',' '\n' | grep -c . || echo "0")
fi
# Memory file quality (if exists)
CLAUDE_MD_LINES="0"
CLAUDE_MD_REFS="0"
if [[ -f "./CLAUDE.md" ]]; then
CLAUDE_MD_LINES=$(get_file_lines "./CLAUDE.md")
CLAUDE_MD_REFS=$(count_pattern "./CLAUDE.md" "@")
fi
# Single Source of Truth pattern
HAS_SSOT="false"
if [[ -f "./CLAUDE.md" ]]; then
grep -qE "^@|/docs/|/conventions/" "./CLAUDE.md" 2>/dev/null && HAS_SSOT="true"
fi
# === OUTPUT ===
if [[ "$OUTPUT_MODE" == "json" ]]; then
# JSON output - ensure all values are properly formatted
cat <<EOF
{
"global": {
"claude_md": $GLOBAL_CLAUDE_MD,
"settings": $GLOBAL_SETTINGS,
"mcp_json": $GLOBAL_MCP
},
"project": {
"claude_md": $PROJECT_CLAUDE_MD,
"local_claude_md": $LOCAL_CLAUDE_MD,
"settings": $PROJECT_SETTINGS,
"local_settings": $LOCAL_SETTINGS
},
"extensions": {
"agents": $AGENTS_COUNT,
"commands": $COMMANDS_COUNT,
"skills": $SKILLS_COUNT,
"hooks": $HOOKS_COUNT,
"rules": $RULES_COUNT
},
"tech_stack": "$TECH_STACK",
"quality": {
"has_security_hooks": $HAS_SECURITY_HOOKS,
"has_ssot_references": $HAS_SSOT,
"claude_md_lines": $CLAUDE_MD_LINES,
"claude_md_refs": $CLAUDE_MD_REFS
},
"mcp": {
"configured": $([ -n "$MCP_SERVERS" ] && echo "true" || echo "false"),
"count": $MCP_COUNT,
"servers": "$MCP_SERVERS",
"source": "$MCP_SOURCE"
}
}
EOF
else
# Human-readable output
echo -e "${BLUE}=== CLAUDE CODE SETUP AUDIT ===${NC}\n"
echo -e "${BLUE}📁 GLOBAL CONFIG${NC} (~/.claude/)"
[[ "$GLOBAL_CLAUDE_MD" == "true" ]] && echo -e " ${GREEN}${NC} CLAUDE.md" || echo -e " ${RED}${NC} CLAUDE.md"
[[ "$GLOBAL_SETTINGS" == "true" ]] && echo -e " ${GREEN}${NC} settings.json" || echo -e " ${RED}${NC} settings.json"
[[ "$GLOBAL_MCP" == "true" ]] && echo -e " ${GREEN}${NC} mcp.json (legacy)" || echo -e " ${YELLOW}⚠️${NC} mcp.json (not required, MCP in ~/.claude.json)"
echo -e "\n${BLUE}📁 PROJECT CONFIG${NC} (./)"
[[ "$PROJECT_CLAUDE_MD" == "true" ]] && echo -e " ${GREEN}${NC} CLAUDE.md" || echo -e " ${YELLOW}⚠️${NC} CLAUDE.md (recommended)"
[[ "$LOCAL_CLAUDE_MD" == "true" ]] && echo -e " ${GREEN}${NC} .claude/CLAUDE.md (local)" || echo -e " ${YELLOW}⚠️${NC} .claude/CLAUDE.md (optional)"
[[ "$PROJECT_SETTINGS" == "true" ]] && echo -e " ${GREEN}${NC} .claude/settings.json" || echo -e " ${YELLOW}⚠️${NC} .claude/settings.json (optional)"
echo -e "\n${BLUE}🔧 EXTENSIONS${NC} (.claude/)"
echo -e " Agents: $AGENTS_COUNT"
echo -e " Commands: $COMMANDS_COUNT"
echo -e " Skills: $SKILLS_COUNT"
echo -e " Hooks: $HOOKS_COUNT"
echo -e " Rules: $RULES_COUNT"
echo -e "\n${BLUE}💻 TECH STACK${NC}"
echo -e " Detected: $TECH_STACK"
echo -e "\n${BLUE}✨ QUALITY PATTERNS${NC}"
[[ "$HAS_SECURITY_HOOKS" == "true" ]] && echo -e " ${GREEN}${NC} Security hooks (PreToolUse)" || echo -e " ${RED}${NC} Security hooks (recommended)"
[[ "$HAS_SSOT" == "true" ]] && echo -e " ${GREEN}${NC} Single Source of Truth (@refs)" || echo -e " ${YELLOW}⚠️${NC} SSoT pattern (recommended)"
if [[ "$PROJECT_CLAUDE_MD" == "true" ]]; then
echo -e " ${BLUE}${NC} CLAUDE.md: $CLAUDE_MD_LINES lines, $CLAUDE_MD_REFS @references"
if [[ $CLAUDE_MD_LINES -gt 200 ]]; then
echo -e " ${YELLOW}⚠️${NC} Consider shortening (>200 lines)"
fi
fi
if [[ -n "$MCP_SERVERS" ]]; then
echo -e " ${GREEN}${NC} MCP servers ($MCP_COUNT): $MCP_SERVERS"
echo -e " ${BLUE}Source:${NC} $MCP_SOURCE"
else
echo -e " ${YELLOW}⚠️${NC} No MCP servers configured for this project"
fi
echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "Scan complete! Use ${YELLOW}--json${NC} flag for machine-readable output."
fi