- Improved error handling and path resolution in run-server.ps1 for better reliability. - Implemented conversation tests for Docker mode compatibility in validation_crossplatform.py. - Updated run-server.ps1 to include detailed help documentation, configuration management, and backup retention for configuration files. - Added Docker path validation tests in validation_crossplatform.py to ensure correct path handling in Docker mode. - Enhanced integration test script run_integration_tests.ps1 with comprehensive documentation and parameter support for output customization.
1859 lines
58 KiB
PowerShell
1859 lines
58 KiB
PowerShell
<#
|
||
.SYNOPSIS
|
||
Installation, configuration, and launch script for Zen MCP server on Windows.
|
||
|
||
.DESCRIPTION
|
||
This PowerShell script prepares the environment for the Zen MCP server:
|
||
- Installs and checks Python 3.10+ (with venv or uv if available)
|
||
- Installs required Python dependencies
|
||
- Configures environment files (.env)
|
||
- Validates presence of required API keys
|
||
- Cleans Python caches and obsolete Docker artifacts
|
||
- Offers automatic integration with Claude Desktop, Gemini CLI, VSCode, Cursor, Windsurf, and Trae
|
||
- Manages configuration file backups (max 3 retained)
|
||
- Allows real-time log following or server launch
|
||
|
||
.PARAMETER Help
|
||
Shows script help.
|
||
|
||
.PARAMETER Version
|
||
Shows Zen MCP server version.
|
||
|
||
.PARAMETER Follow
|
||
Follows server logs in real time.
|
||
|
||
.PARAMETER Config
|
||
Shows configuration instructions for Claude and other compatible clients.
|
||
|
||
.PARAMETER ClearCache
|
||
Removes Python cache files (__pycache__, .pyc).
|
||
|
||
.PARAMETER SkipVenv
|
||
Skips Python virtual environment creation.
|
||
|
||
.PARAMETER SkipDocker
|
||
Skips Docker checks and cleanup.
|
||
|
||
.PARAMETER Force
|
||
Forces recreation of the Python virtual environment.
|
||
|
||
.PARAMETER VerboseOutput
|
||
Enables more detailed output (currently unused).
|
||
|
||
.EXAMPLE
|
||
.\run-server.ps1
|
||
Prepares the environment and starts the Zen MCP server.
|
||
|
||
.\run-server.ps1 -Follow
|
||
Follows server logs in real time.
|
||
|
||
.\run-server.ps1 -Config
|
||
Shows configuration instructions for clients.
|
||
|
||
.NOTES
|
||
Project Author : BeehiveInnovations
|
||
Script Author : GiGiDKR (https://github.com/GiGiDKR)
|
||
Date : 07-05-2025
|
||
Version : See config.py (__version__)
|
||
References : https://github.com/BeehiveInnovations/zen-mcp-server
|
||
|
||
#>
|
||
#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
|
||
}
|
||
}
|
||
|
||
# Manage configuration file backups with maximum 3 files retention
|
||
function Manage-ConfigBackups {
|
||
param(
|
||
[string]$ConfigFilePath,
|
||
[int]$MaxBackups = 3
|
||
)
|
||
|
||
if (!(Test-Path $ConfigFilePath)) {
|
||
Write-Warning "Configuration file not found: $ConfigFilePath"
|
||
return $null
|
||
}
|
||
|
||
try {
|
||
# Create new backup with timestamp
|
||
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
|
||
$backupPath = "$ConfigFilePath.backup_$timestamp"
|
||
Copy-Item $ConfigFilePath $backupPath -ErrorAction Stop
|
||
|
||
# Find all existing backups for this config file
|
||
$configDir = Split-Path $ConfigFilePath -Parent
|
||
$configFileName = Split-Path $ConfigFilePath -Leaf
|
||
$backupPattern = "$configFileName.backup_*"
|
||
|
||
$existingBackups = Get-ChildItem -Path $configDir -Filter $backupPattern -ErrorAction SilentlyContinue |
|
||
Sort-Object LastWriteTime -Descending
|
||
|
||
# Keep only the most recent MaxBackups files
|
||
if ($existingBackups.Count -gt $MaxBackups) {
|
||
$backupsToRemove = $existingBackups | Select-Object -Skip $MaxBackups
|
||
foreach ($backup in $backupsToRemove) {
|
||
try {
|
||
Remove-Item $backup.FullName -Force -ErrorAction Stop
|
||
Write-Info "Removed old backup: $($backup.Name)"
|
||
} catch {
|
||
Write-Warning "Could not remove old backup: $($backup.Name)"
|
||
}
|
||
}
|
||
Write-Success "Backup retention: kept $MaxBackups most recent backups"
|
||
}
|
||
|
||
Write-Success "Backup created: $(Split-Path $backupPath -Leaf)"
|
||
return $backupPath
|
||
|
||
} catch {
|
||
Write-Warning "Failed to create backup: $_"
|
||
return $null
|
||
}
|
||
}
|
||
|
||
# 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: $_"
|
||
}
|
||
}
|
||
|
||
# Get absolute path
|
||
function Get-AbsolutePath {
|
||
param([string]$Path)
|
||
|
||
if (Test-Path $Path) {
|
||
# Use Resolve-Path for full resolution
|
||
return Resolve-Path $Path
|
||
} else {
|
||
# Use unresolved method
|
||
return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
|
||
}
|
||
}
|
||
|
||
# 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 Get-AbsolutePath $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 Get-AbsolutePath "$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 Get-AbsolutePath "$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 Get-AbsolutePath "$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 -ne 'y' -and $response -ne 'Y') {
|
||
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 with retention management
|
||
$backupPath = Manage-ConfigBackups $claudeConfigPath
|
||
|
||
# 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 -ne 'y' -and $response -ne 'Y') {
|
||
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 with retention management
|
||
$backupPath = Manage-ConfigBackups $geminiConfig
|
||
|
||
# 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
|
||
}
|
||
}
|
||
|
||
# Check and update Cursor configuration
|
||
function Test-CursorIntegration {
|
||
param([string]$PythonPath, [string]$ServerPath)
|
||
|
||
Write-Step "Checking Cursor Integration"
|
||
|
||
# Check if Cursor is installed
|
||
if (!(Test-Command "cursor")) {
|
||
Write-Info "Cursor not detected - skipping Cursor integration"
|
||
return
|
||
}
|
||
|
||
Write-Info "Found Cursor"
|
||
|
||
$cursorConfigPath = "$env:USERPROFILE\.cursor\mcp.json"
|
||
|
||
# Check if MCP is already configured
|
||
if (Test-Path $cursorConfigPath) {
|
||
try {
|
||
$settings = Get-Content $cursorConfigPath -Raw | ConvertFrom-Json
|
||
if ($settings.mcpServers -and $settings.mcpServers.zen) {
|
||
Write-Success "Zen MCP already configured in Cursor"
|
||
return
|
||
}
|
||
} catch {
|
||
Write-Warning "Could not read existing Cursor configuration"
|
||
}
|
||
}
|
||
|
||
# Ask user if they want to configure Cursor
|
||
Write-Host ""
|
||
$response = Read-Host "Configure Zen MCP for Cursor? (y/N)"
|
||
if ($response -ne 'y' -and $response -ne 'Y') {
|
||
Write-Info "Skipping Cursor integration"
|
||
return
|
||
}
|
||
|
||
try {
|
||
# Create config directory if it doesn't exist
|
||
$configDir = Split-Path $cursorConfigPath -Parent
|
||
if (!(Test-Path $configDir)) {
|
||
New-Item -ItemType Directory -Path $configDir -Force | Out-Null
|
||
}
|
||
|
||
# Create backup with retention management
|
||
if (Test-Path $cursorConfigPath) {
|
||
$backupPath = Manage-ConfigBackups $cursorConfigPath
|
||
}
|
||
|
||
# Read existing config or create new one
|
||
$config = @{}
|
||
if (Test-Path $cursorConfigPath) {
|
||
$config = Get-Content $cursorConfigPath -Raw | ConvertFrom-Json
|
||
}
|
||
|
||
# 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 $cursorConfigPath -Encoding UTF8
|
||
|
||
Write-Success "Successfully configured Cursor"
|
||
Write-Host " Config: $cursorConfigPath" -ForegroundColor Gray
|
||
Write-Host " Restart Cursor to use Zen MCP Server" -ForegroundColor Gray
|
||
|
||
} catch {
|
||
Write-Error "Failed to update Cursor configuration: $_"
|
||
Write-Host ""
|
||
Write-Host "Manual configuration for Cursor:"
|
||
Write-Host "Location: $cursorConfigPath"
|
||
Write-Host "Add this configuration:"
|
||
Write-Host @"
|
||
{
|
||
"mcpServers": {
|
||
"zen": {
|
||
"command": "$PythonPath",
|
||
"args": ["$ServerPath"]
|
||
}
|
||
}
|
||
}
|
||
"@ -ForegroundColor Yellow
|
||
}
|
||
}
|
||
|
||
# Check and update Windsurf configuration
|
||
function Test-WindsurfIntegration {
|
||
param([string]$PythonPath, [string]$ServerPath)
|
||
|
||
Write-Step "Checking Windsurf Integration"
|
||
|
||
$windsurfConfigPath = "$env:USERPROFILE\.codeium\windsurf\mcp_config.json"
|
||
$windsurfAppDir = "$env:USERPROFILE\.codeium\windsurf"
|
||
|
||
# Check if Windsurf directory exists (better detection than command)
|
||
if (!(Test-Path $windsurfAppDir)) {
|
||
Write-Info "Windsurf not detected - skipping Windsurf integration"
|
||
return
|
||
}
|
||
|
||
Write-Info "Found Windsurf installation"
|
||
|
||
# Check if MCP is already configured
|
||
if (Test-Path $windsurfConfigPath) {
|
||
try {
|
||
$settings = Get-Content $windsurfConfigPath -Raw | ConvertFrom-Json
|
||
if ($settings.mcpServers -and $settings.mcpServers.zen) {
|
||
Write-Success "Zen MCP already configured in Windsurf"
|
||
return
|
||
}
|
||
} catch {
|
||
Write-Warning "Could not read existing Windsurf configuration"
|
||
}
|
||
}
|
||
|
||
# Ask user if they want to configure Windsurf
|
||
Write-Host ""
|
||
$response = Read-Host "Configure Zen MCP for Windsurf? (y/N)"
|
||
if ($response -ne 'y' -and $response -ne 'Y') {
|
||
Write-Info "Skipping Windsurf integration"
|
||
return
|
||
}
|
||
|
||
try {
|
||
# Create config directory if it doesn't exist
|
||
$configDir = Split-Path $windsurfConfigPath -Parent
|
||
if (!(Test-Path $configDir)) {
|
||
New-Item -ItemType Directory -Path $configDir -Force | Out-Null
|
||
}
|
||
|
||
# Create backup with retention management
|
||
if (Test-Path $windsurfConfigPath) {
|
||
$backupPath = Manage-ConfigBackups $windsurfConfigPath
|
||
}
|
||
|
||
# Read existing config or create new one
|
||
$config = @{}
|
||
if (Test-Path $windsurfConfigPath) {
|
||
$config = Get-Content $windsurfConfigPath -Raw | ConvertFrom-Json
|
||
}
|
||
|
||
# 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 $windsurfConfigPath -Encoding UTF8
|
||
|
||
Write-Success "Successfully configured Windsurf"
|
||
Write-Host " Config: $windsurfConfigPath" -ForegroundColor Gray
|
||
Write-Host " Restart Windsurf to use Zen MCP Server" -ForegroundColor Gray
|
||
|
||
} catch {
|
||
Write-Error "Failed to update Windsurf configuration: $_"
|
||
Write-Host ""
|
||
Write-Host "Manual configuration for Windsurf:"
|
||
Write-Host "Location: $windsurfConfigPath"
|
||
Write-Host "Add this configuration:"
|
||
Write-Host @"
|
||
{
|
||
"mcpServers": {
|
||
"zen": {
|
||
"command": "$PythonPath",
|
||
"args": ["$ServerPath"]
|
||
}
|
||
}
|
||
}
|
||
"@ -ForegroundColor Yellow
|
||
}
|
||
}
|
||
|
||
# Check and update Trae configuration
|
||
function Test-TraeIntegration {
|
||
param([string]$PythonPath, [string]$ServerPath)
|
||
|
||
Write-Step "Checking Trae Integration"
|
||
|
||
$traeConfigPath = "$env:APPDATA\Trae\User\mcp.json"
|
||
$traeAppDir = "$env:APPDATA\Trae"
|
||
|
||
# Check if Trae directory exists (better detection than command)
|
||
if (!(Test-Path $traeAppDir)) {
|
||
Write-Info "Trae not detected - skipping Trae integration"
|
||
return
|
||
}
|
||
|
||
Write-Info "Found Trae installation"
|
||
|
||
# Check if MCP is already configured
|
||
if (Test-Path $traeConfigPath) {
|
||
try {
|
||
$settings = Get-Content $traeConfigPath -Raw | ConvertFrom-Json
|
||
if ($settings.mcpServers -and $settings.mcpServers.zen) {
|
||
Write-Success "Zen MCP already configured in Trae"
|
||
return
|
||
}
|
||
} catch {
|
||
Write-Warning "Could not read existing Trae configuration"
|
||
}
|
||
}
|
||
|
||
# Ask user if they want to configure Trae
|
||
Write-Host ""
|
||
$response = Read-Host "Configure Zen MCP for Trae? (y/N)"
|
||
if ($response -ne 'y' -and $response -ne 'Y') {
|
||
Write-Info "Skipping Trae integration"
|
||
return
|
||
}
|
||
|
||
try {
|
||
# Create config directory if it doesn't exist
|
||
$configDir = Split-Path $traeConfigPath -Parent
|
||
if (!(Test-Path $configDir)) {
|
||
New-Item -ItemType Directory -Path $configDir -Force | Out-Null
|
||
}
|
||
|
||
# Create backup with retention management
|
||
if (Test-Path $traeConfigPath) {
|
||
$backupPath = Manage-ConfigBackups $traeConfigPath
|
||
}
|
||
|
||
# Read existing config or create new one
|
||
$config = @{}
|
||
if (Test-Path $traeConfigPath) {
|
||
$config = Get-Content $traeConfigPath -Raw | ConvertFrom-Json
|
||
}
|
||
|
||
# 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 $traeConfigPath -Encoding UTF8
|
||
|
||
Write-Success "Successfully configured Trae"
|
||
Write-Host " Config: $traeConfigPath" -ForegroundColor Gray
|
||
Write-Host " Restart Trae to use Zen MCP Server" -ForegroundColor Gray
|
||
|
||
} catch {
|
||
Write-Error "Failed to update Trae configuration: $_"
|
||
Write-Host ""
|
||
Write-Host "Manual configuration for Trae:"
|
||
Write-Host "Location: $traeConfigPath"
|
||
Write-Host "Add this configuration:"
|
||
Write-Host @"
|
||
{
|
||
"mcpServers": {
|
||
"zen": {
|
||
"command": "$PythonPath",
|
||
"args": ["$ServerPath"]
|
||
}
|
||
}
|
||
}
|
||
"@ -ForegroundColor Yellow
|
||
}
|
||
}
|
||
|
||
# Check and update VSCode configuration
|
||
function Test-VSCodeIntegration {
|
||
param([string]$PythonPath, [string]$ServerPath)
|
||
|
||
Write-Step "Checking VSCode Integration"
|
||
|
||
# Check for VSCode installations
|
||
$vscodeVersions = @()
|
||
|
||
# VSCode standard
|
||
if (Test-Command "code") {
|
||
$vscodeVersions += @{
|
||
Name = "VSCode"
|
||
Command = "code"
|
||
UserPath = "$env:APPDATA\Code\User"
|
||
}
|
||
}
|
||
|
||
# VSCode Insiders
|
||
if (Test-Command "code-insiders") {
|
||
$vscodeVersions += @{
|
||
Name = "VSCode Insiders"
|
||
Command = "code-insiders"
|
||
UserPath = "$env:APPDATA\Code - Insiders\User"
|
||
}
|
||
}
|
||
|
||
if ($vscodeVersions.Count -eq 0) {
|
||
Write-Info "VSCode not detected - skipping VSCode integration"
|
||
return
|
||
}
|
||
|
||
foreach ($vscode in $vscodeVersions) {
|
||
Write-Info "Found $($vscode.Name)"
|
||
|
||
# Find settings.json files with modification dates
|
||
$settingsFiles = @()
|
||
$userPath = $vscode.UserPath
|
||
|
||
# Check default profile
|
||
$defaultSettings = Join-Path $userPath "settings.json"
|
||
if (Test-Path $defaultSettings) {
|
||
$lastWrite = (Get-Item $defaultSettings).LastWriteTime
|
||
$settingsFiles += @{
|
||
Path = $defaultSettings
|
||
ProfileName = "Default Profile"
|
||
LastModified = $lastWrite
|
||
}
|
||
}
|
||
|
||
# Check profiles directory
|
||
$profilesPath = Join-Path $userPath "profiles"
|
||
if (Test-Path $profilesPath) {
|
||
$profiles = Get-ChildItem $profilesPath -Directory
|
||
foreach ($profile in $profiles) {
|
||
$profileSettings = Join-Path $profile.FullName "settings.json"
|
||
if (Test-Path $profileSettings) {
|
||
$lastWrite = (Get-Item $profileSettings).LastWriteTime
|
||
$settingsFiles += @{
|
||
Path = $profileSettings
|
||
ProfileName = "Profile: $($profile.Name)"
|
||
LastModified = $lastWrite
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($settingsFiles.Count -eq 0) {
|
||
Write-Warning "No settings.json found for $($vscode.Name)"
|
||
continue
|
||
}
|
||
|
||
# Sort by last modified date (most recent first) and take only the most recent
|
||
$mostRecentProfile = $settingsFiles | Sort-Object LastModified -Descending | Select-Object -First 1
|
||
|
||
# Process only the most recent settings file
|
||
$settingsFile = $mostRecentProfile
|
||
$settingsPath = $settingsFile.Path
|
||
$profileName = $settingsFile.ProfileName
|
||
|
||
# Check if MCP is already configured
|
||
if (Test-Path $settingsPath) {
|
||
try {
|
||
$settings = Get-Content $settingsPath -Raw | ConvertFrom-Json
|
||
if ($settings.mcp -and $settings.mcp.servers -and $settings.mcp.servers.zen) {
|
||
Write-Success "Zen MCP already configured in $($vscode.Name)"
|
||
continue
|
||
}
|
||
} catch {
|
||
Write-Warning "Could not read existing settings for $($vscode.Name)"
|
||
}
|
||
}
|
||
|
||
# Ask user if they want to configure this VSCode instance
|
||
Write-Host ""
|
||
$response = Read-Host "Configure Zen MCP for $($vscode.Name)? (y/N)"
|
||
if ($response -ne 'y' -and $response -ne 'Y') {
|
||
Write-Info "Skipping $($vscode.Name)"
|
||
continue
|
||
}
|
||
|
||
try {
|
||
# Create backup with retention management
|
||
if (Test-Path $settingsPath) {
|
||
$backupPath = Manage-ConfigBackups $settingsPath
|
||
}
|
||
|
||
# Read existing settings as JSON string
|
||
$jsonContent = "{}"
|
||
if (Test-Path $settingsPath) {
|
||
$jsonContent = Get-Content $settingsPath -Raw
|
||
if (!$jsonContent.Trim()) {
|
||
$jsonContent = "{}"
|
||
}
|
||
} else {
|
||
# Create directory if it doesn't exist
|
||
$settingsDir = Split-Path $settingsPath -Parent
|
||
if (!(Test-Path $settingsDir)) {
|
||
New-Item -ItemType Directory -Path $settingsDir -Force | Out-Null
|
||
}
|
||
}
|
||
|
||
# Parse JSON
|
||
$settings = $jsonContent | ConvertFrom-Json
|
||
|
||
# Build zen configuration
|
||
$zenConfigObject = New-Object PSObject
|
||
$zenConfigObject | Add-Member -MemberType NoteProperty -Name "command" -Value $PythonPath
|
||
$zenConfigObject | Add-Member -MemberType NoteProperty -Name "args" -Value @($ServerPath)
|
||
|
||
# Build servers object
|
||
$serversObject = New-Object PSObject
|
||
$serversObject | Add-Member -MemberType NoteProperty -Name "zen" -Value $zenConfigObject
|
||
|
||
# Build mcp object
|
||
$mcpObject = New-Object PSObject
|
||
$mcpObject | Add-Member -MemberType NoteProperty -Name "servers" -Value $serversObject
|
||
|
||
# Add mcp to settings (replace if exists)
|
||
if ($settings.PSObject.Properties.Name -contains "mcp") {
|
||
$settings.mcp = $mcpObject
|
||
} else {
|
||
$settings | Add-Member -MemberType NoteProperty -Name "mcp" -Value $mcpObject
|
||
}
|
||
|
||
# Write updated settings
|
||
$settings | ConvertTo-Json -Depth 10 | Out-File $settingsPath -Encoding UTF8
|
||
|
||
Write-Success "Successfully configured $($vscode.Name)"
|
||
Write-Host " Config: $settingsPath" -ForegroundColor Gray
|
||
Write-Host " Restart $($vscode.Name) to use Zen MCP Server" -ForegroundColor Gray
|
||
|
||
} catch {
|
||
Write-Error "Failed to update $($vscode.Name) settings: $_"
|
||
Write-Host ""
|
||
Write-Host "Manual configuration for $($vscode.Name):"
|
||
Write-Host "Location: $settingsPath"
|
||
Write-Host "Add this to your settings.json:"
|
||
Write-Host @"
|
||
{
|
||
"mcp": {
|
||
"servers": {
|
||
"zen": {
|
||
"command": "$PythonPath",
|
||
"args": ["$ServerPath"]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
"@ -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. For VSCode:"
|
||
Write-Host " Add this configuration to your VSCode settings.json:"
|
||
Write-Host " Location: $env:APPDATA\Code\User\<profile-id>\settings.json"
|
||
Write-Host ""
|
||
|
||
$vscodeConfigJson = @{
|
||
mcp = @{
|
||
servers = @{
|
||
zen = @{
|
||
command = $PythonPath
|
||
args = @($ServerPath)
|
||
}
|
||
}
|
||
}
|
||
} | ConvertTo-Json -Depth 5
|
||
|
||
Write-Host $vscodeConfigJson -ForegroundColor Yellow
|
||
Write-Host ""
|
||
|
||
Write-Info "4. For Cursor:"
|
||
Write-Host " Add this configuration to your Cursor config file:"
|
||
Write-Host " Location: $env:USERPROFILE\.cursor\mcp.json"
|
||
Write-Host ""
|
||
|
||
$cursorConfigJson = @{
|
||
mcpServers = @{
|
||
zen = @{
|
||
command = $PythonPath
|
||
args = @($ServerPath)
|
||
}
|
||
}
|
||
} | ConvertTo-Json -Depth 5
|
||
|
||
Write-Host $cursorConfigJson -ForegroundColor Yellow
|
||
Write-Host ""
|
||
|
||
Write-Info "5. For Trae:"
|
||
Write-Host " Add this configuration to your Trae config file:"
|
||
Write-Host " Location: $env:APPDATA\Trae\Users\mcp.json"
|
||
Write-Host ""
|
||
|
||
$traeConfigJson = @{
|
||
mcpServers = @{
|
||
zen = @{
|
||
command = $PythonPath
|
||
args = @($ServerPath)
|
||
}
|
||
}
|
||
} | ConvertTo-Json -Depth 5
|
||
|
||
Write-Host $traeConfigJson -ForegroundColor Yellow
|
||
Write-Host ""
|
||
|
||
Write-Info "6. For Windsurf:"
|
||
Write-Host " Add this configuration to your Windsurf config file:"
|
||
Write-Host " Location: $env:USERPROFILE\.codeium\windsurf\mcp_config.json"
|
||
Write-Host ""
|
||
|
||
$windsurfConfigJson = @{
|
||
mcpServers = @{
|
||
zen = @{
|
||
command = $PythonPath
|
||
args = @($ServerPath)
|
||
}
|
||
}
|
||
} | ConvertTo-Json -Depth 5
|
||
|
||
Write-Host $windsurfConfigJson -ForegroundColor Yellow
|
||
Write-Host ""
|
||
|
||
Write-Info "7. Restart Claude Desktop, Gemini CLI, VSCode, Cursor, Windsurf, or Trae 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 = Get-AbsolutePath "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 = Get-AbsolutePath "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 VSCode integration
|
||
Test-VSCodeIntegration $pythonPath $serverPath
|
||
|
||
# Step 11: Check Cursor integration
|
||
Test-CursorIntegration $pythonPath $serverPath
|
||
|
||
# Step 12: Check Windsurf integration
|
||
Test-WindsurfIntegration $pythonPath $serverPath
|
||
|
||
# Step 13: Check Trae integration
|
||
Test-TraeIntegration $pythonPath $serverPath
|
||
|
||
# Step 14: Check Gemini CLI integration
|
||
Test-GeminiCliIntegration (Split-Path $serverPath -Parent)
|
||
|
||
# Step 15: Setup logging directory
|
||
Initialize-Logging
|
||
|
||
# Step 16: Display log information
|
||
Write-Host ""
|
||
Write-Host "Logs will be written to: $(Get-AbsolutePath $LOG_DIR)\$LOG_FILE"
|
||
Write-Host ""
|
||
|
||
# Step 17: 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
|