diff --git a/code_quality_checks.ps1 b/code_quality_checks.ps1 index 2b15d65..73e3dcc 100644 --- a/code_quality_checks.ps1 +++ b/code_quality_checks.ps1 @@ -1,4 +1,33 @@ -#!/usr/bin/env pwsh +<# +.SYNOPSIS + Code quality checks script for Zen MCP server on Windows. + +.DESCRIPTION + This PowerShell script performs code quality checks for the Zen MCP server project: + - Runs static analysis and linting tools on the codebase + - Ensures code style compliance and detects potential issues + - Can be integrated into CI/CD pipelines or used locally before commits + +.PARAMETER Help + Displays help information for using the script. + +.PARAMETER Verbose + Enables detailed output during code quality checks. + +.EXAMPLE + .\code_quality_checks.ps1 + Runs all code quality checks on the project. + + .\code_quality_checks.ps1 -Verbose + Runs code quality checks with detailed output. + +.NOTES + Project Author : BeehiveInnovations + Script Author : GiGiDKR (https://github.com/GiGiDKR) + Date : 07-05-2025 + Version : See project documentation + References : https://github.com/BeehiveInnovations/zen-mcp-server +#> #Requires -Version 5.1 [CmdletBinding()] param( diff --git a/patch/patch_crossplatform.py b/patch/patch_crossplatform.py index cd28b69..4afe9ea 100644 --- a/patch/patch_crossplatform.py +++ b/patch/patch_crossplatform.py @@ -50,12 +50,22 @@ FIXED ISSUES: add Windows-specific path detection 10. WINDOWS PATH VALIDATION: - - Some path validation logic did not handle Windows/Unix cross-compatibility + - Some path validation logic did not handle Windows/Unix + cross-compatibility - Solution: Improved path validation to support both Windows and Unix absolute paths for tests +11. DOCKER PATH VALIDATION: + - Docker paths were not recognized as absolute on Windows + - Solution: Added Docker path validation in file_utils.py + +12. DOCKER MODE COMPATIBILITY IN TESTS: + - Conversation tests failed when MCP_FILE_PATH_MODE=docker due to path + conversion + - Solution: Force local mode in tests and reset PathModeDetector cache + MODIFIED FILES: -- utils/file_utils.py : Home patterns + Unix path validation +- utils/file_utils.py : Home patterns + Unix path validation + Docker support - tests/test_file_protection.py : Cross-platform assertions - tests/test_utils.py : Safe_files test with temporary file - run_integration_tests.sh : Windows venv detection @@ -63,6 +73,7 @@ MODIFIED FILES: - communication_simulator_test.py : Logger initialization order + Windows paths - simulator_tests/base_test.py : Logger initialization order + Windows paths - tools/shared/base_tool.py : Logger initialization order + Windows paths +- tests/test_conversation_file_features.py : Docker mode compatibility Usage: python patch_crossplatform.py [--dry-run] [--backup] [--validate-only] @@ -91,13 +102,14 @@ class CrossPlatformPatcher: """Find all files to patch.""" files = { "file_utils": self.workspace_root / "utils" / "file_utils.py", - "test_file_protection": self.workspace_root / "tests" / "test_file_protection.py", + "test_file_protection": (self.workspace_root / "tests" / "test_file_protection.py"), "test_utils": self.workspace_root / "tests" / "test_utils.py", - "run_integration_tests_sh": self.workspace_root / "run_integration_tests.sh", - "code_quality_checks_sh": self.workspace_root / "code_quality_checks.sh", - "communication_simulator": self.workspace_root / "communication_simulator_test.py", - "base_test": self.workspace_root / "simulator_tests" / "base_test.py", - "base_tool": self.workspace_root / "tools" / "shared" / "base_tool.py", + "run_integration_tests_sh": (self.workspace_root / "run_integration_tests.sh"), + "code_quality_checks_sh": (self.workspace_root / "code_quality_checks.sh"), + "communication_simulator": (self.workspace_root / "communication_simulator_test.py"), + "base_test": (self.workspace_root / "simulator_tests" / "base_test.py"), + "base_tool": (self.workspace_root / "tools" / "shared" / "base_tool.py"), + "test_conversation_features": (self.workspace_root / "tests" / "test_conversation_file_features.py"), } for _, path in files.items(): @@ -177,7 +189,9 @@ class CrossPlatformPatcher: # Get the part after the pattern after_pattern = parts[1] # Check if we're at the user's root (no subdirectories) - if "/" not in after_pattern and "\\\\" not in after_pattern: + if ( + "/" not in after_pattern and "\\\\" not in after_pattern + ): logger.warning( f"Attempted to scan user home directory root: {path}. " f"Please specify a subdirectory instead." @@ -230,7 +244,10 @@ class CrossPlatformPatcher: old_validation = """ # Step 2: Security Policy - Require absolute paths # Relative paths could be interpreted differently depending on working directory if not user_path.is_absolute(): - raise ValueError(f"Relative paths are not supported. Please provide an absolute path.\\nReceived: {path_str}")""" + raise ValueError( + f"Relative paths are not supported. Please provide an absolute path.\\n" + f"Received: {path_str}" + )""" new_validation = """ # Step 2: Security Policy - Require absolute paths # Relative paths could be interpreted differently depending on working directory @@ -245,7 +262,10 @@ class CrossPlatformPatcher: is_absolute_path = path_str_normalized.startswith('/') if not is_absolute_path: - raise ValueError(f"Relative paths are not supported. Please provide an absolute path.\\nReceived: {path_str}")""" + raise ValueError( + f"Relative paths are not supported. Please provide an absolute path.\\n" + f"Received: {path_str}" + )""" if old_validation in content: content = content.replace(old_validation, new_validation) @@ -487,7 +507,10 @@ fi""" # Configure logging first log_level = logging.DEBUG if verbose else logging.INFO - logging.basicConfig(level=log_level, format="%(asctime)s - %(levelname)s - %(message)s") + logging.basicConfig( + level=log_level, + format="%(asctime)s - %(levelname)s - %(message)s" + ) self.logger = logging.getLogger(__name__)""" new_init_order = """ self.verbose = verbose @@ -695,11 +718,16 @@ fi""" continue # Handle both single paths and lists of paths - paths_to_check = field_value if isinstance(field_value, list) else [field_value] + paths_to_check = ( + field_value if isinstance(field_value, list) else [field_value] + ) for path in paths_to_check: if path and not os.path.isabs(path): - return f"All file paths must be FULL absolute paths. Invalid path: '{path}'" + return ( + "All file paths must be FULL absolute paths. " + f"Invalid path: '{path}'" + ) return None''' @@ -793,11 +821,16 @@ fi""" if os.name == "nt": # Windows absolute paths should start with drive letter or UNC path has_drive = ( - len(normalized_path) >= 3 and normalized_path[1:3] in (":\\\\", ":/") and normalized_path[0].isalpha() + ( + len(normalized_path) >= 3 + and normalized_path[1:3] in (":\\\\", ":/") + and normalized_path[0].isalpha() + ) ) has_unc = normalized_path.startswith(("\\\\\\\\", "//")) - # Also accept Unix-style absolute paths (starting with /) for cross-platform compatibility + # Also accept Unix-style absolute paths (starting with /) for + # cross-platform compatibility has_unix_root = normalized_path.startswith("/") result = (is_abs_os or is_abs_path) and (has_drive or has_unc or has_unix_root) @@ -841,6 +874,186 @@ fi""" return content, False + def patch_docker_path_validation(self, content: str) -> tuple[str, bool]: + """Patch 14: Add Docker path validation support in file_utils.py.""" + # Check if already patched + if "is_docker_path = converted_path_str.startswith" in content: + return content, False + + # Add import for path_detector if not present + if "from utils.path_detector import get_path_detector" not in content: + # Find the imports section and add the import + import_section = "import tempfile\nfrom pathlib import Path" + if import_section in content: + new_import = import_section + "\n\nfrom utils.path_detector import get_path_detector" + content = content.replace(import_section, new_import) + + # Replace the path validation logic + old_validation = """ # Step 2: Security Policy - Require absolute paths + # Relative paths could be interpreted differently depending on working directory + # Handle cross-platform path format compatibility for testing + is_absolute_path = user_path.is_absolute() + + # On Windows, also accept Unix-style absolute paths for cross-platform testing + # This allows paths like "/etc/passwd" to be treated as absolute + import os + if os.name == 'nt' and not is_absolute_path: + path_str_normalized = path_str.replace('\\\\', '/') + is_absolute_path = path_str_normalized.startswith('/') + + if not is_absolute_path: + raise ValueError( + "Relative paths are not supported. Please provide an absolute path.\\n" + f"Received: {path_str}" + )""" + + new_validation = """ # Step 1.5: Convert path according to current mode (Docker vs local) + path_detector = get_path_detector() + converted_path_str = path_detector.convert_path(path_str) + user_path = Path(converted_path_str) + + # Step 2: Security Policy - Require absolute paths + # Relative paths could be interpreted differently depending on working directory + # Handle cross-platform path format compatibility for testing + is_absolute_path = user_path.is_absolute() + + # Special handling for Docker mode paths + is_docker_path = converted_path_str.startswith("/app/project/") + is_docker_mode = ( + hasattr(path_detector, 'get_path_mode') and + path_detector.get_path_mode() == "docker" + ) + + # On Windows, also accept Unix-style absolute paths for cross-platform testing + # This allows paths like "/etc/passwd" to be treated as absolute + # Also accept Docker paths when in Docker mode + import os + if os.name == 'nt' and not is_absolute_path: + path_str_normalized = path_str.replace('\\\\', '/') + is_absolute_path = ( + path_str_normalized.startswith('/') or + (is_docker_mode and is_docker_path) + ) + + if not is_absolute_path and not (is_docker_mode and is_docker_path): + raise ValueError( + f"Relative paths are not supported. Please provide an absolute path.\\n" + f"Received: {path_str}" + )""" + + if old_validation in content: + content = content.replace(old_validation, new_validation) + return content, True + + return content, False + + def patch_conversation_test_docker_mode(self, content: str) -> tuple[str, bool]: + """Patch 15: Fix conversation file features test for Docker mode compatibility.""" + # Check if already patched + if '@patch("utils.file_utils.resolve_and_validate_path")' in content and "MCP_FILE_PATH_MODE" in content: + return content, False + + modified = False + + # 1. Update the test method decorator to include MCP_FILE_PATH_MODE and add mock + old_decorator = """ @patch.dict( + os.environ, {"GEMINI_API_KEY": "test-key", "OPENAI_API_KEY": "", "MCP_FILE_PATH_MODE": "local"}, clear=False + ) + def test_build_conversation_history_with_file_content(self, project_path):""" + + new_decorator = """ @patch.dict( + os.environ, {"GEMINI_API_KEY": "test-key", "OPENAI_API_KEY": "", "MCP_FILE_PATH_MODE": "local"}, clear=False + ) + @patch("utils.file_utils.resolve_and_validate_path") + def test_build_conversation_history_with_file_content(self, mock_resolve_path, project_path):""" + + if old_decorator in content: + content = content.replace(old_decorator, new_decorator) + modified = True + + # 2. Add the mock setup to return Path objects for the resolve_and_validate_path + old_test_start = ''' """Test that conversation history includes embedded file content""" + from providers.registry import ModelProviderRegistry + + ModelProviderRegistry.clear_cache() + + # Reset PathModeDetector singleton and cache to ensure local mode + from utils.path_detector import PathModeDetector + + PathModeDetector._instance = None + PathModeDetector._cached_mode = None + + from utils.path_detector import get_path_detector + + detector = get_path_detector() + detector._cached_mode = None + + # Force path mode to local and verify + mode = detector.get_path_mode() + assert mode == "local", f"Expected local mode, got {mode}" + + # Create test file with known content in a project-like structure''' + + new_test_start = ''' """Test that conversation history includes embedded file content""" + from providers.registry import ModelProviderRegistry + + ModelProviderRegistry.clear_cache() + + # Create test file with known content in a project-like structure''' + + if old_test_start in content: + content = content.replace(old_test_start, new_test_start) + modified = True + + # 3. Add the mock setup after the test file creation + old_file_creation = ''' project_subdir = os.path.join(project_path, "zen-mcp-server") + os.makedirs(project_subdir, exist_ok=True) + test_file = os.path.join(project_subdir, "test.py") + test_content = "# Test file\\ndef hello():\\n print('Hello, world!')\\n" + with open(test_file, "w") as f: + f.write(test_content) + + # Debug output for troubleshooting + print(f"DEBUG: Test file path: {test_file}") + print(f"DEBUG: File exists: {os.path.exists(test_file)}") + print(f"DEBUG: Path mode: {detector.get_path_mode()}") + print(f"DEBUG: Converted path: {detector.convert_path(test_file)}") + + # Verify the converted path matches original for local mode + converted_path = detector.convert_path(test_file) + assert converted_path == test_file, f"Path conversion failed: {test_file} -> {converted_path}"''' + + new_file_creation = """ # Mock resolve_and_validate_path to return a Path object (simulating local mode) + from pathlib import Path + + mock_resolve_path.side_effect = lambda path: Path(path) + project_subdir = os.path.join(project_path, "zen-mcp-server") + os.makedirs(project_subdir, exist_ok=True) + test_file = os.path.join(project_subdir, "test.py") + test_content = "# Test file\\ndef hello():\\n print('Hello, world!')\\n" + with open(test_file, "w") as f: + f.write(test_content)""" + + if old_file_creation in content: + content = content.replace(old_file_creation, new_file_creation) + modified = True + + # Fix the test file creation to use zen-mcp-server subdirectory + # This is already correct, check for alternate pattern + alt_file_creation = """ # Create a test file + test_file = os.path.join(project_path, "test.py")""" + + new_file_creation = """ # Create a test file in zen-mcp-server subdirectory for proper path detection + project_subdir = os.path.join(project_path, "zen-mcp-server") + os.makedirs(project_subdir, exist_ok=True) + test_file = os.path.join(project_subdir, "test.py")""" + + if alt_file_creation in content: + content = content.replace(alt_file_creation, new_file_creation) + modified = True + + return content, modified + def apply_all_patches(self, files: dict[str, Path], create_backups: bool = False) -> bool: """Apply all necessary patches.""" all_success = True @@ -1011,6 +1224,41 @@ fi""" else: print(" โ„น๏ธ tools/shared/base_tool.py already patched") + # Patch 14: Enhanced Docker path validation in utils/file_utils.py + print("\n๐Ÿ”ง Applying Docker path validation patch to utils/file_utils.py...") + + file_utils_content = self.read_file(files["file_utils"]) + file_utils_content, modified14 = self.patch_docker_path_validation(file_utils_content) + + if modified14: + if create_backups: + backup = self.create_backup(files["file_utils"]) + print(f" โœ… Backup created: {backup}") + + self.write_file(files["file_utils"], file_utils_content) + print(" โœ… Docker path validation support added") + self.patches_applied.append("Docker path validation (file_utils.py)") + else: + print(" โ„น๏ธ Docker path validation already patched in file_utils.py") + + # Patch 15: Conversation test Docker mode compatibility + print("\n๐Ÿ”ง Patching tests/test_conversation_file_features.py...") + + if "test_conversation_features" in files: + conversation_content = self.read_file(files["test_conversation_features"]) + conversation_content, modified15 = self.patch_conversation_test_docker_mode(conversation_content) + + if modified15: + if create_backups: + backup = self.create_backup(files["test_conversation_features"]) + print(f" โœ… Backup created: {backup}") + + self.write_file(files["test_conversation_features"], conversation_content) + print(" โœ… Docker mode compatibility added to conversation tests") + self.patches_applied.append("Docker mode compatibility (test_conversation_file_features.py)") + else: + print(" โ„น๏ธ tests/test_conversation_file_features.py already patched") + return all_success def validate_patches(self, files: dict[str, Path]) -> list[str]: @@ -1089,6 +1337,29 @@ fi""" if "has_unix_root = normalized_path.startswith" not in base_tool_content: errors.append("Enhanced Windows path validation missing in base_tool.py") + # Validate Docker path validation in utils/file_utils.py + if "from utils.path_detector import get_path_detector" not in file_utils_content: + errors.append("Path detector import missing in file_utils.py") + + if "is_docker_path = converted_path_str.startswith" not in file_utils_content: + errors.append("Docker path validation missing in file_utils.py") + + if "is_docker_mode = " not in file_utils_content: + errors.append("Docker mode detection missing in file_utils.py") + + # Validate conversation test patches + if "test_conversation_features" in files: + conversation_content = self.read_file(files["test_conversation_features"]) + + if '"MCP_FILE_PATH_MODE": "local"' not in conversation_content: + errors.append("Local mode forcing missing in test_conversation_file_features.py") + + if "PathModeDetector._instance = None" not in conversation_content: + errors.append("PathModeDetector reset missing in test_conversation_file_features.py") + + if "detector._cached_mode = None" not in conversation_content: + errors.append("PathModeDetector cache reset missing in test_conversation_file_features.py") + return errors def show_diff_summary(self, files: dict[str, Path]) -> None: @@ -1149,6 +1420,24 @@ fi""" "Cross-platform compatibility for Unix-style paths", ], ), + ( + "utils/file_utils.py (Docker support)", + [ + "Add path_detector import for Docker path conversion", + "Support Docker path validation (/app/project/...)", + "Accept Docker paths as valid when in Docker mode", + "Enhanced cross-platform path handling", + ], + ), + ( + "tests/test_conversation_file_features.py", + [ + "Force local mode in conversation tests", + "Reset PathModeDetector singleton and cache", + "Create test files in zen-mcp-server subdirectory", + "Add debug output for troubleshooting", + ], + ), ] for filename, changes in modifications: diff --git a/patch/validation_crossplatform.py b/patch/validation_crossplatform.py index 39cb1a8..df58a66 100644 --- a/patch/validation_crossplatform.py +++ b/patch/validation_crossplatform.py @@ -12,6 +12,8 @@ work correctly on Windows, including: 5. Communication simulator logger and Python path fixes 6. BaseSimulatorTest logger and Python path fixes 7. Shell scripts Windows virtual environment support +8. Docker path validation and mode compatibility +9. Conversation tests Docker mode compatibility Tests cover all modified files: - utils/file_utils.py @@ -21,8 +23,10 @@ Tests cover all modified files: - simulator_tests/base_test.py - run_integration_tests.sh - code_quality_checks.sh +- tests/test_conversation_file_features.py (Docker mode) """ +import os import sys import tempfile from pathlib import Path @@ -376,6 +380,163 @@ def test_shell_scripts_windows_support(): return False +def test_docker_path_validation(): + """Test 8: Docker path validation in file_utils.py.""" + print("\n๐Ÿงช Test 8: Docker path validation") + print("-" * 60) + + try: + # Test that path_detector import is available + try: + from utils.path_detector import PathModeDetector + + detector_import = True + print(" โœ… Path detector import: True") + except ImportError as e: + detector_import = False + print(f" โŒ Path detector import: False ({e})") + + # Test Docker path validation with mocked Docker mode + try: + # Mock Docker mode + with patch.dict(os.environ, {"MCP_FILE_PATH_MODE": "docker"}): + # Reset singleton to pick up new environment + PathModeDetector._instance = None + + # Test Docker path validation + docker_path = "/app/project/test.py" + try: + from utils.file_utils import resolve_and_validate_path + + resolve_and_validate_path(docker_path) + docker_validation = True + print(" โœ… Docker path validation: True") + except ValueError as e: + if "Relative paths are not supported" in str(e): + docker_validation = False + print(" โŒ Docker path validation: False (still rejected)") + else: + docker_validation = True # Different error, not path format + print(" โœ… Docker path validation: True (security error)") + except Exception as e: + docker_validation = False + print(f" โŒ Docker path validation: Error ({e})") + + # Reset singleton after test + PathModeDetector._instance = None + + except Exception as e: + docker_validation = False + print(f" โŒ Docker path test error: {e}") + + # Test that file_utils.py contains Docker-related code + try: + with open("utils/file_utils.py", encoding="utf-8") as f: + file_utils_content = f.read() + + has_detector_import = "from utils.path_detector import get_path_detector" in file_utils_content + has_docker_check = "is_docker_path = converted_path_str.startswith" in file_utils_content + has_docker_mode = "is_docker_mode" in file_utils_content + + print(f" โœ… Has detector import: {has_detector_import}") + print(f" โœ… Has Docker path check: {has_docker_check}") + print(f" โœ… Has Docker mode check: {has_docker_mode}") + + file_content_ok = has_detector_import and has_docker_check and has_docker_mode + + except FileNotFoundError: + file_content_ok = False + print(" โŒ utils/file_utils.py not found") + + success = detector_import and docker_validation and file_content_ok + print(f"\nResult: Docker path validation {'passed' if success else 'failed'}") + + return success + + except Exception as e: + print(f" โŒ Error testing Docker path validation: {e}") + print("\nResult: Docker path validation failed") + return False + + +def test_conversation_docker_compatibility(): + """Test 9: Conversation tests Docker mode compatibility.""" + print("\n๐Ÿงช Test 9: Conversation tests Docker mode compatibility") + print("-" * 60) + + try: + # Test that test_conversation_file_features.py contains Docker fixes + try: + with open("tests/test_conversation_file_features.py", encoding="utf-8") as f: + test_content = f.read() + + has_local_mode = '"MCP_FILE_PATH_MODE": "local"' in test_content + has_detector_reset = "PathModeDetector._instance = None" in test_content + has_cache_reset = "detector._cached_mode = None" in test_content + has_subdir = "zen-mcp-server" in test_content + + print(f" โœ… Forces local mode: {has_local_mode}") + print(f" โœ… Resets PathModeDetector: {has_detector_reset}") + print(f" โœ… Resets detector cache: {has_cache_reset}") + print(f" โœ… Uses project subdirectory: {has_subdir}") + + test_content_ok = has_local_mode and has_detector_reset and has_cache_reset + + except FileNotFoundError: + test_content_ok = False + print(" โŒ tests/test_conversation_file_features.py not found") + + # Test PathModeDetector cache reset functionality + try: + from utils.path_detector import PathModeDetector, get_path_detector + + # Test that we can reset the singleton + detector1 = get_path_detector() + detector1.get_path_mode() + + # Reset and test again + PathModeDetector._instance = None + detector2 = get_path_detector() + + # Test cache reset + detector2._cached_mode = None + detector2.get_path_mode() + + reset_works = True + print(" โœ… PathModeDetector reset: True") + + except Exception as e: + reset_works = False + print(f" โŒ PathModeDetector reset: False ({e})") + + # Test environment patching works + try: + from utils.path_detector import get_path_detector + + with patch.dict(os.environ, {"MCP_FILE_PATH_MODE": "local"}): + PathModeDetector._instance = None + detector = get_path_detector() + detector._cached_mode = None + mode = detector.get_path_mode() + + env_patch_works = mode == "local" + print(f" โœ… Environment patching: {env_patch_works}") + + except Exception as e: + env_patch_works = False + print(f" โŒ Environment patching: False ({e})") + + success = test_content_ok and reset_works and env_patch_works + print(f"\nResult: Conversation Docker compatibility {'passed' if success else 'failed'}") + + return success + + except Exception as e: + print(f" โŒ Error testing conversation Docker compatibility: {e}") + print("\nResult: Conversation Docker compatibility failed") + return False + + def main(): """Main validation function.""" print("๐Ÿ”ง Final validation of cross-platform fixes") @@ -393,6 +554,8 @@ def main(): results.append(("Communication simulator", test_communication_simulator_fixes())) results.append(("BaseSimulatorTest", test_base_simulator_test_fixes())) results.append(("Shell scripts Windows support", test_shell_scripts_windows_support())) + results.append(("Docker path validation", test_docker_path_validation())) + results.append(("Conversation Docker compatibility", test_conversation_docker_compatibility())) # Final summary print("\n" + "=" * 70) diff --git a/run-server.ps1 b/run-server.ps1 index 80fd2c0..50ed03b 100644 --- a/run-server.ps1 +++ b/run-server.ps1 @@ -1,4 +1,63 @@ -#!/usr/bin/env pwsh +<# +.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( @@ -126,6 +185,55 @@ function Remove-LockedDirectory { } } +# 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 { @@ -160,6 +268,19 @@ function Clear-PythonCache { } } +# 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) @@ -356,7 +477,7 @@ function Initialize-Environment { Write-Success "Virtual environment already exists" $pythonPath = "$VENV_PATH\Scripts\python.exe" if (Test-Path $pythonPath) { - return $pythonPath + return Get-AbsolutePath $pythonPath } } } @@ -365,15 +486,8 @@ function Initialize-Environment { Write-Info "Creating virtual environment with uv..." uv venv $VENV_PATH --python 3.12 if ($LASTEXITCODE -eq 0) { - # Install pip in the uv environment for compatibility - Write-Info "Installing pip in uv environment..." - uv pip install --python "$VENV_PATH\Scripts\python.exe" pip - if ($LASTEXITCODE -eq 0) { - Write-Success "Environment created with uv (pip installed)" - } else { - Write-Success "Environment created with uv" - } - return "$VENV_PATH\Scripts\python.exe" + Write-Success "Environment created with uv" + return Get-AbsolutePath "$VENV_PATH\Scripts\python.exe" } } catch { Write-Warning "uv failed, falling back to venv" @@ -415,7 +529,7 @@ function Initialize-Environment { } } else { Write-Success "Virtual environment already exists" - return "$VENV_PATH\Scripts\python.exe" + return Get-AbsolutePath "$VENV_PATH\Scripts\python.exe" } } @@ -431,7 +545,7 @@ function Initialize-Environment { } Write-Success "Virtual environment created" - return "$VENV_PATH\Scripts\python.exe" + return Get-AbsolutePath "$VENV_PATH\Scripts\python.exe" } # Setup virtual environment (legacy function for compatibility) @@ -553,18 +667,8 @@ function Install-Dependencies { if (Test-Uv) { Write-Info "Installing dependencies with uv..." try { - # Install in the virtual environment - uv pip install --python "$VENV_PATH\Scripts\python.exe" -r requirements.txt + uv pip install -r requirements.txt if ($LASTEXITCODE -eq 0) { - # Also install dev dependencies if available - if (Test-Path "requirements-dev.txt") { - uv pip install --python "$VENV_PATH\Scripts\python.exe" -r requirements-dev.txt - if ($LASTEXITCODE -eq 0) { - Write-Success "Development dependencies installed with uv" - } else { - Write-Warning "Failed to install dev dependencies with uv, continuing..." - } - } Write-Success "Dependencies installed with uv" return } @@ -678,8 +782,8 @@ function Test-ClaudeDesktopIntegration { } Write-Host "" - $response = Read-Host "Configure Zen for Claude Desktop? (Y/n)" - if ($response -eq 'n' -or $response -eq 'N') { + $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 @@ -698,9 +802,8 @@ function Test-ClaudeDesktopIntegration { 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 + # Create backup with retention management + $backupPath = Manage-ConfigBackups $claudeConfigPath # Read existing config $existingContent = Get-Content $claudeConfigPath -Raw @@ -804,8 +907,8 @@ function Test-GeminiCliIntegration { # 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') { + $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 } @@ -821,7 +924,7 @@ if exist ".zen_venv\Scripts\python.exe" ( ) else ( python server.py %* ) -"@ | Out-File -FilePath $zenWrapper -Encoding UTF8 +"@ | Out-File -FilePath $zenWrapper -Encoding ASCII Write-Success "Created zen-mcp-server.cmd wrapper script" } @@ -830,9 +933,8 @@ if exist ".zen_venv\Scripts\python.exe" ( Write-Info "Updating Gemini CLI configuration..." try { - # Create backup - $backupPath = "$geminiConfig.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')" - Copy-Item $geminiConfig $backupPath -ErrorAction SilentlyContinue + # Create backup with retention management + $backupPath = Manage-ConfigBackups $geminiConfig # Read existing config or create new one $config = @{} @@ -876,6 +978,464 @@ if exist ".zen_venv\Scripts\python.exe" ( } } +# 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) @@ -924,7 +1484,77 @@ function Show-ConfigInstructions { Write-Host $geminiConfigJson -ForegroundColor Yellow Write-Host "" - Write-Info "3. Restart Claude Desktop or Gemini CLI after updating the config files" + Write-Info "3. For VSCode:" + Write-Host " Add this configuration to your VSCode settings.json:" + Write-Host " Location: $env:APPDATA\Code\User\\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 "" @@ -1119,7 +1749,7 @@ function Start-MainProcess { Write-Host "" try { $pythonPath = Initialize-Environment - $serverPath = Resolve-Path "server.py" + $serverPath = Get-AbsolutePath "server.py" Show-ConfigInstructions $pythonPath $serverPath } catch { Write-Error "Failed to setup environment: $_" @@ -1174,7 +1804,7 @@ function Start-MainProcess { } # Step 7: Get absolute server path - $serverPath = Resolve-Path "server.py" + $serverPath = Get-AbsolutePath "server.py" # Step 8: Display setup instructions Show-SetupInstructions $pythonPath $serverPath @@ -1183,18 +1813,30 @@ function Start-MainProcess { Test-ClaudeCliIntegration $pythonPath $serverPath Test-ClaudeDesktopIntegration $pythonPath $serverPath - # Step 10: Check Gemini CLI integration + # 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 11: Setup logging directory + # Step 15: Setup logging directory Initialize-Logging - # Step 12: Display log information + # Step 16: Display log information Write-Host "" - Write-Host "Logs will be written to: $(Resolve-Path $LOG_DIR)\$LOG_FILE" + Write-Host "Logs will be written to: $(Get-AbsolutePath $LOG_DIR)\$LOG_FILE" Write-Host "" - # Step 12: Handle command line arguments + # Step 17: Handle command line arguments if ($Follow) { Follow-Logs } else { diff --git a/run_integration_tests.ps1 b/run_integration_tests.ps1 index 3519902..539fd0a 100644 --- a/run_integration_tests.ps1 +++ b/run_integration_tests.ps1 @@ -1,4 +1,32 @@ -#!/usr/bin/env pwsh +<# +.SYNOPSIS + Integration test runner script for the Zen MCP server on Windows. + +.DESCRIPTION + This PowerShell script prepares and runs integration tests for the Zen MCP server: + - Sets up the test environment + - Installs required dependencies + - Runs automated integration tests + - Displays test results and related logs + - Allows output customization via parameters (e.g., display color) + +.PARAMETER Color + Sets the display color for console messages (default: White). + +.EXAMPLE + .\run_integration_tests.ps1 + Prepares the environment and runs all integration tests. + + .\run_integration_tests.ps1 -Color Cyan + Runs the tests with messages displayed in cyan. + +.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(