From d3da46d5d5d0d9106225058e252cc36f04b081a9 Mon Sep 17 00:00:00 2001 From: jango-blockchained Date: Sat, 1 Feb 2025 06:45:57 +0100 Subject: [PATCH] Enhance Claude Desktop MCP Setup Script with Robust Installation and Configuration Management - Added comprehensive error handling and validation functions - Implemented backup and upgrade/clean install workflow - Enhanced system requirements and dependency checks - Improved configuration management with secure token and URL validation - Added detailed user interaction and informative console output - Implemented flexible installation modes with existing configuration preservation --- claude-desktop-macos-setup.sh | 335 ++++++++++++++++++++++++++-------- 1 file changed, 256 insertions(+), 79 deletions(-) diff --git a/claude-desktop-macos-setup.sh b/claude-desktop-macos-setup.sh index 09542f0..63abd74 100644 --- a/claude-desktop-macos-setup.sh +++ b/claude-desktop-macos-setup.sh @@ -6,127 +6,307 @@ RED='\033[0;31m' GREEN='\033[0;32m' BLUE='\033[0;34m' +YELLOW='\033[0;33m' NC='\033[0m' echo -e "${BLUE}Setting up MCP Integration for Claude Desktop${NC}" +# Error handling function +handle_error() { + echo -e "${RED}Error: $1${NC}" + exit 1 +} + # Function to compare version numbers version_greater_equal() { printf '%s\n' "$2" "$1" | sort -V -C } +# Function to backup existing configuration +backup_config() { + local backup_dir="$MCP_DIR/backups/$(date +%Y%m%d_%H%M%S)" + echo -e "${BLUE}Creating backup at: $backup_dir${NC}" + + mkdir -p "$backup_dir" || handle_error "Failed to create backup directory" + + # Backup existing configurations if they exist + if [ -f "$CLAUDE_CONFIG_DIR/claude_desktop_config.json" ]; then + cp "$CLAUDE_CONFIG_DIR/claude_desktop_config.json" "$backup_dir/" || handle_error "Failed to backup Claude config" + fi + + if [ -f "$MCP_DIR/homeassistant-mcp/.env" ]; then + cp "$MCP_DIR/homeassistant-mcp/.env" "$backup_dir/" || handle_error "Failed to backup .env file" + fi + + echo -e "${GREEN}Backup created successfully${NC}" + return 0 +} + +# Function to check for existing installation +check_existing_installation() { + local has_existing=false + local config_exists=false + local repo_exists=false + + if [ -d "$MCP_DIR/homeassistant-mcp" ]; then + repo_exists=true + fi + + if [ -f "$CLAUDE_CONFIG_DIR/claude_desktop_config.json" ] || [ -f "$MCP_DIR/homeassistant-mcp/.env" ]; then + config_exists=true + fi + + if $repo_exists || $config_exists; then + echo -e "${YELLOW}Existing MCP installation detected${NC}" + echo -e "Found:" + $repo_exists && echo " - MCP repository at $MCP_DIR/homeassistant-mcp" + [ -f "$CLAUDE_CONFIG_DIR/claude_desktop_config.json" ] && echo " - Claude Desktop configuration" + [ -f "$MCP_DIR/homeassistant-mcp/.env" ] && echo " - Environment configuration" + + while true; do + echo -e "\nPlease choose an option:" + echo "1) Upgrade existing installation (preserves configuration)" + echo "2) Clean reinstall (backs up and replaces configuration)" + echo "3) Exit" + read -p "Enter your choice (1-3): " choice + + case $choice in + 1) + echo -e "${BLUE}Upgrading existing installation...${NC}" + backup_config + UPGRADE_MODE=true + break + ;; + 2) + echo -e "${BLUE}Performing clean reinstall...${NC}" + backup_config + + # Remove existing installation + if [ -d "$MCP_DIR/homeassistant-mcp" ]; then + rm -rf "$MCP_DIR/homeassistant-mcp" || handle_error "Failed to remove existing repository" + fi + if [ -f "$CLAUDE_CONFIG_DIR/claude_desktop_config.json" ]; then + rm "$CLAUDE_CONFIG_DIR/claude_desktop_config.json" || handle_error "Failed to remove existing Claude config" + fi + CLEAN_INSTALL=true + break + ;; + 3) + echo -e "${BLUE}Installation cancelled${NC}" + exit 0 + ;; + *) + echo -e "${RED}Invalid choice. Please enter 1, 2, or 3${NC}" + ;; + esac + done + fi +} + +# Create MCP directory if it doesn't exist +MCP_DIR="$HOME/.mcp" +mkdir -p "$MCP_DIR" || handle_error "Failed to create MCP directory" + +# Create Claude Desktop config directory (macOS specific path) +CLAUDE_CONFIG_DIR="$HOME/Library/Application Support/Claude" +mkdir -p "$CLAUDE_CONFIG_DIR" || handle_error "Failed to create Claude config directory" + +# Initialize installation mode flags +UPGRADE_MODE=false +CLEAN_INSTALL=false + +# Check for existing installation before proceeding +check_existing_installation + +# Check system requirements +echo -e "${BLUE}Checking system requirements...${NC}" + # Check if Homebrew is installed if ! command -v brew &> /dev/null; then - echo -e "${RED}Homebrew is not installed. Installing Homebrew...${NC}" - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + echo -e "${YELLOW}Homebrew is not installed. Installing Homebrew...${NC}" + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || handle_error "Failed to install Homebrew" fi # Check if Node.js is installed if ! command -v node &> /dev/null; then - echo -e "${RED}Node.js is not installed. Installing via Homebrew...${NC}" - brew install node@20 - brew link node@20 + echo -e "${YELLOW}Node.js is not installed. Installing via Homebrew...${NC}" + brew install node@20 || handle_error "Failed to install Node.js" + brew link node@20 || handle_error "Failed to link Node.js" else NODE_VERSION=$(node -v) if ! version_greater_equal "${NODE_VERSION//v/}" "20.10.0"; then - echo -e "${RED}Node.js version must be 20.10.0 or higher. Current version: $NODE_VERSION${NC}" + echo -e "${YELLOW}Node.js version must be 20.10.0 or higher. Current version: $NODE_VERSION${NC}" echo -e "${BLUE}Installing Node.js 20 via Homebrew...${NC}" - brew install node@20 - brew link node@20 + brew install node@20 || handle_error "Failed to install Node.js" + brew link node@20 || handle_error "Failed to link Node.js" fi fi # Check if npm is installed if ! command -v npm &> /dev/null; then - echo -e "${RED}npm is not installed. Please install npm and try again.${NC}" - exit 1 + handle_error "npm is not installed. Please install npm and try again." fi # Check if jq is installed if ! command -v jq &> /dev/null; then - echo -e "${RED}jq is not installed. Installing via Homebrew...${NC}" - brew install jq + echo -e "${YELLOW}jq is not installed. Installing via Homebrew...${NC}" + brew install jq || handle_error "Failed to install jq" fi -# Create MCP directory if it doesn't exist -MCP_DIR="$HOME/.mcp" -mkdir -p "$MCP_DIR" +# Check for required development tools +echo -e "${BLUE}Checking development dependencies...${NC}" +REQUIRED_TOOLS=("git" "curl" "typescript") +for tool in "${REQUIRED_TOOLS[@]}"; do + if ! command -v "$tool" &> /dev/null; then + case $tool in + "typescript") + echo -e "${YELLOW}TypeScript is not installed. Installing globally...${NC}" + npm install -g typescript || handle_error "Failed to install TypeScript" + ;; + *) + echo -e "${YELLOW}$tool is not installed. Installing via Homebrew...${NC}" + brew install "$tool" || handle_error "Failed to install $tool" + ;; + esac + fi +done # Clone the Home Assistant MCP repository echo -e "${BLUE}Cloning Home Assistant MCP repository...${NC}" -git clone https://github.com/jango-blockchained/homeassistant-mcp.git "$MCP_DIR/homeassistant-mcp" -cd "$MCP_DIR/homeassistant-mcp" +if [ -d "$MCP_DIR/homeassistant-mcp" ] && [ "$CLEAN_INSTALL" = false ]; then + echo -e "${YELLOW}Repository exists. Updating...${NC}" + cd "$MCP_DIR/homeassistant-mcp" || handle_error "Failed to change directory" + git fetch origin || handle_error "Failed to fetch updates" + git reset --hard origin/main || handle_error "Failed to reset to main branch" + git clean -fd || handle_error "Failed to clean repository" +else + if [ -d "$MCP_DIR/homeassistant-mcp" ]; then + rm -rf "$MCP_DIR/homeassistant-mcp" || handle_error "Failed to remove existing repository" + fi + git clone https://github.com/jango-blockchained/homeassistant-mcp.git "$MCP_DIR/homeassistant-mcp" || handle_error "Failed to clone repository" + cd "$MCP_DIR/homeassistant-mcp" || handle_error "Failed to change directory" +fi # Install dependencies and build echo -e "${BLUE}Installing dependencies and building...${NC}" -npm install -npm run build +npm ci || handle_error "Failed to install npm dependencies" +npm run build || handle_error "Failed to build project" -# Create Claude Desktop config directory (macOS specific path) -CLAUDE_CONFIG_DIR="$HOME/Library/Application Support/Claude" -mkdir -p "$CLAUDE_CONFIG_DIR" +# If upgrading, try to reuse existing configuration +if [ "$UPGRADE_MODE" = true ] && [ -f "$CLAUDE_CONFIG_DIR/claude_desktop_config.json" ]; then + echo -e "${BLUE}Reusing existing configuration...${NC}" + # Extract existing values + if [ -f "$MCP_DIR/homeassistant-mcp/.env" ]; then + source "$MCP_DIR/homeassistant-mcp/.env" + HASS_HOST=${HASS_HOST:-""} + HASS_TOKEN=${HASS_TOKEN:-""} + HASS_SOCKET_URL=${HASS_SOCKET_URL:-""} + fi + + # If values are missing, prompt for them + if [ -z "$HASS_HOST" ] || [ -z "$HASS_TOKEN" ]; then + echo -e "${YELLOW}Some configuration values are missing. Please provide them:${NC}" + # Get and validate Home Assistant URL + while true; do + read -p "Home Assistant URL (e.g., http://homeassistant.local:8123): " HASS_HOST + if validate_url "$HASS_HOST"; then + break + fi + done -# Prompt for configurations -echo -e "${BLUE}Please enter your configurations:${NC}" -read -p "Home Assistant URL (e.g., http://homeassistant.local:8123): " HASS_HOST -read -p "Home Assistant Long-lived access token: " HASS_TOKEN + # Get and validate Home Assistant token + while true; do + read -p "Home Assistant Long-lived access token: " HASS_TOKEN + if validate_token "$HASS_TOKEN"; then + break + fi + done -# Create .env file for Home Assistant -cat > "$MCP_DIR/homeassistant-mcp/.env" << EOL + # Create WebSocket URL from HASS_HOST + HASS_SOCKET_URL="${HASS_HOST/http/ws}/api/websocket" + fi +else + # Prompt for configurations with validation + echo -e "${BLUE}Please enter your configurations:${NC}" + + # Function to validate URL + validate_url() { + if [[ ! "$1" =~ ^https?:// ]]; then + handle_error "Invalid URL format. URL must start with http:// or https://" + fi + } + + # Function to validate token + validate_token() { + if [[ -z "$1" ]]; then + handle_error "Token cannot be empty" + fi + } + + # Get and validate Home Assistant URL + while true; do + read -p "Home Assistant URL (e.g., http://homeassistant.local:8123): " HASS_HOST + if validate_url "$HASS_HOST"; then + break + fi + done + + # Get and validate Home Assistant token + while true; do + read -p "Home Assistant Long-lived access token: " HASS_TOKEN + if validate_token "$HASS_TOKEN"; then + break + fi + done + + # Create WebSocket URL from HASS_HOST + HASS_SOCKET_URL="${HASS_HOST/http/ws}/api/websocket" + + # Create .env file for Home Assistant + cat > "$MCP_DIR/homeassistant-mcp/.env" << EOL NODE_ENV=production HASS_HOST=$HASS_HOST HASS_TOKEN=$HASS_TOKEN +HASS_SOCKET_URL=$HASS_SOCKET_URL PORT=3000 +DEBUG=false EOL -# Create base configuration for Home Assistant -CONFIG_JSON='{ - "mcpServers": { - "homeassistant": { - "command": "node", - "args": [ - "'$MCP_DIR'/homeassistant-mcp/dist/index.js" - ], - "env": { - "HASS_TOKEN": "'$HASS_TOKEN'", - "HASS_HOST": "'$HASS_HOST'", - "NODE_ENV": "production", - "PORT": "3000" - } - } - } -}' - -# Prompt for enabling Brave Search -read -p "Do you want to enable Brave Search integration? (y/n): " ENABLE_BRAVE_SEARCH - -if [[ $ENABLE_BRAVE_SEARCH =~ ^[Yy]$ ]]; then - # Install Brave Search MCP globally only if enabled - echo -e "${BLUE}Installing Brave Search MCP...${NC}" - npm install -g @modelcontextprotocol/server-brave-search - - read -p "Brave Search API Key: " BRAVE_API_KEY - - # Add Brave Search to the configuration - CONFIG_JSON=$(echo $CONFIG_JSON | jq '.mcpServers += { - "brave-search": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-brave-search"], - "env": { - "BRAVE_API_KEY": "'$BRAVE_API_KEY'" + # Create base configuration for Home Assistant + CONFIG_JSON='{ + "mcpServers": { + "homeassistant": { + "command": "node", + "args": [ + "'$MCP_DIR'/homeassistant-mcp/dist/index.js" + ], + "env": { + "HASS_TOKEN": "'$HASS_TOKEN'", + "HASS_HOST": "'$HASS_HOST'", + "HASS_SOCKET_URL": "'$HASS_SOCKET_URL'", + "NODE_ENV": "production", + "PORT": "3000", + "DEBUG": "false" + } } } - }') + }' + + # Write the final configuration to file + echo $CONFIG_JSON | jq '.' > "$CLAUDE_CONFIG_DIR/claude_desktop_config.json" || handle_error "Failed to write configuration file" + + # Set proper permissions + chmod 600 "$CLAUDE_CONFIG_DIR/claude_desktop_config.json" || handle_error "Failed to set permissions on config file" + chmod 600 "$MCP_DIR/homeassistant-mcp/.env" || handle_error "Failed to set permissions on .env file" fi -# Write the final configuration to file -echo $CONFIG_JSON | jq '.' > "$CLAUDE_CONFIG_DIR/claude_desktop_config.json" - -# Set proper permissions -chmod 600 "$CLAUDE_CONFIG_DIR/claude_desktop_config.json" -chmod 600 "$MCP_DIR/homeassistant-mcp/.env" - echo -e "${GREEN}Installation complete!${NC}" +if [ "$UPGRADE_MODE" = true ]; then + echo -e "${BLUE}Upgraded existing installation${NC}" +elif [ "$CLEAN_INSTALL" = true ]; then + echo -e "${BLUE}Performed clean installation${NC}" + echo -e "${YELLOW}Your previous configuration has been backed up to: $MCP_DIR/backups/$(ls -t "$MCP_DIR/backups" | head -n1)${NC}" +fi echo -e "${BLUE}Configuration files created at:${NC}" echo " - $CLAUDE_CONFIG_DIR/claude_desktop_config.json" echo " - $MCP_DIR/homeassistant-mcp/.env" @@ -134,19 +314,16 @@ echo -e "${BLUE}To use the integration:${NC}" echo "1. Make sure Claude Desktop is installed from https://claude.ai/download" echo "2. Restart Claude Desktop" echo "3. Home Assistant MCP integration is now available" -if [[ $ENABLE_BRAVE_SEARCH =~ ^[Yy]$ ]]; then - echo "4. Brave Search MCP integration is also available" -fi -echo -e "${RED}Note: Keep your access tokens and API keys secure and never share them with others${NC}" +echo -e "${YELLOW}Note: Keep your access tokens and API keys secure and never share them with others${NC}" # Optional: Test the installations read -p "Would you like to test the installations? (y/n) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then echo -e "${BLUE}Testing Home Assistant MCP connection...${NC}" - node "$MCP_DIR/homeassistant-mcp/dist/index.js" test - if [[ $ENABLE_BRAVE_SEARCH =~ ^[Yy]$ ]]; then - echo -e "${BLUE}Testing Brave Search MCP...${NC}" - npx @modelcontextprotocol/server-brave-search test + if ! node "$MCP_DIR/homeassistant-mcp/dist/index.js" test; then + echo -e "${RED}Home Assistant MCP test failed. Please check your configuration and try again.${NC}" + else + echo -e "${GREEN}Home Assistant MCP test successful!${NC}" fi fi \ No newline at end of file