1673 lines
57 KiB
Bash
Executable File
1673 lines
57 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# ============================================================================
|
|
# Zen MCP Server Setup Script
|
|
#
|
|
# A platform-agnostic setup script that works on macOS, Linux, and WSL.
|
|
# Handles environment setup, dependency installation, and configuration.
|
|
# ============================================================================
|
|
|
|
# Initialize pyenv if available (do this early)
|
|
if [[ -d "$HOME/.pyenv" ]]; then
|
|
export PYENV_ROOT="$HOME/.pyenv"
|
|
export PATH="$PYENV_ROOT/bin:$PATH"
|
|
if command -v pyenv &> /dev/null; then
|
|
eval "$(pyenv init --path)" 2>/dev/null || true
|
|
eval "$(pyenv init -)" 2>/dev/null || true
|
|
fi
|
|
fi
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Constants and Configuration
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# Colors for output (ANSI codes work on all platforms)
|
|
readonly GREEN='\033[0;32m'
|
|
readonly YELLOW='\033[1;33m'
|
|
readonly RED='\033[0;31m'
|
|
readonly NC='\033[0m' # No Color
|
|
|
|
# Configuration
|
|
readonly VENV_PATH=".zen_venv"
|
|
readonly DOCKER_CLEANED_FLAG=".docker_cleaned"
|
|
readonly DESKTOP_CONFIG_FLAG=".desktop_configured"
|
|
readonly LOG_DIR="logs"
|
|
readonly LOG_FILE="mcp_server.log"
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Utility Functions
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# Print colored output
|
|
print_success() {
|
|
echo -e "${GREEN}✓${NC} $1" >&2
|
|
}
|
|
|
|
print_error() {
|
|
echo -e "${RED}✗${NC} $1" >&2
|
|
}
|
|
|
|
print_warning() {
|
|
echo -e "${YELLOW}!${NC} $1" >&2
|
|
}
|
|
|
|
print_info() {
|
|
echo -e "${YELLOW}$1${NC}" >&2
|
|
}
|
|
|
|
# Get the script's directory (works on all platforms)
|
|
get_script_dir() {
|
|
cd "$(dirname "$0")" && pwd
|
|
}
|
|
|
|
# Extract version from config.py
|
|
get_version() {
|
|
grep -E '^__version__ = ' config.py 2>/dev/null | sed 's/__version__ = "\(.*\)"/\1/' || echo "unknown"
|
|
}
|
|
|
|
# Clear Python cache files to prevent import issues
|
|
clear_python_cache() {
|
|
print_info "Clearing Python cache files..."
|
|
find . -name "*.pyc" -delete 2>/dev/null || true
|
|
find . -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
|
|
print_success "Python cache cleared"
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Platform Detection Functions
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# Get cross-platform Python executable path from venv
|
|
get_venv_python_path() {
|
|
local venv_path="$1"
|
|
|
|
# Check for both Unix and Windows Python executable paths
|
|
if [[ -f "$venv_path/bin/python" ]]; then
|
|
echo "$venv_path/bin/python"
|
|
elif [[ -f "$venv_path/Scripts/python.exe" ]]; then
|
|
echo "$venv_path/Scripts/python.exe"
|
|
else
|
|
return 1 # No Python executable found
|
|
fi
|
|
}
|
|
|
|
# Detect the operating system
|
|
detect_os() {
|
|
case "$OSTYPE" in
|
|
darwin*) echo "macos" ;;
|
|
linux*)
|
|
if grep -qi microsoft /proc/version 2>/dev/null; then
|
|
echo "wsl"
|
|
else
|
|
echo "linux"
|
|
fi
|
|
;;
|
|
msys*|cygwin*|win32) echo "windows" ;;
|
|
*) echo "unknown" ;;
|
|
esac
|
|
}
|
|
|
|
# Get Claude config path based on platform
|
|
get_claude_config_path() {
|
|
local os_type=$(detect_os)
|
|
|
|
case "$os_type" in
|
|
macos)
|
|
echo "$HOME/Library/Application Support/Claude/claude_desktop_config.json"
|
|
;;
|
|
linux)
|
|
echo "$HOME/.config/Claude/claude_desktop_config.json"
|
|
;;
|
|
wsl)
|
|
local win_appdata
|
|
if command -v wslvar &> /dev/null; then
|
|
win_appdata=$(wslvar APPDATA 2>/dev/null)
|
|
fi
|
|
|
|
if [[ "${win_appdata:-}" != "" ]]; then
|
|
echo "$(wslpath "$win_appdata")/Claude/claude_desktop_config.json"
|
|
else
|
|
print_warning "Could not determine Windows user path automatically. Please ensure APPDATA is set correctly or provide the full path manually."
|
|
echo "/mnt/c/Users/$USER/AppData/Roaming/Claude/claude_desktop_config.json"
|
|
fi
|
|
;;
|
|
windows)
|
|
echo "$APPDATA/Claude/claude_desktop_config.json"
|
|
;;
|
|
*)
|
|
echo ""
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Docker Cleanup Functions
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# Clean up old Docker artifacts
|
|
cleanup_docker() {
|
|
# Skip if already cleaned or Docker not available
|
|
[[ -f "$DOCKER_CLEANED_FLAG" ]] && return 0
|
|
|
|
if ! command -v docker &> /dev/null || ! docker info &> /dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
|
|
local found_artifacts=false
|
|
|
|
# Define containers to remove
|
|
local containers=(
|
|
"gemini-mcp-server"
|
|
"gemini-mcp-redis"
|
|
"zen-mcp-server"
|
|
"zen-mcp-redis"
|
|
"zen-mcp-log-monitor"
|
|
)
|
|
|
|
# Remove containers
|
|
for container in "${containers[@]}"; do
|
|
if docker ps -a --format "{{.Names}}" | grep -q "^${container}$" 2>/dev/null; then
|
|
if [[ "$found_artifacts" == false ]]; then
|
|
echo "One-time Docker cleanup..."
|
|
found_artifacts=true
|
|
fi
|
|
echo " Removing container: $container"
|
|
docker stop "$container" >/dev/null 2>&1 || true
|
|
docker rm "$container" >/dev/null 2>&1 || true
|
|
fi
|
|
done
|
|
|
|
# Remove images
|
|
local images=("gemini-mcp-server:latest" "zen-mcp-server:latest")
|
|
for image in "${images[@]}"; do
|
|
if docker images --format "{{.Repository}}:{{.Tag}}" | grep -q "^${image}$" 2>/dev/null; then
|
|
if [[ "$found_artifacts" == false ]]; then
|
|
echo "One-time Docker cleanup..."
|
|
found_artifacts=true
|
|
fi
|
|
echo " Removing image: $image"
|
|
docker rmi "$image" >/dev/null 2>&1 || true
|
|
fi
|
|
done
|
|
|
|
# Remove volumes
|
|
local volumes=("redis_data" "mcp_logs")
|
|
for volume in "${volumes[@]}"; do
|
|
if docker volume ls --format "{{.Name}}" | grep -q "^${volume}$" 2>/dev/null; then
|
|
if [[ "$found_artifacts" == false ]]; then
|
|
echo "One-time Docker cleanup..."
|
|
found_artifacts=true
|
|
fi
|
|
echo " Removing volume: $volume"
|
|
docker volume rm "$volume" >/dev/null 2>&1 || true
|
|
fi
|
|
done
|
|
|
|
if [[ "$found_artifacts" == true ]]; then
|
|
print_success "Docker cleanup complete"
|
|
fi
|
|
|
|
touch "$DOCKER_CLEANED_FLAG"
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Python Environment Functions
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# Find suitable Python command
|
|
find_python() {
|
|
# Pyenv should already be initialized at script start, but check if .python-version exists
|
|
if [[ -f ".python-version" ]] && command -v pyenv &> /dev/null; then
|
|
# Ensure pyenv respects the local .python-version
|
|
pyenv local &>/dev/null || true
|
|
fi
|
|
|
|
# Prefer Python 3.12 for best compatibility
|
|
local python_cmds=("python3.12" "python3.13" "python3.11" "python3.10" "python3" "python" "py")
|
|
|
|
for cmd in "${python_cmds[@]}"; do
|
|
if command -v "$cmd" &> /dev/null; then
|
|
local version=$($cmd --version 2>&1)
|
|
if [[ $version =~ Python\ 3\.([0-9]+)\.([0-9]+) ]]; then
|
|
local major_version=${BASH_REMATCH[1]}
|
|
local minor_version=${BASH_REMATCH[2]}
|
|
|
|
# Check minimum version (3.10) for better library compatibility
|
|
if [[ $major_version -ge 10 ]]; then
|
|
# Verify the command actually exists (important for pyenv)
|
|
if command -v "$cmd" &> /dev/null; then
|
|
echo "$cmd"
|
|
print_success "Found Python: $version"
|
|
|
|
# Recommend Python 3.12
|
|
if [[ $major_version -ne 12 ]]; then
|
|
print_info "Note: Python 3.12 is recommended for best compatibility."
|
|
fi
|
|
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# No suitable Python found - check if we can use pyenv
|
|
local os_type=$(detect_os)
|
|
|
|
# Check for pyenv on Unix-like systems (macOS/Linux)
|
|
if [[ "$os_type" == "macos" || "$os_type" == "linux" || "$os_type" == "wsl" ]]; then
|
|
if command -v pyenv &> /dev/null; then
|
|
# pyenv exists, check if Python 3.12 is installed
|
|
if ! pyenv versions 2>/dev/null | grep -E "3\.(1[2-9]|[2-9][0-9])" >/dev/null; then
|
|
echo ""
|
|
echo "Python 3.10+ is required. Pyenv can install Python 3.12 locally for this project."
|
|
read -p "Install Python 3.12 using pyenv? (Y/n): " -n 1 -r
|
|
echo ""
|
|
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
|
if install_python_with_pyenv; then
|
|
# Try finding Python again
|
|
if python_cmd=$(find_python); then
|
|
echo "$python_cmd"
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
else
|
|
# Python 3.12+ is installed in pyenv but may not be active
|
|
# Check if .python-version exists
|
|
if [[ ! -f ".python-version" ]] || ! grep -qE "3\.(1[2-9]|[2-9][0-9])" .python-version 2>/dev/null; then
|
|
echo ""
|
|
print_info "Python 3.12 is installed via pyenv but not set for this project."
|
|
read -p "Set Python 3.12.0 for this project? (Y/n): " -n 1 -r
|
|
echo ""
|
|
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
|
# Find the first suitable Python version
|
|
local py_version=$(pyenv versions --bare | grep -E "^3\.(1[2-9]|[2-9][0-9])" | head -1)
|
|
if [[ -n "$py_version" ]]; then
|
|
pyenv local "$py_version"
|
|
print_success "Set Python $py_version for this project"
|
|
# Re-initialize pyenv to pick up the change
|
|
eval "$(pyenv init --path)" 2>/dev/null || true
|
|
eval "$(pyenv init -)" 2>/dev/null || true
|
|
# Try finding Python again
|
|
if python_cmd=$(find_python); then
|
|
echo "$python_cmd"
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
else
|
|
# No pyenv installed - show instructions
|
|
echo "" >&2
|
|
print_error "Python 3.10+ not found. The 'mcp' package requires Python 3.10+."
|
|
echo "" >&2
|
|
|
|
if [[ "$os_type" == "macos" ]]; then
|
|
echo "To install Python locally for this project:" >&2
|
|
echo "" >&2
|
|
echo "1. Install pyenv (manages Python versions per project):" >&2
|
|
echo " brew install pyenv" >&2
|
|
echo "" >&2
|
|
echo "2. Add to ~/.zshrc:" >&2
|
|
echo ' export PYENV_ROOT="$HOME/.pyenv"' >&2
|
|
echo ' export PATH="$PYENV_ROOT/bin:$PATH"' >&2
|
|
echo ' eval "$(pyenv init -)"' >&2
|
|
echo "" >&2
|
|
echo "3. Restart terminal, then run:" >&2
|
|
echo " pyenv install 3.12.0" >&2
|
|
echo " cd $(pwd)" >&2
|
|
echo " pyenv local 3.12.0" >&2
|
|
echo " ./run-server.sh" >&2
|
|
else
|
|
# Linux/WSL
|
|
echo "To install Python locally for this project:" >&2
|
|
echo "" >&2
|
|
echo "1. Install pyenv:" >&2
|
|
echo " curl https://pyenv.run | bash" >&2
|
|
echo "" >&2
|
|
echo "2. Add to ~/.bashrc:" >&2
|
|
echo ' export PYENV_ROOT="$HOME/.pyenv"' >&2
|
|
echo ' export PATH="$PYENV_ROOT/bin:$PATH"' >&2
|
|
echo ' eval "$(pyenv init -)"' >&2
|
|
echo "" >&2
|
|
echo "3. Restart terminal, then run:" >&2
|
|
echo " pyenv install 3.12.0" >&2
|
|
echo " cd $(pwd)" >&2
|
|
echo " pyenv local 3.12.0" >&2
|
|
echo " ./run-server.sh" >&2
|
|
fi
|
|
fi
|
|
else
|
|
# Other systems (shouldn't happen with bash script)
|
|
print_error "Python 3.10+ not found. Please install Python 3.10 or newer."
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# Install Python with pyenv (when pyenv is already installed)
|
|
install_python_with_pyenv() {
|
|
# Ensure pyenv is initialized
|
|
export PYENV_ROOT="${PYENV_ROOT:-$HOME/.pyenv}"
|
|
export PATH="$PYENV_ROOT/bin:$PATH"
|
|
eval "$(pyenv init -)" 2>/dev/null || true
|
|
|
|
print_info "Installing Python 3.12 (this may take a few minutes)..."
|
|
if pyenv install -s 3.12.0; then
|
|
print_success "Python 3.12 installed"
|
|
|
|
# Set local Python version for this project
|
|
pyenv local 3.12.0
|
|
print_success "Python 3.12 set for this project"
|
|
|
|
# Show shell configuration instructions
|
|
echo ""
|
|
print_info "To make pyenv work in new terminals, add to your shell config:"
|
|
local shell_config="~/.zshrc"
|
|
if [[ "$SHELL" == *"bash"* ]]; then
|
|
shell_config="~/.bashrc"
|
|
fi
|
|
echo ' export PYENV_ROOT="$HOME/.pyenv"'
|
|
echo ' command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"'
|
|
echo ' eval "$(pyenv init -)"'
|
|
echo ""
|
|
|
|
# Re-initialize pyenv to use the newly installed Python
|
|
eval "$(pyenv init --path)" 2>/dev/null || true
|
|
eval "$(pyenv init -)" 2>/dev/null || true
|
|
|
|
return 0
|
|
else
|
|
print_error "Failed to install Python 3.12"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Detect Linux distribution
|
|
detect_linux_distro() {
|
|
if [[ -f /etc/os-release ]]; then
|
|
. /etc/os-release
|
|
echo "${ID:-unknown}"
|
|
elif [[ -f /etc/debian_version ]]; then
|
|
echo "debian"
|
|
elif [[ -f /etc/redhat-release ]]; then
|
|
echo "rhel"
|
|
elif [[ -f /etc/arch-release ]]; then
|
|
echo "arch"
|
|
else
|
|
echo "unknown"
|
|
fi
|
|
}
|
|
|
|
# Get package manager and install command for the distro
|
|
get_install_command() {
|
|
local distro="$1"
|
|
local python_version="${2:-}"
|
|
|
|
# Extract major.minor version if provided
|
|
local version_suffix=""
|
|
if [[ -n "$python_version" ]] && [[ "$python_version" =~ ([0-9]+\.[0-9]+) ]]; then
|
|
version_suffix="${BASH_REMATCH[1]}"
|
|
fi
|
|
|
|
case "$distro" in
|
|
ubuntu|debian|raspbian|pop|linuxmint|elementary)
|
|
if [[ -n "$version_suffix" ]]; then
|
|
# Try version-specific packages first, then fall back to generic
|
|
echo "sudo apt update && (sudo apt install -y python${version_suffix}-venv python${version_suffix}-dev || sudo apt install -y python3-venv python3-pip)"
|
|
else
|
|
echo "sudo apt update && sudo apt install -y python3-venv python3-pip"
|
|
fi
|
|
;;
|
|
fedora)
|
|
echo "sudo dnf install -y python3-venv python3-pip"
|
|
;;
|
|
rhel|centos|rocky|almalinux|oracle)
|
|
echo "sudo dnf install -y python3-venv python3-pip || sudo yum install -y python3-venv python3-pip"
|
|
;;
|
|
arch|manjaro|endeavouros)
|
|
echo "sudo pacman -Syu --noconfirm python-pip python-virtualenv"
|
|
;;
|
|
opensuse|suse)
|
|
echo "sudo zypper install -y python3-venv python3-pip"
|
|
;;
|
|
alpine)
|
|
echo "sudo apk add --no-cache python3-dev py3-pip py3-virtualenv"
|
|
;;
|
|
*)
|
|
echo ""
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Check if we can use sudo
|
|
can_use_sudo() {
|
|
# Check if sudo exists and user can use it
|
|
if command -v sudo &> /dev/null; then
|
|
# Test sudo with a harmless command
|
|
if sudo -n true 2>/dev/null; then
|
|
return 0
|
|
elif [[ -t 0 ]]; then
|
|
# Terminal is interactive, test if sudo works with password
|
|
if sudo true 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# Try to install system packages automatically
|
|
try_install_system_packages() {
|
|
local python_cmd="${1:-python3}"
|
|
local os_type=$(detect_os)
|
|
|
|
# Skip on macOS as it works fine
|
|
if [[ "$os_type" == "macos" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
# Only try on Linux systems
|
|
if [[ "$os_type" != "linux" && "$os_type" != "wsl" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
# Get Python version
|
|
local python_version=""
|
|
if command -v "$python_cmd" &> /dev/null; then
|
|
python_version=$($python_cmd --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "")
|
|
fi
|
|
|
|
local distro=$(detect_linux_distro)
|
|
local install_cmd=$(get_install_command "$distro" "$python_version")
|
|
|
|
if [[ -z "$install_cmd" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
print_info "Attempting to install required Python packages..."
|
|
|
|
# Check if we can use sudo
|
|
if can_use_sudo; then
|
|
print_info "Installing system packages (this may ask for your password)..."
|
|
if bash -c "$install_cmd" >/dev/null 2>&1; then # Replaced eval to prevent command injection
|
|
print_success "System packages installed successfully"
|
|
return 0
|
|
else
|
|
print_warning "Failed to install system packages automatically"
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# Bootstrap pip in virtual environment
|
|
bootstrap_pip() {
|
|
local venv_python="$1"
|
|
local python_cmd="$2"
|
|
|
|
print_info "Bootstrapping pip in virtual environment..."
|
|
|
|
# Try ensurepip first
|
|
if $venv_python -m ensurepip --default-pip >/dev/null 2>&1; then
|
|
print_success "Successfully bootstrapped pip using ensurepip"
|
|
return 0
|
|
fi
|
|
|
|
# Try to download get-pip.py
|
|
print_info "Downloading pip installer..."
|
|
local get_pip_url="https://bootstrap.pypa.io/get-pip.py"
|
|
local temp_pip=$(mktemp)
|
|
local download_success=false
|
|
|
|
# Try curl first
|
|
if command -v curl &> /dev/null; then
|
|
if curl -sSL "$get_pip_url" -o "$temp_pip" 2>/dev/null; then
|
|
download_success=true
|
|
fi
|
|
fi
|
|
|
|
# Try wget if curl failed
|
|
if [[ "$download_success" == false ]] && command -v wget &> /dev/null; then
|
|
if wget -qO "$temp_pip" "$get_pip_url" 2>/dev/null; then
|
|
download_success=true
|
|
fi
|
|
fi
|
|
|
|
# Try python urllib as last resort
|
|
if [[ "$download_success" == false ]]; then
|
|
print_info "Using Python to download pip installer..."
|
|
if $python_cmd -c "import urllib.request; urllib.request.urlretrieve('$get_pip_url', '$temp_pip')" 2>/dev/null; then
|
|
download_success=true
|
|
fi
|
|
fi
|
|
|
|
if [[ "$download_success" == true ]] && [[ -f "$temp_pip" ]] && [[ -s "$temp_pip" ]]; then
|
|
print_info "Installing pip..."
|
|
if $venv_python "$temp_pip" --no-warn-script-location >/dev/null 2>&1; then
|
|
rm -f "$temp_pip"
|
|
print_success "Successfully installed pip"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
rm -f "$temp_pip" 2>/dev/null
|
|
return 1
|
|
}
|
|
|
|
# Setup environment using uv-first approach
|
|
setup_environment() {
|
|
local venv_python=""
|
|
|
|
# Try uv-first approach
|
|
if command -v uv &> /dev/null; then
|
|
print_info "Setting up environment with uv..."
|
|
|
|
# Only remove existing venv if it wasn't created by uv (to ensure clean uv setup)
|
|
if [[ -d "$VENV_PATH" ]] && [[ ! -f "$VENV_PATH/uv_created" ]]; then
|
|
print_info "Removing existing environment for clean uv setup..."
|
|
rm -rf "$VENV_PATH"
|
|
fi
|
|
|
|
# Try Python 3.12 first (preferred)
|
|
local uv_output
|
|
if uv_output=$(uv venv --python 3.12 "$VENV_PATH" 2>&1); then
|
|
# Use helper function for cross-platform path detection
|
|
if venv_python=$(get_venv_python_path "$VENV_PATH"); then
|
|
touch "$VENV_PATH/uv_created" # Mark as uv-created
|
|
print_success "Created environment with uv using Python 3.12"
|
|
|
|
# Ensure pip is installed in uv environment
|
|
if ! $venv_python -m pip --version &>/dev/null 2>&1; then
|
|
print_info "Installing pip in uv environment..."
|
|
# uv doesn't install pip by default, use bootstrap method
|
|
if bootstrap_pip "$venv_python" "python3"; then
|
|
print_success "pip installed in uv environment"
|
|
else
|
|
print_warning "Failed to install pip in uv environment"
|
|
fi
|
|
fi
|
|
else
|
|
print_warning "uv succeeded but Python executable not found in venv"
|
|
fi
|
|
# Fall back to any available Python through uv
|
|
elif uv_output=$(uv venv "$VENV_PATH" 2>&1); then
|
|
# Use helper function for cross-platform path detection
|
|
if venv_python=$(get_venv_python_path "$VENV_PATH"); then
|
|
touch "$VENV_PATH/uv_created" # Mark as uv-created
|
|
local python_version=$($venv_python --version 2>&1)
|
|
print_success "Created environment with uv using $python_version"
|
|
|
|
# Ensure pip is installed in uv environment
|
|
if ! $venv_python -m pip --version &>/dev/null 2>&1; then
|
|
print_info "Installing pip in uv environment..."
|
|
# uv doesn't install pip by default, use bootstrap method
|
|
if bootstrap_pip "$venv_python" "python3"; then
|
|
print_success "pip installed in uv environment"
|
|
else
|
|
print_warning "Failed to install pip in uv environment"
|
|
fi
|
|
fi
|
|
else
|
|
print_warning "uv succeeded but Python executable not found in venv"
|
|
fi
|
|
else
|
|
print_warning "uv environment creation failed, falling back to system Python detection"
|
|
print_warning "uv output: $uv_output"
|
|
fi
|
|
else
|
|
print_info "uv not found, using system Python detection"
|
|
fi
|
|
|
|
# If uv failed or not available, fallback to system Python detection
|
|
if [[ -z "$venv_python" ]]; then
|
|
print_info "Setting up environment with system Python..."
|
|
local python_cmd
|
|
python_cmd=$(find_python) || return 1
|
|
|
|
# Use existing venv creation logic
|
|
venv_python=$(setup_venv "$python_cmd")
|
|
if [[ $? -ne 0 ]]; then
|
|
return 1
|
|
fi
|
|
else
|
|
# venv_python was already set by uv creation above, just convert to absolute path
|
|
if [[ -n "$venv_python" ]]; then
|
|
# Convert to absolute path for MCP registration
|
|
local abs_venv_python
|
|
if cd "$(dirname "$venv_python")" 2>/dev/null; then
|
|
abs_venv_python=$(pwd)/$(basename "$venv_python")
|
|
venv_python="$abs_venv_python"
|
|
else
|
|
print_error "Failed to resolve absolute path for venv_python"
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
echo "$venv_python"
|
|
return 0
|
|
}
|
|
|
|
# Setup virtual environment
|
|
setup_venv() {
|
|
local python_cmd="$1"
|
|
local venv_python=""
|
|
local venv_pip=""
|
|
|
|
# Create venv if it doesn't exist
|
|
if [[ ! -d "$VENV_PATH" ]]; then
|
|
print_info "Creating isolated environment..."
|
|
|
|
# Capture error output for better diagnostics
|
|
local venv_error
|
|
if venv_error=$($python_cmd -m venv "$VENV_PATH" 2>&1); then
|
|
print_success "Created isolated environment"
|
|
else
|
|
# Check for common Linux issues and try fallbacks
|
|
local os_type=$(detect_os)
|
|
if [[ "$os_type" == "linux" || "$os_type" == "wsl" ]]; then
|
|
if echo "$venv_error" | grep -E -q "No module named venv|venv.*not found|ensurepip is not|python3.*-venv"; then
|
|
# Try to install system packages automatically first
|
|
if try_install_system_packages "$python_cmd"; then
|
|
print_info "Retrying virtual environment creation..."
|
|
if venv_error=$($python_cmd -m venv "$VENV_PATH" 2>&1); then
|
|
print_success "Created isolated environment"
|
|
else
|
|
# Continue to fallback methods below
|
|
print_warning "Still unable to create venv, trying fallback methods..."
|
|
fi
|
|
fi
|
|
|
|
# If venv still doesn't exist, try fallback methods
|
|
if [[ ! -d "$VENV_PATH" ]]; then
|
|
# Try virtualenv as fallback
|
|
if command -v virtualenv &> /dev/null; then
|
|
print_info "Attempting to create environment with virtualenv..."
|
|
if virtualenv -p "$python_cmd" "$VENV_PATH" &>/dev/null 2>&1; then
|
|
print_success "Created environment using virtualenv fallback"
|
|
fi
|
|
fi
|
|
|
|
# Try python -m virtualenv if directory wasn't created
|
|
if [[ ! -d "$VENV_PATH" ]]; then
|
|
if $python_cmd -m virtualenv "$VENV_PATH" &>/dev/null 2>&1; then
|
|
print_success "Created environment using python -m virtualenv fallback"
|
|
fi
|
|
fi
|
|
|
|
# Last resort: try to install virtualenv via pip and use it
|
|
if [[ ! -d "$VENV_PATH" ]] && command -v pip3 &> /dev/null; then
|
|
print_info "Installing virtualenv via pip..."
|
|
if pip3 install --user virtualenv &>/dev/null 2>&1; then
|
|
local user_bin="$HOME/.local/bin"
|
|
if [[ -f "$user_bin/virtualenv" ]]; then
|
|
if "$user_bin/virtualenv" -p "$python_cmd" "$VENV_PATH" &>/dev/null 2>&1; then
|
|
print_success "Created environment using pip-installed virtualenv"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Check if any method succeeded
|
|
if [[ ! -d "$VENV_PATH" ]]; then
|
|
print_error "Unable to create virtual environment"
|
|
echo ""
|
|
echo "Your system is missing Python development packages."
|
|
echo ""
|
|
|
|
local distro=$(detect_linux_distro)
|
|
local python_version=$($python_cmd --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "")
|
|
local install_cmd=$(get_install_command "$distro" "$python_version")
|
|
|
|
if [[ -n "$install_cmd" ]]; then
|
|
echo "Please run this command to install them:"
|
|
echo " $install_cmd"
|
|
else
|
|
echo "Please install Python venv support for your system:"
|
|
echo " Ubuntu/Debian: sudo apt install python3-venv python3-pip"
|
|
echo " RHEL/CentOS: sudo dnf install python3-venv python3-pip"
|
|
echo " Arch: sudo pacman -S python-pip python-virtualenv"
|
|
fi
|
|
echo ""
|
|
echo "Then run this script again."
|
|
exit 1
|
|
fi
|
|
elif echo "$venv_error" | grep -q "Permission denied"; then
|
|
print_error "Permission denied creating virtual environment"
|
|
echo ""
|
|
echo "Try running in a different directory:"
|
|
echo " cd ~ && git clone <repository-url> && cd zen-mcp-server && ./run-server.sh"
|
|
echo ""
|
|
exit 1
|
|
else
|
|
print_error "Failed to create virtual environment"
|
|
echo "Error: $venv_error"
|
|
exit 1
|
|
fi
|
|
else
|
|
# For non-Linux systems, show the error and exit
|
|
print_error "Failed to create virtual environment"
|
|
echo "Error: $venv_error"
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Get venv Python path based on platform
|
|
local os_type=$(detect_os)
|
|
case "$os_type" in
|
|
windows)
|
|
venv_python="$VENV_PATH/Scripts/python.exe"
|
|
venv_pip="$VENV_PATH/Scripts/pip.exe"
|
|
;;
|
|
*)
|
|
venv_python="$VENV_PATH/bin/python"
|
|
venv_pip="$VENV_PATH/bin/pip"
|
|
;;
|
|
esac
|
|
|
|
# Check if venv Python exists
|
|
if [[ ! -f "$venv_python" ]]; then
|
|
print_error "Virtual environment Python not found"
|
|
exit 1
|
|
fi
|
|
|
|
# Always check if pip exists in the virtual environment (regardless of how it was created)
|
|
if [[ ! -f "$venv_pip" ]] && ! $venv_python -m pip --version &>/dev/null 2>&1; then
|
|
print_warning "pip not found in virtual environment, installing..."
|
|
|
|
# On Linux, try to install system packages if pip is missing
|
|
local os_type=$(detect_os)
|
|
if [[ "$os_type" == "linux" || "$os_type" == "wsl" ]]; then
|
|
if try_install_system_packages "$python_cmd"; then
|
|
# Check if pip is now available after system package install
|
|
if $venv_python -m pip --version &>/dev/null 2>&1; then
|
|
print_success "pip is now available"
|
|
else
|
|
# Still need to bootstrap pip
|
|
bootstrap_pip "$venv_python" "$python_cmd" || true
|
|
fi
|
|
else
|
|
# Try to bootstrap pip without system packages
|
|
bootstrap_pip "$venv_python" "$python_cmd" || true
|
|
fi
|
|
else
|
|
# For non-Linux systems, just try to bootstrap pip
|
|
bootstrap_pip "$venv_python" "$python_cmd" || true
|
|
fi
|
|
|
|
# Final check after all attempts
|
|
if ! $venv_python -m pip --version &>/dev/null 2>&1; then
|
|
print_error "Failed to install pip in virtual environment"
|
|
echo ""
|
|
echo "Your Python installation appears to be incomplete."
|
|
echo ""
|
|
|
|
local distro=$(detect_linux_distro)
|
|
local python_version=$($python_cmd --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "")
|
|
local install_cmd=$(get_install_command "$distro" "$python_version")
|
|
|
|
if [[ -n "$install_cmd" ]]; then
|
|
echo "Please run this command to install Python packages:"
|
|
echo " $install_cmd"
|
|
else
|
|
echo "Please install Python pip support for your system."
|
|
fi
|
|
echo ""
|
|
echo "Then delete the virtual environment and run this script again:"
|
|
echo " rm -rf $VENV_PATH"
|
|
echo " ./run-server.sh"
|
|
echo ""
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Verify pip is working
|
|
if ! $venv_python -m pip --version &>/dev/null 2>&1; then
|
|
print_error "pip is not working correctly in the virtual environment"
|
|
echo ""
|
|
echo "Try deleting the virtual environment and running again:"
|
|
echo " rm -rf $VENV_PATH"
|
|
echo " ./run-server.sh"
|
|
echo ""
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n "${VIRTUAL_ENV:-}" ]]; then
|
|
print_success "Using activated virtual environment with pip"
|
|
else
|
|
print_success "Virtual environment ready with pip"
|
|
fi
|
|
|
|
# Convert to absolute path for MCP registration
|
|
local abs_venv_python=$(cd "$(dirname "$venv_python")" && pwd)/$(basename "$venv_python")
|
|
echo "$abs_venv_python"
|
|
return 0
|
|
}
|
|
|
|
# Check if package is installed
|
|
check_package() {
|
|
local python_cmd="$1"
|
|
local package="$2"
|
|
$python_cmd -c "import $package" 2>/dev/null
|
|
}
|
|
|
|
# Install dependencies
|
|
install_dependencies() {
|
|
local python_cmd="$1"
|
|
local deps_needed=false
|
|
|
|
# First verify pip is available (always check, even for uv environments)
|
|
if ! $python_cmd -m pip --version &>/dev/null 2>&1; then
|
|
print_error "pip is not available in the Python environment"
|
|
echo ""
|
|
echo "This indicates an incomplete Python installation."
|
|
echo "Please see the instructions above for installing the required packages."
|
|
return 1
|
|
fi
|
|
|
|
# Check required packages
|
|
local packages=("mcp" "google.generativeai" "openai" "pydantic" "dotenv")
|
|
for package in "${packages[@]}"; do
|
|
local import_name=${package%%.*} # Get first part before dot
|
|
if ! check_package "$python_cmd" "$import_name"; then
|
|
deps_needed=true
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ "$deps_needed" == false ]]; then
|
|
print_success "Dependencies already installed"
|
|
return 0
|
|
fi
|
|
|
|
echo ""
|
|
print_info "Setting up Zen MCP Server..."
|
|
echo "Installing required components:"
|
|
echo " • MCP protocol library"
|
|
echo " • AI model connectors"
|
|
echo " • Data validation tools"
|
|
echo " • Environment configuration"
|
|
echo ""
|
|
|
|
# Determine installation method - prefer uv if available and we're in a uv-created environment
|
|
local install_cmd
|
|
local use_uv=false
|
|
|
|
if command -v uv &> /dev/null && [[ -f "$VENV_PATH/uv_created" ]]; then
|
|
# Use uv for faster installation if environment was created by uv
|
|
install_cmd="uv pip install -q -r requirements.txt --python $python_cmd"
|
|
use_uv=true
|
|
print_info "Using uv for faster package installation..."
|
|
elif [[ -n "${VIRTUAL_ENV:-}" ]] || [[ "$python_cmd" == *"$VENV_PATH"* ]]; then
|
|
install_cmd="$python_cmd -m pip install -q -r requirements.txt"
|
|
else
|
|
install_cmd="$python_cmd -m pip install -q --user -r requirements.txt"
|
|
fi
|
|
|
|
# Install packages with better error handling
|
|
echo -n "Downloading packages..."
|
|
local install_output
|
|
local install_error
|
|
|
|
# Capture both stdout and stderr
|
|
install_output=$($install_cmd 2>&1)
|
|
local exit_code=$?
|
|
|
|
if [[ $exit_code -ne 0 ]]; then
|
|
echo -e "\r${RED}✗ Setup failed${NC} "
|
|
echo ""
|
|
echo "Installation error:"
|
|
echo "$install_output" | head -20
|
|
echo ""
|
|
|
|
# Check for common issues
|
|
if echo "$install_output" | grep -q "No module named pip"; then
|
|
print_error "pip module not found"
|
|
echo ""
|
|
echo "Your Python installation is incomplete. Please install pip:"
|
|
|
|
local distro=$(detect_linux_distro)
|
|
local python_version=$($python_cmd --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "")
|
|
local install_cmd=$(get_install_command "$distro" "$python_version")
|
|
|
|
if [[ -n "$install_cmd" ]]; then
|
|
echo ""
|
|
echo "For your system ($distro), run:"
|
|
echo " $install_cmd"
|
|
else
|
|
echo ""
|
|
echo " Ubuntu/Debian: sudo apt install python3-pip"
|
|
echo " RHEL/CentOS: sudo dnf install python3-pip"
|
|
echo " Arch: sudo pacman -S python-pip"
|
|
fi
|
|
elif echo "$install_output" | grep -q "Permission denied"; then
|
|
print_error "Permission denied during installation"
|
|
echo ""
|
|
echo "Try using a virtual environment or install with --user flag:"
|
|
echo " $python_cmd -m pip install --user -r requirements.txt"
|
|
else
|
|
echo "Try running manually:"
|
|
if [[ "$use_uv" == true ]]; then
|
|
echo " uv pip install -r requirements.txt --python $python_cmd"
|
|
echo "Or fallback to pip:"
|
|
fi
|
|
echo " $python_cmd -m pip install -r requirements.txt"
|
|
echo ""
|
|
echo "Or install individual packages:"
|
|
echo " $python_cmd -m pip install mcp google-genai openai pydantic python-dotenv"
|
|
fi
|
|
return 1
|
|
else
|
|
echo -e "\r${GREEN}✓ Setup complete!${NC} "
|
|
|
|
# Verify critical imports work
|
|
if ! check_package "$python_cmd" "dotenv"; then
|
|
print_warning "python-dotenv not imported correctly, installing explicitly..."
|
|
if $python_cmd -m pip install python-dotenv &>/dev/null 2>&1; then
|
|
print_success "python-dotenv installed successfully"
|
|
else
|
|
print_error "Failed to install python-dotenv"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Environment Configuration Functions
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# Setup .env file
|
|
setup_env_file() {
|
|
if [[ -f .env ]]; then
|
|
print_success ".env file already exists"
|
|
migrate_env_file
|
|
return 0
|
|
fi
|
|
|
|
if [[ ! -f .env.example ]]; then
|
|
print_error ".env.example not found!"
|
|
return 1
|
|
fi
|
|
|
|
cp .env.example .env
|
|
print_success "Created .env from .env.example"
|
|
|
|
# Detect sed version for cross-platform compatibility
|
|
local sed_cmd
|
|
if sed --version >/dev/null 2>&1; then
|
|
sed_cmd="sed -i" # GNU sed (Linux)
|
|
else
|
|
sed_cmd="sed -i ''" # BSD sed (macOS)
|
|
fi
|
|
|
|
# Update API keys from environment if present
|
|
local api_keys=(
|
|
"GEMINI_API_KEY:your_gemini_api_key_here"
|
|
"OPENAI_API_KEY:your_openai_api_key_here"
|
|
"XAI_API_KEY:your_xai_api_key_here"
|
|
"DIAL_API_KEY:your_dial_api_key_here"
|
|
"OPENROUTER_API_KEY:your_openrouter_api_key_here"
|
|
)
|
|
|
|
for key_pair in "${api_keys[@]}"; do
|
|
local key_name="${key_pair%%:*}"
|
|
local placeholder="${key_pair##*:}"
|
|
local key_value="${!key_name:-}"
|
|
|
|
if [[ -n "$key_value" ]]; then
|
|
$sed_cmd "s/$placeholder/$key_value/" .env
|
|
print_success "Updated .env with $key_name from environment"
|
|
fi
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
# Migrate .env file from Docker to standalone format
|
|
migrate_env_file() {
|
|
# Check if migration is needed
|
|
if ! grep -q "host\.docker\.internal" .env 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
|
|
print_warning "Migrating .env from Docker to standalone format..."
|
|
|
|
# Create backup
|
|
cp .env .env.backup_$(date +%Y%m%d_%H%M%S)
|
|
|
|
# Detect sed version for cross-platform compatibility
|
|
local sed_cmd
|
|
if sed --version >/dev/null 2>&1; then
|
|
sed_cmd="sed -i" # GNU sed (Linux)
|
|
else
|
|
sed_cmd="sed -i ''" # BSD sed (macOS)
|
|
fi
|
|
|
|
# Replace host.docker.internal with localhost
|
|
$sed_cmd 's/host\.docker\.internal/localhost/g' .env
|
|
|
|
print_success "Migrated Docker URLs to localhost in .env"
|
|
echo " (Backup saved as .env.backup_*)"
|
|
}
|
|
|
|
# Validate API keys
|
|
validate_api_keys() {
|
|
local has_key=false
|
|
local api_keys=(
|
|
"GEMINI_API_KEY:your_gemini_api_key_here"
|
|
"OPENAI_API_KEY:your_openai_api_key_here"
|
|
"XAI_API_KEY:your_xai_api_key_here"
|
|
"DIAL_API_KEY:your_dial_api_key_here"
|
|
"OPENROUTER_API_KEY:your_openrouter_api_key_here"
|
|
)
|
|
|
|
for key_pair in "${api_keys[@]}"; do
|
|
local key_name="${key_pair%%:*}"
|
|
local placeholder="${key_pair##*:}"
|
|
local key_value="${!key_name:-}"
|
|
|
|
if [[ -n "$key_value" ]] && [[ "$key_value" != "$placeholder" ]]; then
|
|
print_success "$key_name configured"
|
|
has_key=true
|
|
fi
|
|
done
|
|
|
|
# Check custom API URL
|
|
if [[ -n "${CUSTOM_API_URL:-}" ]]; then
|
|
print_success "CUSTOM_API_URL configured: $CUSTOM_API_URL"
|
|
has_key=true
|
|
fi
|
|
|
|
if [[ "$has_key" == false ]]; then
|
|
print_error "No API keys found in .env!"
|
|
echo "" >&2
|
|
echo "Please edit .env and add at least one API key:" >&2
|
|
echo " GEMINI_API_KEY=your-actual-key" >&2
|
|
echo " OPENAI_API_KEY=your-actual-key" >&2
|
|
echo " XAI_API_KEY=your-actual-key" >&2
|
|
echo " DIAL_API_KEY=your-actual-key" >&2
|
|
echo " OPENROUTER_API_KEY=your-actual-key" >&2
|
|
echo "" >&2
|
|
print_info "After adding your API keys, run ./run-server.sh again" >&2
|
|
echo "" >&2
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Claude Integration Functions
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# Check if MCP is added to Claude CLI and verify it's correct
|
|
check_claude_cli_integration() {
|
|
local python_cmd="$1"
|
|
local server_path="$2"
|
|
|
|
if ! command -v claude &> /dev/null; then
|
|
echo ""
|
|
print_warning "Claude CLI not found"
|
|
echo ""
|
|
read -p "Would you like to add Zen to Claude Code? (Y/n): " -n 1 -r
|
|
echo ""
|
|
if [[ $REPLY =~ ^[Nn]$ ]]; then
|
|
print_info "Skipping Claude Code integration"
|
|
return 0
|
|
fi
|
|
|
|
echo ""
|
|
echo "Please install Claude Code first:"
|
|
echo " Visit: https://docs.anthropic.com/en/docs/claude-code/cli-usage"
|
|
echo ""
|
|
echo "Then run this script again to register MCP."
|
|
return 1
|
|
fi
|
|
|
|
# Check if zen is registered
|
|
local mcp_list=$(claude mcp list 2>/dev/null)
|
|
if echo "$mcp_list" | grep -q "zen"; then
|
|
# Check if it's using the old Docker command
|
|
if echo "$mcp_list" | grep -E "zen.*docker|zen.*compose" &>/dev/null; then
|
|
print_warning "Found old Docker-based Zen registration, updating..."
|
|
claude mcp remove zen -s user 2>/dev/null || true
|
|
|
|
# Re-add with correct Python command
|
|
if claude mcp add zen -s user -- "$python_cmd" "$server_path" 2>/dev/null; then
|
|
print_success "Updated Zen to become a standalone script"
|
|
return 0
|
|
else
|
|
echo ""
|
|
echo "Failed to update MCP registration. Please run manually:"
|
|
echo " claude mcp remove zen -s user"
|
|
echo " claude mcp add zen -s user -- $python_cmd $server_path"
|
|
return 1
|
|
fi
|
|
else
|
|
# Verify the registered path matches current setup
|
|
local expected_cmd="$python_cmd $server_path"
|
|
if echo "$mcp_list" | grep -F "$server_path" &>/dev/null; then
|
|
return 0
|
|
else
|
|
print_warning "Zen registered with different path, updating..."
|
|
claude mcp remove zen -s user 2>/dev/null || true
|
|
|
|
if claude mcp add zen -s user -- "$python_cmd" "$server_path" 2>/dev/null; then
|
|
print_success "Updated Zen with current path"
|
|
return 0
|
|
else
|
|
echo ""
|
|
echo "Failed to update MCP registration. Please run manually:"
|
|
echo " claude mcp remove zen -s user"
|
|
echo " claude mcp add zen -s user -- $python_cmd $server_path"
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
else
|
|
# Not registered at all, ask user if they want to add it
|
|
echo ""
|
|
read -p "Add Zen to Claude Code? (Y/n): " -n 1 -r
|
|
echo ""
|
|
if [[ $REPLY =~ ^[Nn]$ ]]; then
|
|
print_info "To add manually later, run:"
|
|
echo " claude mcp add zen -s user -- $python_cmd $server_path"
|
|
return 0
|
|
fi
|
|
|
|
print_info "Registering Zen with Claude Code..."
|
|
if claude mcp add zen -s user -- "$python_cmd" "$server_path" 2>/dev/null; then
|
|
print_success "Successfully added Zen to Claude Code"
|
|
return 0
|
|
else
|
|
echo ""
|
|
echo "Failed to add automatically. To add manually, run:"
|
|
echo " claude mcp add zen -s user -- $python_cmd $server_path"
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Check and update Claude Desktop configuration
|
|
check_claude_desktop_integration() {
|
|
local python_cmd="$1"
|
|
local server_path="$2"
|
|
|
|
# Skip if already configured (check flag)
|
|
if [[ -f "$DESKTOP_CONFIG_FLAG" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local config_path=$(get_claude_config_path)
|
|
if [[ -z "$config_path" ]]; then
|
|
print_warning "Unable to determine Claude Desktop config path for this platform"
|
|
return 0
|
|
fi
|
|
|
|
echo ""
|
|
read -p "Configure Zen for Claude Desktop? (Y/n): " -n 1 -r
|
|
echo ""
|
|
if [[ $REPLY =~ ^[Nn]$ ]]; then
|
|
print_info "Skipping Claude Desktop integration"
|
|
touch "$DESKTOP_CONFIG_FLAG" # Don't ask again
|
|
return 0
|
|
fi
|
|
|
|
# Create config directory if it doesn't exist
|
|
local config_dir=$(dirname "$config_path")
|
|
mkdir -p "$config_dir" 2>/dev/null || true
|
|
|
|
# Handle existing config
|
|
if [[ -f "$config_path" ]]; then
|
|
print_info "Updating existing Claude Desktop config..."
|
|
|
|
# Check for old Docker config and remove it
|
|
if grep -q "docker.*compose.*zen\|zen.*docker" "$config_path" 2>/dev/null; then
|
|
print_warning "Removing old Docker-based MCP configuration..."
|
|
# Create backup
|
|
cp "$config_path" "${config_path}.backup_$(date +%Y%m%d_%H%M%S)"
|
|
|
|
# Remove old zen config using a more robust approach
|
|
local temp_file=$(mktemp)
|
|
python3 -c "
|
|
import json
|
|
import sys
|
|
|
|
try:
|
|
with open('$config_path', 'r') as f:
|
|
config = json.load(f)
|
|
|
|
# Remove zen from mcpServers if it exists
|
|
if 'mcpServers' in config and 'zen' in config['mcpServers']:
|
|
del config['mcpServers']['zen']
|
|
print('Removed old zen MCP configuration')
|
|
|
|
with open('$temp_file', 'w') as f:
|
|
json.dump(config, f, indent=2)
|
|
|
|
except Exception as e:
|
|
print(f'Error processing config: {e}', file=sys.stderr)
|
|
sys.exit(1)
|
|
" && mv "$temp_file" "$config_path"
|
|
fi
|
|
|
|
# Add new config
|
|
local temp_file=$(mktemp)
|
|
python3 -c "
|
|
import json
|
|
import sys
|
|
|
|
try:
|
|
with open('$config_path', 'r') as f:
|
|
config = json.load(f)
|
|
except:
|
|
config = {}
|
|
|
|
# Ensure mcpServers exists
|
|
if 'mcpServers' not in config:
|
|
config['mcpServers'] = {}
|
|
|
|
# Add zen server
|
|
config['mcpServers']['zen'] = {
|
|
'command': '$python_cmd',
|
|
'args': ['$server_path']
|
|
}
|
|
|
|
with open('$temp_file', 'w') as f:
|
|
json.dump(config, f, indent=2)
|
|
" && mv "$temp_file" "$config_path"
|
|
|
|
else
|
|
print_info "Creating new Claude Desktop config..."
|
|
cat > "$config_path" << EOF
|
|
{
|
|
"mcpServers": {
|
|
"zen": {
|
|
"command": "$python_cmd",
|
|
"args": ["$server_path"]
|
|
}
|
|
}
|
|
}
|
|
EOF
|
|
fi
|
|
|
|
if [[ $? -eq 0 ]]; then
|
|
print_success "Successfully configured Claude Desktop"
|
|
echo " Config: $config_path"
|
|
echo " Restart Claude Desktop to use the new MCP server"
|
|
touch "$DESKTOP_CONFIG_FLAG"
|
|
else
|
|
print_error "Failed to update Claude Desktop config"
|
|
echo "Manual config location: $config_path"
|
|
echo "Add this configuration:"
|
|
cat << EOF
|
|
{
|
|
"mcpServers": {
|
|
"zen": {
|
|
"command": "$python_cmd",
|
|
"args": ["$server_path"]
|
|
}
|
|
}
|
|
}
|
|
EOF
|
|
fi
|
|
}
|
|
|
|
# Check and update Gemini CLI configuration
|
|
check_gemini_cli_integration() {
|
|
local script_dir="$1"
|
|
local zen_wrapper="$script_dir/zen-mcp-server"
|
|
|
|
# Check if Gemini settings file exists
|
|
local gemini_config="$HOME/.gemini/settings.json"
|
|
if [[ ! -f "$gemini_config" ]]; then
|
|
# Gemini CLI not installed or not configured
|
|
return 0
|
|
fi
|
|
|
|
# Check if zen is already configured
|
|
if grep -q '"zen"' "$gemini_config" 2>/dev/null; then
|
|
# Already configured
|
|
return 0
|
|
fi
|
|
|
|
# Ask user if they want to add Zen to Gemini CLI
|
|
echo ""
|
|
read -p "Configure Zen for Gemini CLI? (Y/n): " -n 1 -r
|
|
echo ""
|
|
if [[ $REPLY =~ ^[Nn]$ ]]; then
|
|
print_info "Skipping Gemini CLI integration"
|
|
return 0
|
|
fi
|
|
|
|
# Ensure wrapper script exists
|
|
if [[ ! -f "$zen_wrapper" ]]; then
|
|
print_info "Creating wrapper script for Gemini CLI..."
|
|
cat > "$zen_wrapper" << 'EOF'
|
|
#!/bin/bash
|
|
# Wrapper script for Gemini CLI compatibility
|
|
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
cd "$DIR"
|
|
exec .zen_venv/bin/python server.py "$@"
|
|
EOF
|
|
chmod +x "$zen_wrapper"
|
|
print_success "Created zen-mcp-server wrapper script"
|
|
fi
|
|
|
|
# Update Gemini settings
|
|
print_info "Updating Gemini CLI configuration..."
|
|
|
|
# Create backup
|
|
cp "$gemini_config" "${gemini_config}.backup_$(date +%Y%m%d_%H%M%S)"
|
|
|
|
# Add zen configuration using Python for proper JSON handling
|
|
local temp_file=$(mktemp)
|
|
python3 -c "
|
|
import json
|
|
import sys
|
|
|
|
try:
|
|
with open('$gemini_config', 'r') as f:
|
|
config = json.load(f)
|
|
|
|
# Ensure mcpServers exists
|
|
if 'mcpServers' not in config:
|
|
config['mcpServers'] = {}
|
|
|
|
# Add zen server
|
|
config['mcpServers']['zen'] = {
|
|
'command': '$zen_wrapper'
|
|
}
|
|
|
|
with open('$temp_file', 'w') as f:
|
|
json.dump(config, f, indent=2)
|
|
|
|
except Exception as e:
|
|
print(f'Error processing config: {e}', file=sys.stderr)
|
|
sys.exit(1)
|
|
" && mv "$temp_file" "$gemini_config"
|
|
|
|
if [[ $? -eq 0 ]]; then
|
|
print_success "Successfully configured Gemini CLI"
|
|
echo " Config: $gemini_config"
|
|
echo " Restart Gemini CLI to use Zen MCP Server"
|
|
else
|
|
print_error "Failed to update Gemini CLI config"
|
|
echo "Manual config location: $gemini_config"
|
|
echo "Add this configuration:"
|
|
cat << EOF
|
|
{
|
|
"mcpServers": {
|
|
"zen": {
|
|
"command": "$zen_wrapper"
|
|
}
|
|
}
|
|
}
|
|
EOF
|
|
fi
|
|
}
|
|
|
|
# Display configuration instructions
|
|
display_config_instructions() {
|
|
local python_cmd="$1"
|
|
local server_path="$2"
|
|
|
|
# Get script directory for Gemini CLI config
|
|
local script_dir=$(dirname "$server_path")
|
|
|
|
echo ""
|
|
local config_header="ZEN MCP SERVER CONFIGURATION"
|
|
echo "===== $config_header ====="
|
|
printf '%*s\n' "$((${#config_header} + 12))" | tr ' ' '='
|
|
echo ""
|
|
echo "To use Zen MCP Server with your Claude clients:"
|
|
echo ""
|
|
|
|
print_info "1. For Claude Code (CLI):"
|
|
echo -e " ${GREEN}claude mcp add zen -s user -- $python_cmd $server_path${NC}"
|
|
echo ""
|
|
|
|
print_info "2. For Claude Desktop:"
|
|
echo " Add this configuration to your Claude Desktop config file:"
|
|
echo ""
|
|
cat << EOF
|
|
{
|
|
"mcpServers": {
|
|
"zen": {
|
|
"command": "$python_cmd",
|
|
"args": ["$server_path"]
|
|
}
|
|
}
|
|
}
|
|
EOF
|
|
|
|
# Show platform-specific config location
|
|
local config_path=$(get_claude_config_path)
|
|
if [[ -n "$config_path" ]]; then
|
|
echo ""
|
|
print_info " Config file location:"
|
|
echo -e " ${YELLOW}$config_path${NC}"
|
|
fi
|
|
|
|
echo ""
|
|
print_info "3. Restart Claude Desktop after updating the config file"
|
|
echo ""
|
|
|
|
print_info "For Gemini CLI:"
|
|
echo " Add this configuration to ~/.gemini/settings.json:"
|
|
echo ""
|
|
cat << EOF
|
|
{
|
|
"mcpServers": {
|
|
"zen": {
|
|
"command": "$script_dir/zen-mcp-server"
|
|
}
|
|
}
|
|
}
|
|
EOF
|
|
echo ""
|
|
}
|
|
|
|
# Display setup instructions
|
|
display_setup_instructions() {
|
|
local python_cmd="$1"
|
|
local server_path="$2"
|
|
|
|
echo ""
|
|
local setup_header="SETUP COMPLETE"
|
|
echo "===== $setup_header ====="
|
|
printf '%*s\n' "$((${#setup_header} + 12))" | tr ' ' '='
|
|
echo ""
|
|
print_success "Zen is ready to use!"
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Log Management Functions
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# Show help message
|
|
show_help() {
|
|
local version=$(get_version)
|
|
local header="🤖 Zen MCP Server v$version"
|
|
echo "$header"
|
|
printf '%*s\n' "${#header}" | tr ' ' '='
|
|
echo ""
|
|
echo "Usage: $0 [OPTIONS]"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " -h, --help Show this help message"
|
|
echo " -v, --version Show version information"
|
|
echo " -f, --follow Follow server logs in real-time"
|
|
echo " -c, --config Show configuration instructions for Claude clients"
|
|
echo " --clear-cache Clear Python cache and exit (helpful for import issues)"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 Setup and start the MCP server"
|
|
echo " $0 -f Setup and follow logs"
|
|
echo " $0 -c Show configuration instructions"
|
|
echo " $0 --version Show version only"
|
|
echo " $0 --clear-cache Clear Python cache (fixes import issues)"
|
|
echo ""
|
|
echo "For more information, visit:"
|
|
echo " https://github.com/BeehiveInnovations/zen-mcp-server"
|
|
}
|
|
|
|
# Show version only
|
|
show_version() {
|
|
local version=$(get_version)
|
|
echo "$version"
|
|
}
|
|
|
|
# Follow logs
|
|
follow_logs() {
|
|
local log_path="$LOG_DIR/$LOG_FILE"
|
|
|
|
echo "Following server logs (Ctrl+C to stop)..."
|
|
echo ""
|
|
|
|
# Create logs directory and file if they don't exist
|
|
mkdir -p "$LOG_DIR"
|
|
touch "$log_path"
|
|
|
|
# Follow the log file
|
|
tail -f "$log_path"
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Main Function
|
|
# ----------------------------------------------------------------------------
|
|
|
|
main() {
|
|
# Parse command line arguments
|
|
local arg="${1:-}"
|
|
|
|
case "$arg" in
|
|
-h|--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
-v|--version)
|
|
show_version
|
|
exit 0
|
|
;;
|
|
-c|--config)
|
|
# Setup minimal environment to get paths for config display
|
|
echo "Setting up environment for configuration display..."
|
|
echo ""
|
|
local python_cmd
|
|
python_cmd=$(setup_environment) || exit 1
|
|
local script_dir=$(get_script_dir)
|
|
local server_path="$script_dir/server.py"
|
|
display_config_instructions "$python_cmd" "$server_path"
|
|
exit 0
|
|
;;
|
|
-f|--follow)
|
|
# Continue with normal setup then follow logs
|
|
;;
|
|
--clear-cache)
|
|
# Clear cache and exit
|
|
clear_python_cache
|
|
print_success "Cache cleared successfully"
|
|
echo ""
|
|
echo "You can now run './run-server.sh' normally"
|
|
exit 0
|
|
;;
|
|
"")
|
|
# Normal setup without following logs
|
|
;;
|
|
*)
|
|
print_error "Unknown option: $arg"
|
|
echo "" >&2
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Display header
|
|
local main_header="🤖 Zen MCP Server"
|
|
echo "$main_header"
|
|
printf '%*s\n' "${#main_header}" | tr ' ' '='
|
|
|
|
# Get and display version
|
|
local version=$(get_version)
|
|
echo "Version: $version"
|
|
echo ""
|
|
|
|
# Check if venv exists
|
|
if [[ ! -d "$VENV_PATH" ]]; then
|
|
echo "Setting up Python environment for first time..."
|
|
fi
|
|
|
|
# Step 1: Docker cleanup
|
|
cleanup_docker
|
|
|
|
# Step 1.5: Clear Python cache to prevent import issues
|
|
clear_python_cache
|
|
|
|
# Step 2: Setup environment file
|
|
setup_env_file || exit 1
|
|
|
|
# Step 3: Source .env file
|
|
if [[ -f .env ]]; then
|
|
set -a
|
|
source .env
|
|
set +a
|
|
fi
|
|
|
|
# Step 4: Validate API keys
|
|
validate_api_keys || exit 1
|
|
|
|
# Step 5: Setup Python environment (uv-first approach)
|
|
local python_cmd
|
|
python_cmd=$(setup_environment) || exit 1
|
|
|
|
# Step 6: Install dependencies
|
|
install_dependencies "$python_cmd" || exit 1
|
|
|
|
# Step 7: Get absolute server path
|
|
local script_dir=$(get_script_dir)
|
|
local server_path="$script_dir/server.py"
|
|
|
|
# Step 8: Display setup instructions
|
|
display_setup_instructions "$python_cmd" "$server_path"
|
|
|
|
# Step 9: Check Claude integrations
|
|
check_claude_cli_integration "$python_cmd" "$server_path"
|
|
check_claude_desktop_integration "$python_cmd" "$server_path"
|
|
|
|
# Step 10: Check Gemini CLI integration
|
|
check_gemini_cli_integration "$script_dir"
|
|
|
|
# Step 11: Display log information
|
|
echo ""
|
|
echo "Logs will be written to: $script_dir/$LOG_DIR/$LOG_FILE"
|
|
echo ""
|
|
|
|
# Step 11: Handle command line arguments
|
|
if [[ "$arg" == "-f" ]] || [[ "$arg" == "--follow" ]]; then
|
|
follow_logs
|
|
else
|
|
echo "To follow logs: ./run-server.sh -f"
|
|
echo "To show config: ./run-server.sh -c"
|
|
echo "To update: git pull, then run ./run-server.sh again"
|
|
echo ""
|
|
echo "Happy coding! 🎉"
|
|
fi
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Script Entry Point
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# Run main function with all arguments
|
|
main "$@"
|