Migration from Docker to Standalone Python Server (#73)

* Migration from docker to standalone server
Migration handling
Fixed tests
Use simpler in-memory storage
Support for concurrent logging to disk
Simplified direct connections to localhost

* Migration from docker / redis to standalone script
Updated tests
Updated run script
Fixed requirements
Use dotenv
Ask if user would like to install MCP in Claude Desktop once
Updated docs

* More cleanup and references to docker removed

* Cleanup

* Comments

* Fixed tests

* Fix GitHub Actions workflow for standalone Python architecture

- Install requirements-dev.txt for pytest and testing dependencies
- Remove Docker setup from simulation tests (now standalone)
- Simplify linting job to use requirements-dev.txt
- Update simulation tests to run directly without Docker

Fixes unit test failures in CI due to missing pytest dependency.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Remove simulation tests from GitHub Actions

- Removed simulation-tests job that makes real API calls
- Keep only unit tests (mocked, no API costs) and linting
- Simulation tests should be run manually with real API keys
- Reduces CI costs and complexity

GitHub Actions now only runs:
- Unit tests (569 tests, all mocked)
- Code quality checks (ruff, black)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fixed tests

* Fixed tests

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Beehive Innovations
2025-06-18 23:41:22 +04:00
committed by GitHub
parent 9d72545ecd
commit 4151c3c3a5
121 changed files with 2842 additions and 3168 deletions

View File

@@ -11,6 +11,8 @@ import os
import subprocess
from typing import Optional
from .log_utils import LogUtils
class BaseSimulatorTest:
"""Base class for all communication simulator tests"""
@@ -19,14 +21,25 @@ class BaseSimulatorTest:
self.verbose = verbose
self.test_files = {}
self.test_dir = None
self.container_name = "zen-mcp-server"
self.redis_container = "zen-mcp-redis"
self.python_path = self._get_python_path()
# Configure logging
log_level = logging.DEBUG if verbose else logging.INFO
logging.basicConfig(level=log_level, format="%(asctime)s - %(levelname)s - %(message)s")
self.logger = logging.getLogger(self.__class__.__name__)
def _get_python_path(self) -> str:
"""Get the Python path for the virtual environment"""
current_dir = os.getcwd()
venv_python = os.path.join(current_dir, ".zen_venv", "bin", "python")
if os.path.exists(venv_python):
return venv_python
# Fallback to system python if venv doesn't exist
self.logger.warning("Virtual environment not found, using system python")
return "python"
def setup_test_files(self):
"""Create test files for the simulation"""
# Test Python file
@@ -100,7 +113,7 @@ class Calculator:
self.logger.debug(f"Created test files with absolute paths: {list(self.test_files.values())}")
def call_mcp_tool(self, tool_name: str, params: dict) -> tuple[Optional[str], Optional[str]]:
"""Call an MCP tool via Claude CLI (docker exec)"""
"""Call an MCP tool via standalone server"""
try:
# Prepare the MCP initialization and tool call sequence
init_request = {
@@ -131,8 +144,8 @@ class Calculator:
# Join with newlines as MCP expects
input_data = "\n".join(messages) + "\n"
# Simulate Claude CLI calling the MCP server via docker exec
docker_cmd = ["docker", "exec", "-i", self.container_name, "python", "server.py"]
# Call the standalone MCP server directly
server_cmd = [self.python_path, "server.py"]
self.logger.debug(f"Calling MCP tool {tool_name} with proper initialization")
@@ -140,7 +153,7 @@ class Calculator:
# For consensus tool and other long-running tools, we need to ensure
# the subprocess doesn't close prematurely
result = subprocess.run(
docker_cmd,
server_cmd,
input=input_data,
text=True,
capture_output=True,
@@ -149,7 +162,7 @@ class Calculator:
)
if result.returncode != 0:
self.logger.error(f"Docker exec failed with return code {result.returncode}")
self.logger.error(f"Standalone server failed with return code {result.returncode}")
self.logger.error(f"Stderr: {result.stderr}")
# Still try to parse stdout as the response might have been written before the error
self.logger.debug(f"Attempting to parse stdout despite error: {result.stdout[:500]}")
@@ -263,6 +276,56 @@ class Calculator:
shutil.rmtree(self.test_dir)
self.logger.debug(f"Removed test files directory: {self.test_dir}")
# ============================================================================
# Log Utility Methods (delegate to LogUtils)
# ============================================================================
def get_server_logs_since(self, since_time: Optional[str] = None) -> str:
"""Get server logs from both main and activity log files."""
return LogUtils.get_server_logs_since(since_time)
def get_recent_server_logs(self, lines: int = 500) -> str:
"""Get recent server logs from the main log file."""
return LogUtils.get_recent_server_logs(lines)
def get_server_logs_subprocess(self, lines: int = 500) -> str:
"""Get server logs using subprocess (alternative method)."""
return LogUtils.get_server_logs_subprocess(lines)
def check_server_logs_for_errors(self, lines: int = 500) -> list[str]:
"""Check server logs for error messages."""
return LogUtils.check_server_logs_for_errors(lines)
def extract_conversation_usage_logs(self, logs: str) -> list[dict[str, int]]:
"""Extract token budget calculation information from logs."""
return LogUtils.extract_conversation_usage_logs(logs)
def extract_conversation_token_usage(self, logs: str) -> list[int]:
"""Extract conversation token usage values from logs."""
return LogUtils.extract_conversation_token_usage(logs)
def extract_thread_creation_logs(self, logs: str) -> list[dict[str, str]]:
"""Extract thread creation logs with parent relationships."""
return LogUtils.extract_thread_creation_logs(logs)
def extract_history_traversal_logs(self, logs: str) -> list[dict[str, any]]:
"""Extract conversation history traversal logs."""
return LogUtils.extract_history_traversal_logs(logs)
def validate_file_deduplication_in_logs(self, logs: str, tool_name: str, test_file: str) -> bool:
"""Validate that logs show file deduplication behavior."""
return LogUtils.validate_file_deduplication_in_logs(logs, tool_name, test_file)
def search_logs_for_pattern(
self, pattern: str, logs: Optional[str] = None, case_sensitive: bool = False
) -> list[str]:
"""Search logs for a specific pattern."""
return LogUtils.search_logs_for_pattern(pattern, logs, case_sensitive)
def get_log_file_info(self) -> dict[str, dict[str, any]]:
"""Get information about log files."""
return LogUtils.get_log_file_info()
def run_test(self) -> bool:
"""Run the test - to be implemented by subclasses"""
raise NotImplementedError("Subclasses must implement run_test()")