Skip to main content
Glama

Zen MCP Server

run-server.sh76.1 kB
#!/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" # Convert to absolute path for consistent behavior across shell environments local abs_venv_path abs_venv_path=$(cd "$(dirname "$venv_path")" && pwd)/$(basename "$venv_path") # Check for both Unix and Windows Python executable paths if [[ -f "$abs_venv_path/bin/python" ]]; then echo "$abs_venv_path/bin/python" elif [[ -f "$abs_venv_path/Scripts/python.exe" ]]; then echo "$abs_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 [[ -n "${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 module_name="$2" "$python_cmd" -c "import importlib, sys; importlib.import_module(sys.argv[1])" "$module_name" &>/dev/null } # Install dependencies install_dependencies() { local python_cmd="$1" local deps_needed=false # First verify pip is available with retry logic and bootstrap fallback local pip_available=false local max_attempts=3 for ((attempt=1; attempt<=max_attempts; attempt++)); do if "$python_cmd" -m pip --version &>/dev/null; then pip_available=true break else if (( attempt < max_attempts )); then print_warning "Attempt $attempt/$max_attempts: pip not available, retrying in 1 second..." sleep 1 fi fi done # If pip is still not available after retries, try to bootstrap it if [[ "$pip_available" == false ]]; then print_warning "pip is not available in the Python environment after $max_attempts attempts" # Enhanced diagnostic information for debugging print_info "Diagnostic information:" print_info " Python executable: $python_cmd" print_info " Python executable exists: $(if [[ -f "$python_cmd" ]]; then echo "Yes"; else echo "No"; fi)" print_info " Python executable permissions: $(ls -la "$python_cmd" 2>/dev/null || echo "Cannot check")" print_info " Virtual environment path: $VENV_PATH" print_info " Virtual environment exists: $(if [[ -d "$VENV_PATH" ]]; then echo "Yes"; else echo "No"; fi)" print_info "Attempting to bootstrap pip..." # Extract the base python command for bootstrap (fallback to python3) local base_python_cmd="python3" if command -v python &> /dev/null; then base_python_cmd="python" fi # Try to bootstrap pip if bootstrap_pip "$python_cmd" "$base_python_cmd"; then print_success "Successfully bootstrapped pip" # Verify pip is now available if $python_cmd -m pip --version &>/dev/null 2>&1; then pip_available=true else print_error "pip still not available after bootstrap attempt" fi else print_error "Failed to bootstrap pip" fi fi # Final check - if pip is still not available, exit with error if [[ "$pip_available" == false ]]; then print_error "pip is not available in the Python environment" echo "" echo "This indicates an incomplete Python installation or a problem with the virtual environment." echo "" echo "Final diagnostic information:" echo " Python executable: $python_cmd" echo " Python version: $($python_cmd --version 2>&1 || echo "Cannot determine")" echo " pip module check: $($python_cmd -c "import pip; print('Available')" 2>&1 || echo "Not available")" echo "" echo "Troubleshooting steps:" echo "1. Delete the virtual environment: rm -rf $VENV_PATH" echo "2. Run this script again: ./run-server.sh" echo "3. If the problem persists, check your Python installation" echo "4. For Git Bash on Windows, try running from a regular Command Prompt or PowerShell" echo "" return 1 fi # Check required packages local packages=("mcp" "google.genai" "openai" "pydantic" "dotenv") for package in "${packages[@]}"; do if ! check_package "$python_cmd" "$package"; 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 and execute directly to handle paths with spaces local install_output local exit_code=0 echo -n "Downloading packages..." if command -v uv &> /dev/null && [[ -f "$VENV_PATH/uv_created" ]]; then print_info "Using uv for faster package installation..." install_output=$(uv pip install -q -r requirements.txt --python "$python_cmd" 2>&1) || exit_code=$? elif [[ -n "${VIRTUAL_ENV:-}" ]] || [[ "$python_cmd" == *"$VENV_PATH"* ]]; then install_output=$("$python_cmd" -m pip install -q -r requirements.txt 2>&1) || exit_code=$? else install_output=$("$python_cmd" -m pip install -q --user -r requirements.txt 2>&1) || exit_code=$? fi 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_*)" } # Check API keys and warn if missing (non-blocking) check_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_warning "No API keys found in .env!" echo "" echo "The Python development environment will be set up, but you won't be able to use the MCP server until you add API keys." echo "" echo "To add API keys, edit .env and add at least one:" echo " GEMINI_API_KEY=your-actual-key" echo " OPENAI_API_KEY=your-actual-key" echo " XAI_API_KEY=your-actual-key" echo " DIAL_API_KEY=your-actual-key" echo " OPENROUTER_API_KEY=your-actual-key" echo "" print_info "You can continue with development setup and add API keys later." echo "" fi return 0 # Always return success to continue setup } # ---------------------------------------------------------------------------- # Environment Variable Parsing Function # ---------------------------------------------------------------------------- # Parse .env file and extract all valid environment variables parse_env_variables() { local env_vars="" if [[ -f .env ]]; then # Read .env file and extract non-empty, non-comment variables while IFS= read -r line; do # Skip comments, empty lines, and lines starting with # if [[ -n "$line" && ! "$line" =~ ^[[:space:]]*# && "$line" =~ ^[[:space:]]*([^=]+)=(.*)$ ]]; then local key="${BASH_REMATCH[1]}" local value="${BASH_REMATCH[2]}" # Clean up key (remove leading/trailing whitespace) key=$(echo "$key" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') # Skip if value is empty or just whitespace if [[ -n "$value" && ! "$value" =~ ^[[:space:]]*$ ]]; then # Clean up value (remove leading/trailing whitespace and quotes) value=$(echo "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sed 's/^"//;s/"$//') # Remove inline comments (everything after # that's not in quotes) value=$(echo "$value" | sed 's/[[:space:]]*#.*$//') # Skip if value is a placeholder or empty after comment removal if [[ ! "$value" =~ ^your_.*_here$ && "$value" != "your_" && -n "$value" && ! "$value" =~ ^[[:space:]]*$ ]]; then env_vars+="$key=$value"$'\n' fi fi fi done < .env fi # If no .env file or no valid vars, fall back to environment variables if [[ -z "$env_vars" ]]; then local api_keys=( "GEMINI_API_KEY" "OPENAI_API_KEY" "XAI_API_KEY" "DIAL_API_KEY" "OPENROUTER_API_KEY" "CUSTOM_API_URL" "CUSTOM_API_KEY" "CUSTOM_MODEL_NAME" "DISABLED_TOOLS" "DEFAULT_MODEL" "LOG_LEVEL" "DEFAULT_THINKING_MODE_THINKDEEP" "CONVERSATION_TIMEOUT_HOURS" "MAX_CONVERSATION_TURNS" ) for key_name in "${api_keys[@]}"; do local key_value="${!key_name:-}" if [[ -n "$key_value" && ! "$key_value" =~ ^your_.*_here$ ]]; then env_vars+="$key_name=$key_value"$'\n' fi done fi echo "$env_vars" } # ---------------------------------------------------------------------------- # 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 and environment variables local env_vars=$(parse_env_variables) local env_args="" # Convert environment variables to -e arguments if [[ -n "$env_vars" ]]; then while IFS= read -r line; do if [[ -n "$line" && "$line" =~ ^([^=]+)=(.*)$ ]]; then env_args+=" -e ${BASH_REMATCH[1]}=\"${BASH_REMATCH[2]}\"" fi done <<< "$env_vars" fi local claude_cmd="claude mcp add zen -s user$env_args -- \"$python_cmd\" \"$server_path\"" if eval "$claude_cmd" 2>/dev/null; then print_success "Updated Zen to become a standalone script with environment variables" return 0 else echo "" echo "Failed to update MCP registration. Please run manually:" echo " claude mcp remove zen -s user" echo " $claude_cmd" 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 # Re-add with current path and environment variables local env_vars=$(parse_env_variables) local env_args="" # Convert environment variables to -e arguments if [[ -n "$env_vars" ]]; then while IFS= read -r line; do if [[ -n "$line" && "$line" =~ ^([^=]+)=(.*)$ ]]; then env_args+=" -e ${BASH_REMATCH[1]}=\"${BASH_REMATCH[2]}\"" fi done <<< "$env_vars" fi local claude_cmd="claude mcp add zen -s user$env_args -- \"$python_cmd\" \"$server_path\"" if eval "$claude_cmd" 2>/dev/null; then print_success "Updated Zen with current path and environment variables" return 0 else echo "" echo "Failed to update MCP registration. Please run manually:" echo " claude mcp remove zen -s user" echo " $claude_cmd" 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 local env_vars=$(parse_env_variables) local env_args="" # Convert environment variables to -e arguments for manual command if [[ -n "$env_vars" ]]; then while IFS= read -r line; do if [[ -n "$line" && "$line" =~ ^([^=]+)=(.*)$ ]]; then env_args+=" -e ${BASH_REMATCH[1]}=\"${BASH_REMATCH[2]}\"" fi done <<< "$env_vars" fi print_info "To add manually later, run:" echo " claude mcp add zen -s user$env_args -- $python_cmd $server_path" return 0 fi print_info "Registering Zen with Claude Code..." # Add with environment variables local env_vars=$(parse_env_variables) local env_args="" # Convert environment variables to -e arguments if [[ -n "$env_vars" ]]; then while IFS= read -r line; do if [[ -n "$line" && "$line" =~ ^([^=]+)=(.*)$ ]]; then env_args+=" -e ${BASH_REMATCH[1]}=\"${BASH_REMATCH[2]}\"" fi done <<< "$env_vars" fi local claude_cmd="claude mcp add zen -s user$env_args -- \"$python_cmd\" \"$server_path\"" if eval "$claude_cmd" 2>/dev/null; then print_success "Successfully added Zen to Claude Code with environment variables" return 0 else echo "" echo "Failed to add automatically. To add manually, run:" echo " $claude_cmd" 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 with environment variables local env_vars=$(parse_env_variables) local temp_file=$(mktemp) local env_file=$(mktemp) # Write environment variables to a temporary file for Python to read if [[ -n "$env_vars" ]]; then echo "$env_vars" > "$env_file" fi 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 zen_config = { 'command': '$python_cmd', 'args': ['$server_path'] } # Add environment variables if they exist env_dict = {} try: with open('$env_file', 'r') as f: for line in f: line = line.strip() if '=' in line and line: key, value = line.split('=', 1) env_dict[key] = value except: pass if env_dict: zen_config['env'] = env_dict config['mcpServers']['zen'] = zen_config with open('$temp_file', 'w') as f: json.dump(config, f, indent=2) " && mv "$temp_file" "$config_path" # Clean up temporary env file rm -f "$env_file" 2>/dev/null || true else print_info "Creating new Claude Desktop config..." # Create new config with environment variables local env_vars=$(parse_env_variables) local temp_file=$(mktemp) local env_file=$(mktemp) # Write environment variables to a temporary file for Python to read if [[ -n "$env_vars" ]]; then echo "$env_vars" > "$env_file" fi python3 -c " import json import sys config = {'mcpServers': {}} # Add zen server zen_config = { 'command': '$python_cmd', 'args': ['$server_path'] } # Add environment variables if they exist env_dict = {} try: with open('$env_file', 'r') as f: for line in f: line = line.strip() if '=' in line and line: key, value = line.split('=', 1) env_dict[key] = value except: pass if env_dict: zen_config['env'] = env_dict config['mcpServers']['zen'] = zen_config with open('$temp_file', 'w') as f: json.dump(config, f, indent=2) " && mv "$temp_file" "$config_path" # Clean up temporary env file rm -f "$env_file" 2>/dev/null || true 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:" # Generate example with actual environment variables for error case example_env="" env_vars=$(parse_env_variables) if [[ -n "$env_vars" ]]; then local first_entry=true while IFS= read -r line; do if [[ -n "$line" && "$line" =~ ^([^=]+)=(.*)$ ]]; then local key="${BASH_REMATCH[1]}" local value="your_$(echo "${key}" | tr '[:upper:]' '[:lower:]')" if [[ "$first_entry" == true ]]; then first_entry=false example_env=" \"$key\": \"$value\"" else example_env+=",\n \"$key\": \"$value\"" fi fi done <<< "$env_vars" fi cat << EOF { "mcpServers": { "zen": { "command": "$python_cmd", "args": ["$server_path"]$(if [[ -n "$example_env" ]]; then echo ","; fi)$(if [[ -n "$example_env" ]]; then echo " \"env\": { $(echo -e "$example_env") }"; fi) } } } 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 } # Check and update Codex CLI configuration check_codex_cli_integration() { # Check if Codex is installed if ! command -v codex &> /dev/null; then # Codex CLI not installed return 0 fi local codex_config="$HOME/.codex/config.toml" # Check if zen is already configured if [[ -f "$codex_config" ]] && grep -q '\[mcp_servers\.zen\]' "$codex_config" 2>/dev/null; then # Already configured return 0 fi # Ask user if they want to add Zen to Codex CLI echo "" read -p "Configure Zen for Codex CLI? (Y/n): " -n 1 -r echo "" if [[ $REPLY =~ ^[Nn]$ ]]; then print_info "Skipping Codex CLI integration" return 0 fi print_info "Updating Codex CLI configuration..." # Create config directory if it doesn't exist mkdir -p "$(dirname "$codex_config")" 2>/dev/null || true # Create backup if config exists if [[ -f "$codex_config" ]]; then cp "$codex_config" "${codex_config}.backup_$(date +%Y%m%d_%H%M%S)" fi # Get environment variables using shared function local env_vars=$(parse_env_variables) # Write zen configuration to config.toml { echo "" echo "[mcp_servers.zen]" echo "command = \"bash\"" echo "args = [\"-c\", \"for p in \$(which uvx 2>/dev/null) \$HOME/.local/bin/uvx /opt/homebrew/bin/uvx /usr/local/bin/uvx uvx; do [ -x \\\"\$p\\\" ] && exec \\\"\$p\\\" --from git+https://github.com/BeehiveInnovations/zen-mcp-server.git zen-mcp-server; done; echo 'uvx not found' >&2; exit 1\"]" echo "" echo "[mcp_servers.zen.env]" echo "PATH = \"/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:\$HOME/.local/bin:\$HOME/.cargo/bin:\$HOME/bin\"" if [[ -n "$env_vars" ]]; then # Convert KEY=VALUE format to TOML KEY = "VALUE" format while IFS= read -r line; do if [[ -n "$line" && "$line" =~ ^([^=]+)=(.*)$ ]]; then local key="${BASH_REMATCH[1]}" local value="${BASH_REMATCH[2]}" # Escape backslashes first, then double quotes for TOML compatibility local escaped_value escaped_value=$(echo "$value" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g') echo "$key = \"$escaped_value\"" fi done <<< "$env_vars" fi } >> "$codex_config" if [[ $? -eq 0 ]]; then print_success "Successfully configured Codex CLI" echo " Config: $codex_config" echo " Restart Codex CLI to use Zen MCP Server" else print_error "Failed to update Codex CLI config" echo "Manual config location: $codex_config" echo "Add this configuration:" # Generate example with actual environment variables for error case env_vars=$(parse_env_variables) cat << EOF [mcp_servers.zen] command = "sh" args = ["-c", "exec \$(which uvx 2>/dev/null || echo uvx) --from git+https://github.com/BeehiveInnovations/zen-mcp-server.git zen-mcp-server"] [mcp_servers.zen.env] PATH = "/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:\$HOME/.local/bin:\$HOME/.cargo/bin:\$HOME/bin" EOF # Add environment variable examples only if they exist if [[ -n "$env_vars" ]]; then while IFS= read -r line; do if [[ -n "$line" && "$line" =~ ^([^=]+)=(.*)$ ]]; then local key="${BASH_REMATCH[1]}" echo "${key} = \"your_$(echo "${key}" | tr '[:upper:]' '[:lower:]')\"" fi done <<< "$env_vars" else # Show GEMINI_API_KEY example if no environment variables exist echo "GEMINI_API_KEY = \"your_gemini_api_key_here\"" fi 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):" # Show command with environment variables local env_vars=$(parse_env_variables) local env_args="" if [[ -n "$env_vars" ]]; then while IFS= read -r line; do if [[ -n "$line" && "$line" =~ ^([^=]+)=(.*)$ ]]; then env_args+=" -e ${BASH_REMATCH[1]}=\"${BASH_REMATCH[2]}\"" fi done <<< "$env_vars" fi echo -e " ${GREEN}claude mcp add zen -s user$env_args -- $python_cmd $server_path${NC}" echo "" print_info "2. For Claude Desktop:" echo " Add this configuration to your Claude Desktop config file:" echo "" # Generate example with actual environment variables that exist example_env="" env_vars=$(parse_env_variables) if [[ -n "$env_vars" ]]; then local first_entry=true while IFS= read -r line; do if [[ -n "$line" && "$line" =~ ^([^=]+)=(.*)$ ]]; then local key="${BASH_REMATCH[1]}" local value="your_$(echo "${key}" | tr '[:upper:]' '[:lower:]')" if [[ "$first_entry" == true ]]; then first_entry=false example_env=" \"$key\": \"$value\"" else example_env+=",\n \"$key\": \"$value\"" fi fi done <<< "$env_vars" fi cat << EOF { "mcpServers": { "zen": { "command": "$python_cmd", "args": ["$server_path"]$(if [[ -n "$example_env" ]]; then echo ","; fi)$(if [[ -n "$example_env" ]]; then echo " \"env\": { $(echo -e "$example_env") }"; fi) } } } 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 "" print_info "For Codex CLI:" echo " Add this configuration to ~/.codex/config.toml:" echo "" cat << EOF [mcp_servers.zen] command = "bash" args = ["-c", "for p in \$(which uvx 2>/dev/null) \$HOME/.local/bin/uvx /opt/homebrew/bin/uvx /usr/local/bin/uvx uvx; do [ -x \\\"\$p\\\" ] && exec \\\"\$p\\\" --from git+https://github.com/BeehiveInnovations/zen-mcp-server.git zen-mcp-server; done; echo 'uvx not found' >&2; exit 1"] [mcp_servers.zen.env] PATH = "/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:\$HOME/.local/bin:\$HOME/.cargo/bin:\$HOME/bin" GEMINI_API_KEY = "your_gemini_api_key_here" 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!" # Display enabled/disabled tools if DISABLED_TOOLS is configured if [[ -n "${DISABLED_TOOLS:-}" ]]; then echo "" print_info "Tool Configuration:" # Dynamically discover all available tools from the tools directory # Excludes: __pycache__, shared modules, models.py, listmodels.py, version.py local all_tools=() for tool_file in tools/*.py; do if [[ -f "$tool_file" ]]; then local tool_name=$(basename "$tool_file" .py) # Skip non-tool files if [[ "$tool_name" != "models" && "$tool_name" != "listmodels" && "$tool_name" != "version" && "$tool_name" != "__init__" ]]; then all_tools+=("$tool_name") fi fi done # Convert DISABLED_TOOLS to array IFS=',' read -ra disabled_array <<< "$DISABLED_TOOLS" # Trim whitespace from disabled tools local disabled_tools=() for tool in "${disabled_array[@]}"; do disabled_tools+=("$(echo "$tool" | xargs)") done # Determine enabled tools local enabled_tools=() for tool in "${all_tools[@]}"; do local is_disabled=false for disabled in "${disabled_tools[@]}"; do if [[ "$tool" == "$disabled" ]]; then is_disabled=true break fi done if [[ "$is_disabled" == false ]]; then enabled_tools+=("$tool") fi done # Display enabled tools echo "" echo -e " ${GREEN}Enabled Tools (${#enabled_tools[@]}):${NC}" local enabled_list="" for tool in "${enabled_tools[@]}"; do if [[ -n "$enabled_list" ]]; then enabled_list+=", " fi enabled_list+="$tool" done echo " $enabled_list" # Display disabled tools echo "" echo -e " ${YELLOW}Disabled Tools (${#disabled_tools[@]}):${NC}" local disabled_list="" for tool in "${disabled_tools[@]}"; do if [[ -n "$disabled_list" ]]; then disabled_list+=", " fi disabled_list+="$tool" done echo " $disabled_list" echo "" echo " To enable more tools, edit the DISABLED_TOOLS variable in .env" fi } # ---------------------------------------------------------------------------- # 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: Check API keys (non-blocking - just warn if missing) check_api_keys # 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: Check Codex CLI integration check_codex_cli_integration # Step 12: Display log information echo "" echo "Logs will be written to: $script_dir/$LOG_DIR/$LOG_FILE" echo "" # Step 13: 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 "$@"

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/BeehiveInnovations/zen-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server