Files
my-pal-mcp-server/run-server.ps1
OhMyApps 70196d680d Add PowerShell scripts for server setup and integration testing
- 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`).
2025-06-27 21:37:11 +02:00

1200 lines
37 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters

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

#!/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