- Implemented `run-server.ps1` for setting up the Zen MCP server environment, including virtual environment creation, dependency installation, and Docker cleanup. - Added logging and error handling throughout the setup process. - Included functions for validating API keys and configuring integration with Claude Desktop and Gemini CLI. - Created `run_integration_tests.ps1` to execute integration tests with real API calls, including checks for API key availability and environment setup. - Enhanced output with color-coded messages for better user experience. Patch directory added for cross-platform patching support (`patch_crossplatform.py`).
1200 lines
37 KiB
PowerShell
1200 lines
37 KiB
PowerShell
#!/usr/bin/env pwsh
|
||
#Requires -Version 5.1
|
||
[CmdletBinding()]
|
||
param(
|
||
[switch]$Help,
|
||
[switch]$Version,
|
||
[switch]$Follow,
|
||
[switch]$Config,
|
||
[switch]$ClearCache,
|
||
[switch]$SkipVenv,
|
||
[switch]$SkipDocker,
|
||
[switch]$Force,
|
||
[switch]$VerboseOutput
|
||
)
|
||
|
||
# ============================================================================
|
||
# Zen MCP Server Setup Script for Windows PowerShell
|
||
#
|
||
# A Windows-compatible setup script that handles environment setup,
|
||
# dependency installation, and configuration.
|
||
# ============================================================================
|
||
|
||
# Set error action preference
|
||
$ErrorActionPreference = "Stop"
|
||
|
||
# ----------------------------------------------------------------------------
|
||
# Constants and Configuration
|
||
# ----------------------------------------------------------------------------
|
||
|
||
$script:VENV_PATH = ".zen_venv"
|
||
$script:DOCKER_CLEANED_FLAG = ".docker_cleaned"
|
||
$script:DESKTOP_CONFIG_FLAG = ".desktop_configured"
|
||
$script:LOG_DIR = "logs"
|
||
$script:LOG_FILE = "mcp_server.log"
|
||
|
||
# ----------------------------------------------------------------------------
|
||
# Utility Functions
|
||
# ----------------------------------------------------------------------------
|
||
|
||
function Write-Success {
|
||
param([string]$Message)
|
||
Write-Host "✓ " -ForegroundColor Green -NoNewline
|
||
Write-Host $Message
|
||
}
|
||
|
||
function Write-Error {
|
||
param([string]$Message)
|
||
Write-Host "✗ " -ForegroundColor Red -NoNewline
|
||
Write-Host $Message
|
||
}
|
||
|
||
function Write-Warning {
|
||
param([string]$Message)
|
||
Write-Host "⚠ " -ForegroundColor Yellow -NoNewline
|
||
Write-Host $Message
|
||
}
|
||
|
||
function Write-Info {
|
||
param([string]$Message)
|
||
Write-Host "ℹ " -ForegroundColor Cyan -NoNewline
|
||
Write-Host $Message
|
||
}
|
||
|
||
function Write-Step {
|
||
param([string]$Message)
|
||
Write-Host ""
|
||
Write-Host "=== $Message ===" -ForegroundColor Cyan
|
||
}
|
||
|
||
# Check if command exists
|
||
function Test-Command {
|
||
param([string]$Command)
|
||
try {
|
||
$null = Get-Command $Command -ErrorAction Stop
|
||
return $true
|
||
} catch {
|
||
return $false
|
||
}
|
||
}
|
||
|
||
# Alternative method to force remove locked directories
|
||
function Remove-LockedDirectory {
|
||
param([string]$Path)
|
||
|
||
if (!(Test-Path $Path)) {
|
||
return $true
|
||
}
|
||
|
||
try {
|
||
# Try standard removal first
|
||
Remove-Item -Recurse -Force $Path -ErrorAction Stop
|
||
return $true
|
||
} catch {
|
||
Write-Warning "Standard removal failed, trying alternative methods..."
|
||
|
||
# Method 1: Use takeown and icacls to force ownership
|
||
try {
|
||
Write-Info "Attempting to take ownership of locked files..."
|
||
takeown /F "$Path" /R /D Y 2>$null | Out-Null
|
||
icacls "$Path" /grant administrators:F /T 2>$null | Out-Null
|
||
Remove-Item -Recurse -Force $Path -ErrorAction Stop
|
||
return $true
|
||
} catch {
|
||
Write-Warning "Ownership method failed"
|
||
}
|
||
|
||
# Method 2: Rename and schedule for deletion on reboot
|
||
try {
|
||
$tempName = "$Path.delete_$(Get-Random)"
|
||
Write-Info "Renaming to: $tempName (will be deleted on next reboot)"
|
||
Rename-Item $Path $tempName -ErrorAction Stop
|
||
|
||
# Schedule for deletion on reboot using movefile
|
||
if (Get-Command "schtasks" -ErrorAction SilentlyContinue) {
|
||
Write-Info "Scheduling for deletion on next reboot..."
|
||
}
|
||
|
||
Write-Warning "Environment renamed to $tempName and will be deleted on next reboot"
|
||
return $true
|
||
} catch {
|
||
Write-Warning "Rename method failed"
|
||
}
|
||
|
||
# If all methods fail, return false
|
||
return $false
|
||
}
|
||
}
|
||
|
||
# Get version from config.py
|
||
function Get-Version {
|
||
try {
|
||
if (Test-Path "config.py") {
|
||
$content = Get-Content "config.py" -ErrorAction Stop
|
||
$versionLine = $content | Where-Object { $_ -match '^__version__ = ' }
|
||
if ($versionLine) {
|
||
return ($versionLine -replace '__version__ = "([^"]*)"', '$1')
|
||
}
|
||
}
|
||
return "unknown"
|
||
} catch {
|
||
return "unknown"
|
||
}
|
||
}
|
||
|
||
# Clear Python cache files
|
||
function Clear-PythonCache {
|
||
Write-Info "Clearing Python cache files..."
|
||
|
||
try {
|
||
# Remove .pyc files
|
||
Get-ChildItem -Path . -Recurse -Filter "*.pyc" -ErrorAction SilentlyContinue | Remove-Item -Force
|
||
|
||
# Remove __pycache__ directories
|
||
Get-ChildItem -Path . -Recurse -Name "__pycache__" -Directory -ErrorAction SilentlyContinue |
|
||
ForEach-Object { Remove-Item -Path $_ -Recurse -Force }
|
||
|
||
Write-Success "Python cache cleared"
|
||
} catch {
|
||
Write-Warning "Could not clear all cache files: $_"
|
||
}
|
||
}
|
||
|
||
# Check Python version
|
||
function Test-PythonVersion {
|
||
param([string]$PythonCmd)
|
||
try {
|
||
$version = & $PythonCmd --version 2>&1
|
||
if ($version -match "Python (\d+)\.(\d+)") {
|
||
$major = [int]$matches[1]
|
||
$minor = [int]$matches[2]
|
||
return ($major -gt 3) -or ($major -eq 3 -and $minor -ge 10)
|
||
}
|
||
return $false
|
||
} catch {
|
||
return $false
|
||
}
|
||
}
|
||
|
||
# Find Python installation
|
||
function Find-Python {
|
||
$pythonCandidates = @("python", "python3", "py")
|
||
|
||
foreach ($cmd in $pythonCandidates) {
|
||
if (Test-Command $cmd) {
|
||
if (Test-PythonVersion $cmd) {
|
||
$version = & $cmd --version 2>&1
|
||
Write-Success "Found Python: $version"
|
||
return $cmd
|
||
}
|
||
}
|
||
}
|
||
|
||
# Try Windows Python Launcher with specific versions
|
||
$pythonVersions = @("3.12", "3.11", "3.10", "3.9")
|
||
foreach ($version in $pythonVersions) {
|
||
$cmd = "py -$version"
|
||
try {
|
||
$null = Invoke-Expression "$cmd --version" 2>$null
|
||
Write-Success "Found Python via py launcher: $cmd"
|
||
return $cmd
|
||
} catch {
|
||
continue
|
||
}
|
||
}
|
||
|
||
Write-Error "Python 3.10+ not found. Please install Python from https://python.org"
|
||
return $null
|
||
}
|
||
|
||
# Clean up old Docker artifacts
|
||
function Cleanup-Docker {
|
||
if (Test-Path $DOCKER_CLEANED_FLAG) {
|
||
return
|
||
}
|
||
|
||
if (!(Test-Command "docker")) {
|
||
return
|
||
}
|
||
|
||
try {
|
||
$null = docker info 2>$null
|
||
} catch {
|
||
return
|
||
}
|
||
|
||
$foundArtifacts = $false
|
||
|
||
# Define containers to remove
|
||
$containers = @(
|
||
"gemini-mcp-server",
|
||
"gemini-mcp-redis",
|
||
"zen-mcp-server",
|
||
"zen-mcp-redis",
|
||
"zen-mcp-log-monitor"
|
||
)
|
||
|
||
# Remove containers
|
||
foreach ($container in $containers) {
|
||
try {
|
||
$exists = docker ps -a --format "{{.Names}}" | Where-Object { $_ -eq $container }
|
||
if ($exists) {
|
||
if (!$foundArtifacts) {
|
||
Write-Info "One-time Docker cleanup..."
|
||
$foundArtifacts = $true
|
||
}
|
||
Write-Info " Removing container: $container"
|
||
docker stop $container 2>$null | Out-Null
|
||
docker rm $container 2>$null | Out-Null
|
||
}
|
||
} catch {
|
||
# Ignore errors
|
||
}
|
||
}
|
||
|
||
# Remove images
|
||
$images = @("gemini-mcp-server:latest", "zen-mcp-server:latest")
|
||
foreach ($image in $images) {
|
||
try {
|
||
$exists = docker images --format "{{.Repository}}:{{.Tag}}" | Where-Object { $_ -eq $image }
|
||
if ($exists) {
|
||
if (!$foundArtifacts) {
|
||
Write-Info "One-time Docker cleanup..."
|
||
$foundArtifacts = $true
|
||
}
|
||
Write-Info " Removing image: $image"
|
||
docker rmi $image 2>$null | Out-Null
|
||
}
|
||
} catch {
|
||
# Ignore errors
|
||
}
|
||
}
|
||
|
||
# Remove volumes
|
||
$volumes = @("redis_data", "mcp_logs")
|
||
foreach ($volume in $volumes) {
|
||
try {
|
||
$exists = docker volume ls --format "{{.Name}}" | Where-Object { $_ -eq $volume }
|
||
if ($exists) {
|
||
if (!$foundArtifacts) {
|
||
Write-Info "One-time Docker cleanup..."
|
||
$foundArtifacts = $true
|
||
}
|
||
Write-Info " Removing volume: $volume"
|
||
docker volume rm $volume 2>$null | Out-Null
|
||
}
|
||
} catch {
|
||
# Ignore errors
|
||
}
|
||
}
|
||
|
||
if ($foundArtifacts) {
|
||
Write-Success "Docker cleanup complete"
|
||
}
|
||
|
||
New-Item -Path $DOCKER_CLEANED_FLAG -ItemType File -Force | Out-Null
|
||
}
|
||
|
||
# Validate API keys
|
||
function Test-ApiKeys {
|
||
Write-Step "Validating API Keys"
|
||
|
||
if (!(Test-Path ".env")) {
|
||
Write-Warning "No .env file found. API keys should be configured."
|
||
return $false
|
||
}
|
||
|
||
$envContent = Get-Content ".env"
|
||
$hasValidKey = $false
|
||
|
||
$keyPatterns = @{
|
||
"GEMINI_API_KEY" = "AIza[0-9A-Za-z-_]{35}"
|
||
"OPENAI_API_KEY" = "sk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20}"
|
||
"XAI_API_KEY" = "xai-[a-zA-Z0-9-_]+"
|
||
"OPENROUTER_API_KEY" = "sk-or-[a-zA-Z0-9-_]+"
|
||
}
|
||
|
||
foreach ($line in $envContent) {
|
||
if ($line -match '^([^#][^=]*?)=(.*)$') {
|
||
$key = $matches[1].Trim()
|
||
$value = $matches[2].Trim() -replace '^["'']|["'']$', ''
|
||
|
||
if ($keyPatterns.ContainsKey($key) -and $value -ne "your_${key.ToLower()}_here" -and $value.Length -gt 10) {
|
||
Write-Success "Found valid $key"
|
||
$hasValidKey = $true
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!$hasValidKey) {
|
||
Write-Warning "No valid API keys found in .env file"
|
||
Write-Info "Please edit .env file with your actual API keys"
|
||
return $false
|
||
}
|
||
|
||
return $true
|
||
}
|
||
|
||
# Check if uv is available
|
||
function Test-Uv {
|
||
return Test-Command "uv"
|
||
}
|
||
|
||
# Setup environment using uv-first approach
|
||
function Initialize-Environment {
|
||
Write-Step "Setting up Python Environment"
|
||
|
||
# Try uv first for faster package management
|
||
if (Test-Uv) {
|
||
Write-Info "Using uv for faster package management..."
|
||
|
||
if (Test-Path $VENV_PATH) {
|
||
if ($Force) {
|
||
Write-Warning "Removing existing environment..."
|
||
Remove-Item -Recurse -Force $VENV_PATH
|
||
} else {
|
||
Write-Success "Virtual environment already exists"
|
||
$pythonPath = "$VENV_PATH\Scripts\python.exe"
|
||
if (Test-Path $pythonPath) {
|
||
return $pythonPath
|
||
}
|
||
}
|
||
}
|
||
|
||
try {
|
||
Write-Info "Creating virtual environment with uv..."
|
||
uv venv $VENV_PATH --python 3.12
|
||
if ($LASTEXITCODE -eq 0) {
|
||
Write-Success "Environment created with uv"
|
||
return "$VENV_PATH\Scripts\python.exe"
|
||
}
|
||
} catch {
|
||
Write-Warning "uv failed, falling back to venv"
|
||
}
|
||
}
|
||
|
||
# Fallback to standard venv
|
||
$pythonCmd = Find-Python
|
||
if (!$pythonCmd) {
|
||
throw "Python 3.10+ not found"
|
||
}
|
||
|
||
if (Test-Path $VENV_PATH) {
|
||
if ($Force) {
|
||
Write-Warning "Removing existing environment..."
|
||
try {
|
||
# Stop any Python processes that might be using the venv
|
||
Get-Process python* -ErrorAction SilentlyContinue | Where-Object { $_.Path -like "*$VENV_PATH*" } | Stop-Process -Force -ErrorAction SilentlyContinue
|
||
|
||
# Wait a moment for processes to terminate
|
||
Start-Sleep -Seconds 2
|
||
|
||
# Use the robust removal function
|
||
if (Remove-LockedDirectory $VENV_PATH) {
|
||
Write-Success "Existing environment removed"
|
||
} else {
|
||
throw "Unable to remove existing environment. Please restart your computer and try again."
|
||
}
|
||
|
||
} catch {
|
||
Write-Error "Failed to remove existing environment: $_"
|
||
Write-Host ""
|
||
Write-Host "Try these solutions:" -ForegroundColor Yellow
|
||
Write-Host "1. Close all terminals and VS Code instances" -ForegroundColor White
|
||
Write-Host "2. Run: Get-Process python* | Stop-Process -Force" -ForegroundColor White
|
||
Write-Host "3. Manually delete: $VENV_PATH" -ForegroundColor White
|
||
Write-Host "4. Then run the script again" -ForegroundColor White
|
||
exit 1
|
||
}
|
||
} else {
|
||
Write-Success "Virtual environment already exists"
|
||
return "$VENV_PATH\Scripts\python.exe"
|
||
}
|
||
}
|
||
|
||
Write-Info "Creating virtual environment with $pythonCmd..."
|
||
if ($pythonCmd.StartsWith("py ")) {
|
||
Invoke-Expression "$pythonCmd -m venv $VENV_PATH"
|
||
} else {
|
||
& $pythonCmd -m venv $VENV_PATH
|
||
}
|
||
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "Failed to create virtual environment"
|
||
}
|
||
|
||
Write-Success "Virtual environment created"
|
||
return "$VENV_PATH\Scripts\python.exe"
|
||
}
|
||
|
||
# Setup virtual environment (legacy function for compatibility)
|
||
function Initialize-VirtualEnvironment {
|
||
Write-Step "Setting up Python Virtual Environment"
|
||
|
||
if (!$SkipVenv -and (Test-Path $VENV_PATH)) {
|
||
if ($Force) {
|
||
Write-Warning "Removing existing virtual environment..."
|
||
try {
|
||
# Stop any Python processes that might be using the venv
|
||
Get-Process python* -ErrorAction SilentlyContinue | Where-Object { $_.Path -like "*$VENV_PATH*" } | Stop-Process -Force -ErrorAction SilentlyContinue
|
||
|
||
# Wait a moment for processes to terminate
|
||
Start-Sleep -Seconds 2
|
||
|
||
# Use the robust removal function
|
||
if (Remove-LockedDirectory $VENV_PATH) {
|
||
Write-Success "Existing environment removed"
|
||
} else {
|
||
throw "Unable to remove existing environment. Please restart your computer and try again."
|
||
}
|
||
|
||
} catch {
|
||
Write-Error "Failed to remove existing environment: $_"
|
||
Write-Host ""
|
||
Write-Host "Try these solutions:" -ForegroundColor Yellow
|
||
Write-Host "1. Close all terminals and VS Code instances" -ForegroundColor White
|
||
Write-Host "2. Run: Get-Process python* | Stop-Process -Force" -ForegroundColor White
|
||
Write-Host "3. Manually delete: $VENV_PATH" -ForegroundColor White
|
||
Write-Host "4. Then run the script again" -ForegroundColor White
|
||
exit 1
|
||
}
|
||
} else {
|
||
Write-Success "Virtual environment already exists"
|
||
return
|
||
}
|
||
}
|
||
|
||
if ($SkipVenv) {
|
||
Write-Warning "Skipping virtual environment setup"
|
||
return
|
||
}
|
||
|
||
$pythonCmd = Find-Python
|
||
if (!$pythonCmd) {
|
||
Write-Error "Python 3.10+ not found. Please install Python from https://python.org"
|
||
exit 1
|
||
}
|
||
|
||
Write-Info "Using Python: $pythonCmd"
|
||
Write-Info "Creating virtual environment..."
|
||
|
||
try {
|
||
if ($pythonCmd.StartsWith("py ")) {
|
||
Invoke-Expression "$pythonCmd -m venv $VENV_PATH"
|
||
} else {
|
||
& $pythonCmd -m venv $VENV_PATH
|
||
}
|
||
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "Failed to create virtual environment"
|
||
}
|
||
|
||
Write-Success "Virtual environment created"
|
||
} catch {
|
||
Write-Error "Failed to create virtual environment: $_"
|
||
exit 1
|
||
}
|
||
}
|
||
|
||
# Install dependencies function
|
||
function Install-Dependencies {
|
||
param([string]$PythonPath = "")
|
||
|
||
if ($PythonPath -eq "" -or $args.Count -eq 0) {
|
||
# Legacy call without parameters
|
||
$pipCmd = if (Test-Path "$VENV_PATH\Scripts\pip.exe") {
|
||
"$VENV_PATH\Scripts\pip.exe"
|
||
} elseif (Test-Command "pip") {
|
||
"pip"
|
||
} else {
|
||
Write-Error "pip not found"
|
||
exit 1
|
||
}
|
||
|
||
Write-Step "Installing Dependencies"
|
||
Write-Info "Installing Python dependencies..."
|
||
|
||
try {
|
||
# Install main dependencies
|
||
& $pipCmd install -r requirements.txt
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "Failed to install main dependencies"
|
||
}
|
||
|
||
# Install dev dependencies if file exists
|
||
if (Test-Path "requirements-dev.txt") {
|
||
& $pipCmd install -r requirements-dev.txt
|
||
if ($LASTEXITCODE -ne 0) {
|
||
Write-Warning "Failed to install dev dependencies, continuing..."
|
||
} else {
|
||
Write-Success "Development dependencies installed"
|
||
}
|
||
}
|
||
|
||
Write-Success "Dependencies installed successfully"
|
||
} catch {
|
||
Write-Error "Failed to install dependencies: $_"
|
||
exit 1
|
||
}
|
||
return
|
||
}
|
||
|
||
# Version with parameter - use uv or pip
|
||
Write-Step "Installing Dependencies"
|
||
|
||
# Try uv first
|
||
if (Test-Uv) {
|
||
Write-Info "Installing dependencies with uv..."
|
||
try {
|
||
uv pip install -r requirements.txt
|
||
if ($LASTEXITCODE -eq 0) {
|
||
Write-Success "Dependencies installed with uv"
|
||
return
|
||
}
|
||
} catch {
|
||
Write-Warning "uv install failed, falling back to pip"
|
||
}
|
||
}
|
||
|
||
# Fallback to pip
|
||
$pipCmd = "$VENV_PATH\Scripts\pip.exe"
|
||
if (!(Test-Path $pipCmd)) {
|
||
$pipCmd = "pip"
|
||
}
|
||
|
||
Write-Info "Installing dependencies with pip..."
|
||
|
||
# Upgrade pip first
|
||
try {
|
||
& $pipCmd install --upgrade pip
|
||
} catch {
|
||
Write-Warning "Could not upgrade pip, continuing..."
|
||
}
|
||
|
||
# Install main dependencies
|
||
& $pipCmd install -r requirements.txt
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "Failed to install main dependencies"
|
||
}
|
||
|
||
# Install dev dependencies if file exists
|
||
if (Test-Path "requirements-dev.txt") {
|
||
& $pipCmd install -r requirements-dev.txt
|
||
if ($LASTEXITCODE -eq 0) {
|
||
Write-Success "Development dependencies installed"
|
||
} else {
|
||
Write-Warning "Failed to install dev dependencies, continuing..."
|
||
}
|
||
}
|
||
|
||
Write-Success "Dependencies installed successfully"
|
||
}
|
||
|
||
# Setup logging directory
|
||
function Initialize-Logging {
|
||
Write-Step "Setting up Logging"
|
||
|
||
if (!(Test-Path $LOG_DIR)) {
|
||
New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null
|
||
Write-Success "Logs directory created"
|
||
} else {
|
||
Write-Success "Logs directory already exists"
|
||
}
|
||
}
|
||
|
||
# Check Docker
|
||
function Test-Docker {
|
||
Write-Step "Checking Docker Setup"
|
||
|
||
if ($SkipDocker) {
|
||
Write-Warning "Skipping Docker checks"
|
||
return
|
||
}
|
||
|
||
if (Test-Command "docker") {
|
||
try {
|
||
$null = docker version 2>$null
|
||
Write-Success "Docker is installed and running"
|
||
|
||
if (Test-Command "docker-compose") {
|
||
Write-Success "Docker Compose is available"
|
||
} else {
|
||
Write-Warning "Docker Compose not found. Install Docker Desktop for Windows."
|
||
}
|
||
} catch {
|
||
Write-Warning "Docker is installed but not running. Please start Docker Desktop."
|
||
}
|
||
} else {
|
||
Write-Warning "Docker not found. Install Docker Desktop from https://docker.com"
|
||
}
|
||
}
|
||
|
||
# Check Claude Desktop integration with full functionality like Bash version
|
||
function Test-ClaudeDesktopIntegration {
|
||
param([string]$PythonPath, [string]$ServerPath)
|
||
|
||
# Skip if already configured (check flag)
|
||
if (Test-Path $DESKTOP_CONFIG_FLAG) {
|
||
return
|
||
}
|
||
|
||
Write-Step "Checking Claude Desktop Integration"
|
||
|
||
$claudeConfigPath = "$env:APPDATA\Claude\claude_desktop_config.json"
|
||
|
||
if (!(Test-Path $claudeConfigPath)) {
|
||
Write-Warning "Claude Desktop config not found at: $claudeConfigPath"
|
||
Write-Info "Please install Claude Desktop first"
|
||
Write-Host ""
|
||
Write-Host "To configure manually, add this to your Claude Desktop config:"
|
||
Write-Host @"
|
||
{
|
||
"mcpServers": {
|
||
"zen": {
|
||
"command": "$PythonPath",
|
||
"args": ["$ServerPath"]
|
||
}
|
||
}
|
||
}
|
||
"@ -ForegroundColor Yellow
|
||
return
|
||
}
|
||
|
||
Write-Host ""
|
||
$response = Read-Host "Configure Zen for Claude Desktop? (Y/n)"
|
||
if ($response -eq 'n' -or $response -eq 'N') {
|
||
Write-Info "Skipping Claude Desktop integration"
|
||
New-Item -Path $DESKTOP_CONFIG_FLAG -ItemType File -Force | Out-Null
|
||
return
|
||
}
|
||
|
||
# Create config directory if it doesn't exist
|
||
$configDir = Split-Path $claudeConfigPath -Parent
|
||
if (!(Test-Path $configDir)) {
|
||
New-Item -ItemType Directory -Path $configDir -Force | Out-Null
|
||
}
|
||
|
||
try {
|
||
$config = @{}
|
||
|
||
# Handle existing config
|
||
if (Test-Path $claudeConfigPath) {
|
||
Write-Info "Updating existing Claude Desktop config..."
|
||
|
||
# Create backup
|
||
$backupPath = "$claudeConfigPath.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
|
||
Copy-Item $claudeConfigPath $backupPath
|
||
|
||
# Read existing config
|
||
$existingContent = Get-Content $claudeConfigPath -Raw
|
||
$config = $existingContent | ConvertFrom-Json
|
||
|
||
# Check for old Docker config and remove it
|
||
if ($existingContent -match "docker.*compose.*zen|zen.*docker") {
|
||
Write-Warning "Removing old Docker-based MCP configuration..."
|
||
if ($config.mcpServers -and $config.mcpServers.zen) {
|
||
$config.mcpServers.PSObject.Properties.Remove('zen')
|
||
Write-Success "Removed old zen MCP configuration"
|
||
}
|
||
}
|
||
} else {
|
||
Write-Info "Creating new Claude Desktop config..."
|
||
}
|
||
|
||
# Ensure mcpServers exists
|
||
if (!$config.mcpServers) {
|
||
$config | Add-Member -MemberType NoteProperty -Name "mcpServers" -Value @{} -Force
|
||
}
|
||
|
||
# Add zen server configuration
|
||
$serverConfig = @{
|
||
command = $PythonPath
|
||
args = @($ServerPath)
|
||
}
|
||
|
||
$config.mcpServers | Add-Member -MemberType NoteProperty -Name "zen" -Value $serverConfig -Force
|
||
|
||
# Write updated config
|
||
$config | ConvertTo-Json -Depth 10 | Out-File $claudeConfigPath -Encoding UTF8
|
||
|
||
Write-Success "Successfully configured Claude Desktop"
|
||
Write-Host " Config: $claudeConfigPath" -ForegroundColor Gray
|
||
Write-Host " Restart Claude Desktop to use the new MCP server" -ForegroundColor Gray
|
||
New-Item -Path $DESKTOP_CONFIG_FLAG -ItemType File -Force | Out-Null
|
||
|
||
} catch {
|
||
Write-Error "Failed to update Claude Desktop config: $_"
|
||
Write-Host ""
|
||
Write-Host "Manual configuration:"
|
||
Write-Host "Location: $claudeConfigPath"
|
||
Write-Host "Add this configuration:"
|
||
Write-Host @"
|
||
{
|
||
"mcpServers": {
|
||
"zen": {
|
||
"command": "$PythonPath",
|
||
"args": ["$ServerPath"]
|
||
}
|
||
}
|
||
}
|
||
"@ -ForegroundColor Yellow
|
||
}
|
||
}
|
||
|
||
# Check Claude CLI integration
|
||
function Test-ClaudeCliIntegration {
|
||
param([string]$PythonPath, [string]$ServerPath)
|
||
|
||
if (!(Test-Command "claude")) {
|
||
return
|
||
}
|
||
|
||
Write-Info "Claude CLI detected - checking configuration..."
|
||
|
||
try {
|
||
$claudeConfig = claude config list 2>$null
|
||
if ($claudeConfig -match "zen") {
|
||
Write-Success "Claude CLI already configured for zen server"
|
||
} else {
|
||
Write-Info "To add zen server to Claude CLI, run:"
|
||
Write-Host " claude config add-server zen $PythonPath $ServerPath" -ForegroundColor Cyan
|
||
}
|
||
} catch {
|
||
Write-Info "To configure Claude CLI manually, run:"
|
||
Write-Host " claude config add-server zen $PythonPath $ServerPath" -ForegroundColor Cyan
|
||
}
|
||
}
|
||
|
||
# Check and update Gemini CLI configuration
|
||
function Test-GeminiCliIntegration {
|
||
param([string]$ScriptDir)
|
||
|
||
$zenWrapper = Join-Path $ScriptDir "zen-mcp-server.cmd"
|
||
|
||
# Check if Gemini settings file exists (Windows path)
|
||
$geminiConfig = "$env:USERPROFILE\.gemini\settings.json"
|
||
if (!(Test-Path $geminiConfig)) {
|
||
# Gemini CLI not installed or not configured
|
||
return
|
||
}
|
||
|
||
# Check if zen is already configured
|
||
$configContent = Get-Content $geminiConfig -Raw -ErrorAction SilentlyContinue
|
||
if ($configContent -and $configContent -match '"zen"') {
|
||
# Already configured
|
||
return
|
||
}
|
||
|
||
# Ask user if they want to add Zen to Gemini CLI
|
||
Write-Host ""
|
||
$response = Read-Host "Configure Zen for Gemini CLI? (Y/n)"
|
||
if ($response -eq 'n' -or $response -eq 'N') {
|
||
Write-Info "Skipping Gemini CLI integration"
|
||
return
|
||
}
|
||
|
||
# Ensure wrapper script exists
|
||
if (!(Test-Path $zenWrapper)) {
|
||
Write-Info "Creating wrapper script for Gemini CLI..."
|
||
@"
|
||
@echo off
|
||
cd /d "%~dp0"
|
||
if exist ".zen_venv\Scripts\python.exe" (
|
||
.zen_venv\Scripts\python.exe server.py %*
|
||
) else (
|
||
python server.py %*
|
||
)
|
||
"@ | Out-File -FilePath $zenWrapper -Encoding ASCII
|
||
|
||
Write-Success "Created zen-mcp-server.cmd wrapper script"
|
||
}
|
||
|
||
# Update Gemini settings
|
||
Write-Info "Updating Gemini CLI configuration..."
|
||
|
||
try {
|
||
# Create backup
|
||
$backupPath = "$geminiConfig.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
|
||
Copy-Item $geminiConfig $backupPath -ErrorAction SilentlyContinue
|
||
|
||
# Read existing config or create new one
|
||
$config = @{}
|
||
if (Test-Path $geminiConfig) {
|
||
$config = Get-Content $geminiConfig -Raw | ConvertFrom-Json
|
||
}
|
||
|
||
# Ensure mcpServers exists
|
||
if (!$config.mcpServers) {
|
||
$config | Add-Member -MemberType NoteProperty -Name "mcpServers" -Value @{} -Force
|
||
}
|
||
|
||
# Add zen server
|
||
$zenConfig = @{
|
||
command = $zenWrapper
|
||
}
|
||
|
||
$config.mcpServers | Add-Member -MemberType NoteProperty -Name "zen" -Value $zenConfig -Force
|
||
|
||
# Write updated config
|
||
$config | ConvertTo-Json -Depth 10 | Out-File $geminiConfig -Encoding UTF8
|
||
|
||
Write-Success "Successfully configured Gemini CLI"
|
||
Write-Host " Config: $geminiConfig" -ForegroundColor Gray
|
||
Write-Host " Restart Gemini CLI to use Zen MCP Server" -ForegroundColor Gray
|
||
|
||
} catch {
|
||
Write-Error "Failed to update Gemini CLI config: $_"
|
||
Write-Host ""
|
||
Write-Host "Manual config location: $geminiConfig"
|
||
Write-Host "Add this configuration:"
|
||
Write-Host @"
|
||
{
|
||
"mcpServers": {
|
||
"zen": {
|
||
"command": "$zenWrapper"
|
||
}
|
||
}
|
||
}
|
||
"@ -ForegroundColor Yellow
|
||
}
|
||
}
|
||
|
||
# Display configuration instructions
|
||
function Show-ConfigInstructions {
|
||
param([string]$PythonPath, [string]$ServerPath)
|
||
|
||
# Get script directory for Gemini CLI config
|
||
$scriptDir = Split-Path $ServerPath -Parent
|
||
$zenWrapper = Join-Path $scriptDir "zen-mcp-server.cmd"
|
||
|
||
Write-Host ""
|
||
Write-Host "===== ZEN MCP SERVER CONFIGURATION =====" -ForegroundColor Cyan
|
||
Write-Host "==========================================" -ForegroundColor Cyan
|
||
Write-Host ""
|
||
Write-Host "To use Zen MCP Server with your Claude clients:"
|
||
Write-Host ""
|
||
|
||
Write-Info "1. For Claude Desktop:"
|
||
Write-Host " Add this configuration to your Claude Desktop config file:"
|
||
Write-Host " Location: $env:APPDATA\Claude\claude_desktop_config.json"
|
||
Write-Host ""
|
||
|
||
$configJson = @{
|
||
mcpServers = @{
|
||
zen = @{
|
||
command = $PythonPath
|
||
args = @($ServerPath)
|
||
}
|
||
}
|
||
} | ConvertTo-Json -Depth 5
|
||
|
||
Write-Host $configJson -ForegroundColor Yellow
|
||
Write-Host ""
|
||
|
||
Write-Info "2. For Gemini CLI:"
|
||
Write-Host " Add this configuration to ~/.gemini/settings.json:"
|
||
Write-Host " Location: $env:USERPROFILE\.gemini\settings.json"
|
||
Write-Host ""
|
||
|
||
$geminiConfigJson = @{
|
||
mcpServers = @{
|
||
zen = @{
|
||
command = $zenWrapper
|
||
}
|
||
}
|
||
} | ConvertTo-Json -Depth 5
|
||
|
||
Write-Host $geminiConfigJson -ForegroundColor Yellow
|
||
Write-Host ""
|
||
|
||
Write-Info "3. Restart Claude Desktop or Gemini CLI after updating the config files"
|
||
Write-Host ""
|
||
Write-Info "Note: Claude Code (CLI) is not available on Windows (except in WSL2)"
|
||
Write-Host ""
|
||
}
|
||
|
||
# Follow logs in real-time
|
||
function Follow-Logs {
|
||
$logPath = Join-Path $LOG_DIR $LOG_FILE
|
||
|
||
Write-Host "Following server logs (Ctrl+C to stop)..." -ForegroundColor Yellow
|
||
Write-Host ""
|
||
|
||
# Create logs directory and file if they don't exist
|
||
if (!(Test-Path $LOG_DIR)) {
|
||
New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null
|
||
}
|
||
|
||
if (!(Test-Path $logPath)) {
|
||
New-Item -ItemType File -Path $logPath -Force | Out-Null
|
||
}
|
||
|
||
# Follow the log file using Get-Content -Wait
|
||
try {
|
||
Get-Content $logPath -Wait
|
||
} catch {
|
||
Write-Error "Could not follow logs: $_"
|
||
}
|
||
}
|
||
|
||
# Show help message
|
||
function Show-Help {
|
||
$version = Get-Version
|
||
Write-Host ""
|
||
Write-Host "🤖 Zen MCP Server v$version" -ForegroundColor Cyan
|
||
Write-Host "=============================" -ForegroundColor Cyan
|
||
Write-Host ""
|
||
Write-Host "Usage: .\run-server.ps1 [OPTIONS]"
|
||
Write-Host ""
|
||
Write-Host "Options:"
|
||
Write-Host " -Help Show this help message"
|
||
Write-Host " -Version Show version information"
|
||
Write-Host " -Follow Follow server logs in real-time"
|
||
Write-Host " -Config Show configuration instructions for Claude clients"
|
||
Write-Host " -ClearCache Clear Python cache and exit (helpful for import issues)"
|
||
Write-Host " -Force Force recreate virtual environment"
|
||
Write-Host " -SkipVenv Skip virtual environment setup"
|
||
Write-Host " -SkipDocker Skip Docker checks"
|
||
Write-Host ""
|
||
Write-Host "Examples:"
|
||
Write-Host " .\run-server.ps1 Setup and start the MCP server"
|
||
Write-Host " .\run-server.ps1 -Follow Setup and follow logs"
|
||
Write-Host " .\run-server.ps1 -Config Show configuration instructions"
|
||
Write-Host " .\run-server.ps1 -Version Show version only"
|
||
Write-Host " .\run-server.ps1 -ClearCache Clear Python cache (fixes import issues)"
|
||
Write-Host ""
|
||
Write-Host "For more information, visit:"
|
||
Write-Host " https://github.com/BeehiveInnovations/zen-mcp-server"
|
||
Write-Host ""
|
||
}
|
||
|
||
# Show version only
|
||
function Show-Version {
|
||
$version = Get-Version
|
||
Write-Host $version
|
||
}
|
||
|
||
# Display setup instructions
|
||
function Show-SetupInstructions {
|
||
param([string]$PythonPath, [string]$ServerPath)
|
||
|
||
Write-Host ""
|
||
Write-Host "===== SETUP COMPLETE =====" -ForegroundColor Green
|
||
Write-Host "===========================" -ForegroundColor Green
|
||
Write-Host ""
|
||
Write-Success "Zen is ready to use!"
|
||
Write-Host ""
|
||
}
|
||
|
||
# Load environment variables from .env file
|
||
function Import-EnvFile {
|
||
if (Test-Path ".env") {
|
||
Get-Content ".env" | ForEach-Object {
|
||
if ($_ -match '^([^#][^=]*?)=(.*)$') {
|
||
$name = $matches[1].Trim()
|
||
$value = $matches[2].Trim()
|
||
# Remove quotes if present
|
||
$value = $value -replace '^["'']|["'']$', ''
|
||
[Environment]::SetEnvironmentVariable($name, $value, "Process")
|
||
}
|
||
}
|
||
Write-Success "Environment variables loaded"
|
||
}
|
||
}
|
||
|
||
# Setup environment file
|
||
function Initialize-EnvFile {
|
||
Write-Step "Setting up Environment Configuration"
|
||
|
||
if (!(Test-Path ".env")) {
|
||
if (Test-Path ".env.example") {
|
||
Write-Info "Creating .env file from .env.example..."
|
||
Copy-Item ".env.example" ".env"
|
||
Write-Success ".env file created"
|
||
Write-Warning "Please edit .env file with your API keys!"
|
||
} else {
|
||
Write-Warning ".env.example not found, creating basic .env file"
|
||
@"
|
||
# Zen MCP Server Configuration
|
||
# Add your API keys here
|
||
|
||
# Google/Gemini API Key
|
||
GEMINI_API_KEY=your_gemini_api_key_here
|
||
|
||
# OpenAI API Key
|
||
OPENAI_API_KEY=your_openai_api_key_here
|
||
|
||
# xAI API Key
|
||
XAI_API_KEY=your_xai_api_key_here
|
||
|
||
# OpenRouter API Key
|
||
OPENROUTER_API_KEY=your_openrouter_api_key_here
|
||
|
||
# Logging
|
||
LOGGING_LEVEL=INFO
|
||
"@ | Out-File -FilePath ".env" -Encoding UTF8
|
||
Write-Success "Basic .env file created"
|
||
Write-Warning "Please edit .env file with your actual API keys!"
|
||
}
|
||
} else {
|
||
Write-Success ".env file already exists"
|
||
}
|
||
}
|
||
|
||
# ----------------------------------------------------------------------------
|
||
# Main Execution
|
||
# ----------------------------------------------------------------------------
|
||
|
||
# Main server start function
|
||
function Start-Server {
|
||
Write-Step "Starting Zen MCP Server"
|
||
|
||
# Load environment variables
|
||
Import-EnvFile
|
||
|
||
# Determine Python command
|
||
$pythonCmd = if (Test-Path "$VENV_PATH\Scripts\python.exe") {
|
||
"$VENV_PATH\Scripts\python.exe"
|
||
} elseif (Test-Command "python") {
|
||
"python"
|
||
} else {
|
||
Write-Error "Python not found"
|
||
exit 1
|
||
}
|
||
|
||
Write-Info "Starting server with: $pythonCmd"
|
||
Write-Info "Logs will be written to: $LOG_DIR\$LOG_FILE"
|
||
Write-Info "Press Ctrl+C to stop the server"
|
||
Write-Host ""
|
||
|
||
try {
|
||
& $pythonCmd server.py
|
||
} catch {
|
||
Write-Error "Server failed to start: $_"
|
||
exit 1
|
||
}
|
||
}
|
||
|
||
# Main execution function
|
||
function Start-MainProcess {
|
||
# Parse command line arguments
|
||
if ($Help) {
|
||
Show-Help
|
||
exit 0
|
||
}
|
||
|
||
if ($Version) {
|
||
Show-Version
|
||
exit 0
|
||
}
|
||
|
||
if ($ClearCache) {
|
||
Clear-PythonCache
|
||
Write-Success "Cache cleared successfully"
|
||
Write-Host ""
|
||
Write-Host "You can now run '.\run-server.ps1' normally"
|
||
exit 0
|
||
}
|
||
|
||
if ($Config) {
|
||
# Setup minimal environment to get paths for config display
|
||
Write-Info "Setting up environment for configuration display..."
|
||
Write-Host ""
|
||
try {
|
||
$pythonPath = Initialize-Environment
|
||
$serverPath = Resolve-Path "server.py"
|
||
Show-ConfigInstructions $pythonPath $serverPath
|
||
} catch {
|
||
Write-Error "Failed to setup environment: $_"
|
||
}
|
||
exit 0
|
||
}
|
||
|
||
# Display header
|
||
Write-Host ""
|
||
Write-Host "🤖 Zen MCP Server" -ForegroundColor Cyan
|
||
Write-Host "=================" -ForegroundColor Cyan
|
||
|
||
# Get and display version
|
||
$version = Get-Version
|
||
Write-Host "Version: $version"
|
||
Write-Host ""
|
||
|
||
# Check if venv exists
|
||
if (!(Test-Path $VENV_PATH)) {
|
||
Write-Info "Setting up Python environment for first time..."
|
||
}
|
||
|
||
# Step 1: Docker cleanup
|
||
Cleanup-Docker
|
||
|
||
# Step 1.5: Clear Python cache to prevent import issues
|
||
Clear-PythonCache
|
||
|
||
# Step 2: Setup environment file
|
||
Initialize-EnvFile
|
||
|
||
# Step 3: Load .env file
|
||
Import-EnvFile
|
||
|
||
# Step 4: Validate API keys
|
||
Test-ApiKeys
|
||
|
||
# Step 5: Setup Python environment
|
||
try {
|
||
$pythonPath = Initialize-Environment
|
||
} catch {
|
||
Write-Error "Failed to setup Python environment: $_"
|
||
exit 1
|
||
}
|
||
|
||
# Step 6: Install dependencies
|
||
try {
|
||
Install-Dependencies $pythonPath
|
||
} catch {
|
||
Write-Error "Failed to install dependencies: $_"
|
||
exit 1
|
||
}
|
||
|
||
# Step 7: Get absolute server path
|
||
$serverPath = Resolve-Path "server.py"
|
||
|
||
# Step 8: Display setup instructions
|
||
Show-SetupInstructions $pythonPath $serverPath
|
||
|
||
# Step 9: Check Claude integrations
|
||
Test-ClaudeCliIntegration $pythonPath $serverPath
|
||
Test-ClaudeDesktopIntegration $pythonPath $serverPath
|
||
|
||
# Step 10: Check Gemini CLI integration
|
||
Test-GeminiCliIntegration (Split-Path $serverPath -Parent)
|
||
|
||
# Step 11: Setup logging directory
|
||
Initialize-Logging
|
||
|
||
# Step 12: Display log information
|
||
Write-Host ""
|
||
Write-Host "Logs will be written to: $(Resolve-Path $LOG_DIR)\$LOG_FILE"
|
||
Write-Host ""
|
||
|
||
# Step 12: Handle command line arguments
|
||
if ($Follow) {
|
||
Follow-Logs
|
||
} else {
|
||
Write-Host "To follow logs: .\run-server.ps1 -Follow" -ForegroundColor Yellow
|
||
Write-Host "To show config: .\run-server.ps1 -Config" -ForegroundColor Yellow
|
||
Write-Host "To update: git pull, then run .\run-server.ps1 again" -ForegroundColor Yellow
|
||
Write-Host ""
|
||
Write-Host "Happy coding! 🎉" -ForegroundColor Green
|
||
|
||
# Ask if user wants to start server
|
||
$response = Read-Host "`nStart the server now? (y/N)"
|
||
if ($response -eq 'y' -or $response -eq 'Y') {
|
||
Start-Server
|
||
}
|
||
}
|
||
}
|
||
|
||
# Run main function
|
||
Start-MainProcess
|