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

@@ -9,7 +9,6 @@ Tests custom API endpoint functionality with Ollama-style local models, includin
- Model alias resolution for local models
"""
import subprocess
from .base_test import BaseSimulatorTest
@@ -30,14 +29,15 @@ class OllamaCustomUrlTest(BaseSimulatorTest):
try:
self.logger.info("Test: Ollama custom URL functionality")
# Check if custom URL is configured in the Docker container
custom_url = self._check_docker_custom_url()
# Check if custom URL is configured
import os
custom_url = os.environ.get("CUSTOM_API_URL")
if not custom_url:
self.logger.warning("CUSTOM_API_URL not set in Docker container, skipping Ollama test")
self.logger.warning("CUSTOM_API_URL not set, skipping Ollama test")
self.logger.info("To enable this test, add to .env file:")
self.logger.info("CUSTOM_API_URL=http://host.docker.internal:11434/v1")
self.logger.info("CUSTOM_API_URL=http://localhost:11434/v1")
self.logger.info("CUSTOM_API_KEY=")
self.logger.info("Then restart docker-compose")
return True # Skip gracefully
self.logger.info(f"Testing with custom URL: {custom_url}")
@@ -172,25 +172,6 @@ if __name__ == "__main__":
finally:
self.cleanup_test_files()
def _check_docker_custom_url(self) -> str:
"""Check if CUSTOM_API_URL is set in the Docker container"""
try:
result = subprocess.run(
["docker", "exec", self.container_name, "printenv", "CUSTOM_API_URL"],
capture_output=True,
text=True,
timeout=10,
)
if result.returncode == 0 and result.stdout.strip():
return result.stdout.strip()
return ""
except Exception as e:
self.logger.debug(f"Failed to check Docker CUSTOM_API_URL: {e}")
return ""
def validate_successful_response(self, response: str, test_name: str, files_provided: bool = False) -> bool:
"""Validate that the response indicates success, not an error
@@ -201,7 +182,7 @@ if __name__ == "__main__":
"""
if not response:
self.logger.error(f"No response received for {test_name}")
self._check_docker_logs_for_errors()
self._check_server_logs_for_errors()
return False
# Check for common error indicators
@@ -227,7 +208,7 @@ if __name__ == "__main__":
]
# Special handling for clarification requests from local models
if "clarification_required" in response.lower():
if "files_required_to_continue" in response.lower():
if files_provided:
# If we provided actual files, clarification request is a FAILURE
self.logger.error(
@@ -243,7 +224,7 @@ if __name__ == "__main__":
self.logger.debug(f"Clarification response: {response[:200]}...")
return True
# Check for SSRF security restriction - this is expected for local URLs from Docker
# Check for SSRF security restriction - this is expected for local URLs
if "restricted IP address" in response and "security risk (SSRF)" in response:
self.logger.info(
f"✅ Custom URL routing working - {test_name} correctly attempted to connect to custom API"
@@ -256,19 +237,19 @@ if __name__ == "__main__":
if error.lower() in response_lower:
self.logger.error(f"Error detected in {test_name}: {error}")
self.logger.debug(f"Full response: {response}")
self._check_docker_logs_for_errors()
self._check_server_logs_for_errors()
return False
# Response should be substantial (more than just a few words)
if len(response.strip()) < 10:
self.logger.error(f"Response too short for {test_name}: {response}")
self._check_docker_logs_for_errors()
self._check_server_logs_for_errors()
return False
# Verify this looks like a real AI response, not just an error message
if not self._validate_ai_response_content(response):
self.logger.error(f"Response doesn't look like valid AI output for {test_name}")
self._check_docker_logs_for_errors()
self._check_server_logs_for_errors()
return False
self.logger.debug(f"Successful response for {test_name}: {response[:100]}...")
@@ -329,24 +310,23 @@ if __name__ == "__main__":
return True
def _check_docker_logs_for_errors(self):
"""Check Docker logs for any error messages that might explain failures"""
def _check_server_logs_for_errors(self):
"""Check server logs for any error messages that might explain failures"""
try:
# Get recent logs from the container
result = subprocess.run(
["docker", "logs", "--tail", "50", self.container_name], capture_output=True, text=True, timeout=10
)
# Get recent logs from the log file
log_file_path = "logs/mcp_server.log"
with open(log_file_path) as f:
lines = f.readlines()
recent_logs = lines[-50:] # Last 50 lines
if result.returncode == 0 and result.stderr:
recent_logs = result.stderr.strip()
if recent_logs:
self.logger.info("Recent container logs:")
for line in recent_logs.split("\n")[-10:]: # Last 10 lines
if line.strip():
self.logger.info(f" {line}")
if recent_logs:
self.logger.info("Recent server logs:")
for line in recent_logs[-10:]: # Last 10 lines
if line.strip():
self.logger.info(f" {line.strip()}")
except Exception as e:
self.logger.debug(f"Failed to check Docker logs: {e}")
self.logger.debug(f"Failed to check server logs: {e}")
def validate_local_model_response(self, response: str) -> bool:
"""Validate that response appears to come from a local model"""