From 694ea46f66e5e4eddc24904028dafbfd33b520ad Mon Sep 17 00:00:00 2001 From: Daniel Chalef <131175+danielchalef@users.noreply.github.com> Date: Tue, 26 Aug 2025 07:35:09 -0700 Subject: [PATCH] fix: Resolve CI/CD issues for MCP server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed code formatting with ruff (removed trailing whitespace) - Fixed linting issues (removed unused imports) - Updated Dockerfile for new directory structure - Improved FalkorDB container health checks and timeouts - Enhanced FalkorDB readiness check with GRAPH module verification - Added debugging for FalkorDB startup failures ๐Ÿค– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/mcp-server-tests.yml | 33 ++++++--- mcp_server/docker/Dockerfile | 7 +- mcp_server/src/config/schema.py | 2 +- mcp_server/src/services/factories.py | 2 +- mcp_server/tests/test_falkordb_integration.py | 71 +++++++++---------- mcp_server/tests/test_simple_validation.py | 10 +-- 6 files changed, 72 insertions(+), 53 deletions(-) diff --git a/.github/workflows/mcp-server-tests.yml b/.github/workflows/mcp-server-tests.yml index 9cd5641e..7d26bbe3 100644 --- a/.github/workflows/mcp-server-tests.yml +++ b/.github/workflows/mcp-server-tests.yml @@ -40,11 +40,11 @@ jobs: - 6379:6379 - 3000:3000 options: >- - --health-cmd "redis-cli -h localhost -p 6379 ping" - --health-interval 15s - --health-timeout 10s - --health-retries 8 - --health-start-period 30s + --health-cmd "redis-cli -h localhost -p 6379 ping || exit 1" + --health-interval 20s + --health-timeout 15s + --health-retries 12 + --health-start-period 60s steps: - name: Checkout repository @@ -200,12 +200,26 @@ jobs: - name: Wait for FalkorDB to be ready run: | echo "๐Ÿ”„ Waiting for FalkorDB to be ready..." - max_attempts=30 + + # Install redis-tools first if not available + if ! command -v redis-cli &> /dev/null; then + echo "๐Ÿ“ฆ Installing redis-tools..." + sudo apt-get update && sudo apt-get install -y redis-tools + fi + + max_attempts=40 attempt=1 while [ $attempt -le $max_attempts ]; do - if redis-cli -h localhost -p 6379 ping >/dev/null 2>&1; then + if redis-cli -h localhost -p 6379 ping 2>/dev/null | grep -q PONG; then echo "โœ… FalkorDB is ready!" - break + + # Verify GRAPH module is loaded + if redis-cli -h localhost -p 6379 MODULE LIST 2>/dev/null | grep -q graph; then + echo "โœ… FalkorDB GRAPH module is loaded!" + break + else + echo "โณ Waiting for GRAPH module to load..." + fi fi echo "โณ Attempt $attempt/$max_attempts - FalkorDB not ready yet..." sleep 3 @@ -214,6 +228,9 @@ jobs: if [ $attempt -gt $max_attempts ]; then echo "โŒ FalkorDB failed to start within timeout" + # Get container logs for debugging + docker ps -a + docker logs $(docker ps -q -f "ancestor=falkordb/falkordb:v4.12.4") 2>&1 | tail -50 || echo "Could not fetch logs" exit 1 fi diff --git a/mcp_server/docker/Dockerfile b/mcp_server/docker/Dockerfile index 75e2e9d2..f89f6d1b 100644 --- a/mcp_server/docker/Dockerfile +++ b/mcp_server/docker/Dockerfile @@ -34,8 +34,9 @@ RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --frozen --no-dev # Copy application code and configuration -COPY *.py ./ -COPY config.yaml ./ +COPY main.py ./ +COPY src/ ./src/ +COPY config/ ./config/ # Change ownership to app user RUN chown -Rv app:app /app @@ -47,4 +48,4 @@ USER app EXPOSE 8000 # Command to run the application -CMD ["uv", "run", "graphiti_mcp_server.py"] +CMD ["uv", "run", "main.py"] diff --git a/mcp_server/src/config/schema.py b/mcp_server/src/config/schema.py index a8d0c135..8a75a0b4 100644 --- a/mcp_server/src/config/schema.py +++ b/mcp_server/src/config/schema.py @@ -206,7 +206,7 @@ class GraphitiConfig(BaseSettings): embedder: EmbedderConfig = Field(default_factory=EmbedderConfig) database: DatabaseConfig = Field(default_factory=DatabaseConfig) graphiti: GraphitiAppConfig = Field(default_factory=GraphitiAppConfig) - + # Additional server options use_custom_entities: bool = Field(default=False, description='Enable custom entity types') destroy_graph: bool = Field(default=False, description='Clear graph on startup') diff --git a/mcp_server/src/services/factories.py b/mcp_server/src/services/factories.py index 7a169480..c989ceed 100644 --- a/mcp_server/src/services/factories.py +++ b/mcp_server/src/services/factories.py @@ -272,7 +272,7 @@ class DatabaseDriverFactory: ) if not config.providers.falkordb: raise ValueError('FalkorDB provider configuration not found') - + falkor_config = config.providers.falkordb return { 'driver': 'falkordb', diff --git a/mcp_server/tests/test_falkordb_integration.py b/mcp_server/tests/test_falkordb_integration.py index d4a23b13..b4c97bee 100644 --- a/mcp_server/tests/test_falkordb_integration.py +++ b/mcp_server/tests/test_falkordb_integration.py @@ -9,7 +9,7 @@ import json import time from typing import Any -from mcp import ClientSession, StdioServerParameters +from mcp import StdioServerParameters from mcp.client.stdio import stdio_client @@ -37,14 +37,14 @@ class GraphitiFalkorDBIntegrationTest: # Start the stdio client self.session = await stdio_client(server_params).__aenter__() - print(f' ๐Ÿ“ก Started MCP client session with FalkorDB backend') + print(' ๐Ÿ“ก Started MCP client session with FalkorDB backend') return self async def __aexit__(self, exc_type, exc_val, exc_tb): """Clean up the MCP client session.""" if self.session: await self.session.close() - print(f' ๐Ÿ”Œ Closed MCP client session') + print(' ๐Ÿ”Œ Closed MCP client session') async def call_mcp_tool(self, tool_name: str, arguments: dict[str, Any]) -> dict[str, Any]: """Call an MCP tool via the stdio client.""" @@ -68,15 +68,15 @@ class GraphitiFalkorDBIntegrationTest: """Test the get_status tool to verify FalkorDB connectivity.""" print(' ๐Ÿฅ Testing server status with FalkorDB...') result = await self.call_mcp_tool('get_status', {}) - + if 'error' in result: print(f' โŒ Status check failed: {result["error"]}') return False - + # Check if status indicates FalkorDB is working status_text = result.get('raw_response', result.get('content', '')) if 'running' in str(status_text).lower() or 'ready' in str(status_text).lower(): - print(f' โœ… Server status OK with FalkorDB') + print(' โœ… Server status OK with FalkorDB') return True else: print(f' โš ๏ธ Status unclear: {status_text}') @@ -85,54 +85,53 @@ class GraphitiFalkorDBIntegrationTest: async def test_add_episode(self) -> bool: """Test adding an episode to FalkorDB.""" print(' ๐Ÿ“ Testing episode addition to FalkorDB...') - + episode_data = { 'name': 'FalkorDB Test Episode', 'episode_body': 'This is a test episode to verify FalkorDB integration works correctly.', 'source': 'text', - 'source_description': 'Integration test for FalkorDB backend' + 'source_description': 'Integration test for FalkorDB backend', } - + result = await self.call_mcp_tool('add_episode', episode_data) - + if 'error' in result: print(f' โŒ Add episode failed: {result["error"]}') return False - - print(f' โœ… Episode added successfully to FalkorDB') + + print(' โœ… Episode added successfully to FalkorDB') return True async def test_search_functionality(self) -> bool: """Test search functionality with FalkorDB.""" print(' ๐Ÿ” Testing search functionality with FalkorDB...') - + # Give some time for episode processing await asyncio.sleep(2) - + # Test node search - search_result = await self.call_mcp_tool('search_nodes', { - 'query': 'FalkorDB test episode', - 'limit': 5 - }) - + search_result = await self.call_mcp_tool( + 'search_nodes', {'query': 'FalkorDB test episode', 'limit': 5} + ) + if 'error' in search_result: print(f' โš ๏ธ Search returned error (may be expected): {search_result["error"]}') return True # Don't fail on search errors in integration test - - print(f' โœ… Search functionality working with FalkorDB') + + print(' โœ… Search functionality working with FalkorDB') return True async def test_clear_graph(self) -> bool: """Test clearing the graph in FalkorDB.""" print(' ๐Ÿงน Testing graph clearing in FalkorDB...') - + result = await self.call_mcp_tool('clear_graph', {}) - + if 'error' in result: print(f' โŒ Clear graph failed: {result["error"]}') return False - - print(f' โœ… Graph cleared successfully in FalkorDB') + + print(' โœ… Graph cleared successfully in FalkorDB') return True @@ -140,13 +139,13 @@ async def run_falkordb_integration_test() -> bool: """Run the complete FalkorDB integration test suite.""" print('๐Ÿงช Starting FalkorDB Integration Test Suite') print('=' * 55) - + test_results = [] - + try: async with GraphitiFalkorDBIntegrationTest() as test_client: print(f' ๐ŸŽฏ Using test group: {test_client.test_group_id}') - + # Run test suite tests = [ ('Server Status', test_client.test_server_status), @@ -154,7 +153,7 @@ async def run_falkordb_integration_test() -> bool: ('Search Functionality', test_client.test_search_functionality), ('Clear Graph', test_client.test_clear_graph), ] - + for test_name, test_func in tests: print(f'\n๐Ÿ”ฌ Running {test_name} Test...') try: @@ -167,25 +166,25 @@ async def run_falkordb_integration_test() -> bool: except Exception as e: print(f' ๐Ÿ’ฅ {test_name}: ERROR - {e}') test_results.append((test_name, False)) - + except Exception as e: print(f'๐Ÿ’ฅ Test setup failed: {e}') return False - + # Summary print('\n' + '=' * 55) print('๐Ÿ“Š FalkorDB Integration Test Results:') print('-' * 30) - + passed = sum(1 for _, result in test_results if result) total = len(test_results) - + for test_name, result in test_results: status = 'โœ… PASS' if result else 'โŒ FAIL' print(f' {test_name}: {status}') - + print(f'\n๐ŸŽฏ Overall: {passed}/{total} tests passed') - + if passed == total: print('๐ŸŽ‰ All FalkorDB integration tests PASSED!') return True @@ -196,4 +195,4 @@ async def run_falkordb_integration_test() -> bool: if __name__ == '__main__': success = asyncio.run(run_falkordb_integration_test()) - exit(0 if success else 1) \ No newline at end of file + exit(0 if success else 1) diff --git a/mcp_server/tests/test_simple_validation.py b/mcp_server/tests/test_simple_validation.py index 2bc23fc6..fb1302f8 100644 --- a/mcp_server/tests/test_simple_validation.py +++ b/mcp_server/tests/test_simple_validation.py @@ -13,10 +13,12 @@ import time def test_server_startup(): """Test that the refactored server starts up successfully.""" print('๐Ÿš€ Testing Graphiti MCP Server Startup...') - + # Skip server startup test in CI - we have comprehensive integration tests if os.environ.get('CI'): - print(' โš ๏ธ Skipping server startup test in CI (comprehensive integration tests handle this)') + print( + ' โš ๏ธ Skipping server startup test in CI (comprehensive integration tests handle this)' + ) return True # Check if uv is available @@ -29,7 +31,7 @@ def test_server_startup(): break except (subprocess.TimeoutExpired, FileNotFoundError): continue - + if not uv_cmd: print(' โš ๏ธ uv not found in PATH, skipping server startup test') return True @@ -72,7 +74,7 @@ def test_server_startup(): except Exception: continue - + if not success: print(' โš ๏ธ Timeout waiting for initialization or server startup failed')