# Graphiti MCP Server Integration Tests This directory contains a comprehensive integration test suite for the Graphiti MCP Server using the official Python MCP SDK. ## Overview The test suite is designed to thoroughly test all aspects of the Graphiti MCP server with special consideration for LLM inference latency and system performance. ## Test Organization ### Core Test Modules - **`test_inmemory_example.py`** - **Recommended** in-memory tests using FastMCP Client (fast, reliable) - **`test_comprehensive_integration.py`** - Integration test suite using subprocess (slower, may have env issues) - **`test_async_operations.py`** - Tests for concurrent operations and async patterns - **`test_stress_load.py`** - Stress testing and load testing scenarios - **`test_fixtures.py`** - Shared fixtures and test utilities - **`test_mcp_integration.py`** - Original MCP integration tests - **`test_configuration.py`** - Configuration loading and validation tests ### Test Categories Tests are organized with pytest markers: - `unit` - Fast unit tests without external dependencies - `integration` - Tests requiring database and services - `slow` - Long-running tests (stress/load tests) - `requires_neo4j` - Tests requiring Neo4j - `requires_falkordb` - Tests requiring FalkorDB - `requires_openai` - Tests requiring OpenAI API key ## Installation ```bash # Install test dependencies uv add --dev pytest pytest-asyncio pytest-timeout pytest-xdist faker psutil # Install MCP SDK (fastmcp is already a dependency of graphiti-core) uv add mcp ``` > **Note on FastMCP**: The `fastmcp` package (v2.13.3) is a dependency of `graphiti-core` and provides the `Client` class for testing. The MCP server uses `mcp.server.fastmcp.FastMCP` which is bundled in the official `mcp` package. ## Running Tests ### Quick Start (Recommended) The fastest and most reliable way to test is using the in-memory tests: ```bash # Run in-memory tests (fast, ~1 second) uv run pytest tests/test_inmemory_example.py -v -s ``` This uses FastMCP's recommended testing pattern with in-memory transport, avoiding subprocess issues. ### Alternative: Subprocess-based Tests The original test runner spawns subprocess servers. These tests may experience environment variable issues: ```bash # Run smoke tests (may timeout due to subprocess issues) python tests/run_tests.py smoke # Run integration tests with mock LLM python tests/run_tests.py integration --mock-llm # Run all tests python tests/run_tests.py all ``` > **Note**: The subprocess-based tests use `StdioServerParameters` which can have environment variable isolation issues. If you encounter `ValueError: invalid literal for int()` errors related to `SEMAPHORE_LIMIT` or `MAX_REFLEXION_ITERATIONS`, use the in-memory tests instead. ### Test Runner Options ```bash python tests/run_tests.py [suite] [options] Suites: unit - Unit tests only integration - Integration tests comprehensive - Comprehensive integration suite async - Async operation tests stress - Stress and load tests smoke - Quick smoke tests all - All tests Options: --database - Database backend (neo4j, falkordb) --mock-llm - Use mock LLM for faster testing --parallel N - Run tests in parallel with N workers --coverage - Generate coverage report --skip-slow - Skip slow tests --timeout N - Test timeout in seconds --check-only - Only check prerequisites ``` ### Examples ```bash # Quick smoke test with FalkorDB (default) python tests/run_tests.py smoke # Full integration test with Neo4j python tests/run_tests.py integration --database neo4j # Stress testing with parallel execution python tests/run_tests.py stress --parallel 4 # Run with coverage python tests/run_tests.py all --coverage # Check prerequisites only python tests/run_tests.py all --check-only ``` ## Test Coverage ### Core Operations - Server initialization and tool discovery - Adding memories (text, JSON, message) - Episode queue management - Search operations (semantic, hybrid) - Episode retrieval and deletion - Entity and edge operations ### Async Operations - Concurrent operations - Queue management - Sequential processing within groups - Parallel processing across groups ### Performance Testing - Latency measurement - Throughput testing - Batch processing - Resource usage monitoring ### Stress Testing - Sustained load scenarios - Spike load handling - Memory leak detection - Connection pool exhaustion - Rate limit handling ## Configuration ### Environment Variables ```bash # Database configuration export DATABASE_PROVIDER=falkordb # or neo4j export NEO4J_URI=bolt://localhost:7687 export NEO4J_USER=neo4j export NEO4J_PASSWORD=graphiti export FALKORDB_URI=redis://localhost:6379 # LLM configuration export OPENAI_API_KEY=your_key_here # or use --mock-llm # Test configuration export TEST_MODE=true export LOG_LEVEL=INFO ``` ### pytest.ini Configuration The `pytest.ini` file configures: - Test discovery patterns - Async mode settings - Test markers - Timeout settings - Output formatting ## In-Memory Testing Pattern (Recommended) The `test_inmemory_example.py` file demonstrates FastMCP's recommended testing approach: ```python import os import sys from pathlib import Path import pytest from fastmcp.client import Client # Set env vars BEFORE importing graphiti modules def set_env_if_empty(key: str, value: str): if not os.environ.get(key): os.environ[key] = value set_env_if_empty('SEMAPHORE_LIMIT', '10') set_env_if_empty('MAX_REFLEXION_ITERATIONS', '0') set_env_if_empty('FALKORDB_URI', 'redis://localhost:6379') # Import after env vars are set from graphiti_mcp_server import mcp @pytest.fixture async def mcp_client(): """In-memory MCP client - no subprocess needed.""" async with Client(transport=mcp) as client: yield client async def test_list_tools(mcp_client: Client): tools = await mcp_client.list_tools() assert len(tools) > 0 ``` ### Benefits of In-Memory Testing | Aspect | In-Memory | Subprocess | |--------|-----------|------------| | Speed | ~1 second | 10+ minutes | | Reliability | High | Environment issues | | Debugging | Easy | Difficult | | Resource Usage | Low | High | ### Available MCP Tools The Graphiti MCP server exposes these tools: - `add_memory` - Add episodes to the knowledge graph - `search_nodes` - Search for entity nodes - `search_memory_facts` - Search for facts/relationships - `delete_entity_edge` - Delete an edge - `delete_episode` - Delete an episode - `get_entity_edge` - Get edge by UUID - `get_episodes` - Get recent episodes - `clear_graph` - Clear all data - `get_status` - Get server status ## Test Fixtures ### Data Generation The test suite includes comprehensive data generators: ```python from test_fixtures import TestDataGenerator # Generate test data company = TestDataGenerator.generate_company_profile() conversation = TestDataGenerator.generate_conversation() document = TestDataGenerator.generate_technical_document() ``` ### Test Client Simplified client creation: ```python from test_fixtures import graphiti_test_client async with graphiti_test_client(database="falkordb") as (session, group_id): # Use session for testing result = await session.call_tool('add_memory', {...}) ``` ## Performance Considerations ### LLM Latency Management The tests account for LLM inference latency through: 1. **Configurable timeouts** - Different timeouts for different operations 2. **Mock LLM option** - Fast testing without API calls 3. **Intelligent polling** - Adaptive waiting for episode processing 4. **Batch operations** - Testing efficiency of batched requests ### Resource Management - Memory leak detection - Connection pool monitoring - Resource usage tracking - Graceful degradation testing ## CI/CD Integration ### GitHub Actions ```yaml name: MCP Integration Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest services: neo4j: image: neo4j:5.26 env: NEO4J_AUTH: neo4j/graphiti ports: - 7687:7687 steps: - uses: actions/checkout@v2 - name: Install dependencies run: | pip install uv uv sync --extra dev - name: Run smoke tests run: python tests/run_tests.py smoke --mock-llm - name: Run integration tests run: python tests/run_tests.py integration --database neo4j env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ``` ## Troubleshooting ### Common Issues 1. **Database connection failures** ```bash # Check Neo4j curl http://localhost:7474 # Check FalkorDB redis-cli ping ``` 2. **API key issues** ```bash # Use mock LLM for testing without API key python tests/run_tests.py all --mock-llm ``` 3. **Timeout errors** ```bash # Increase timeout for slow systems python tests/run_tests.py integration --timeout 600 ``` 4. **Memory issues** ```bash # Skip stress tests on low-memory systems python tests/run_tests.py all --skip-slow ``` 5. **Environment variable errors** (`ValueError: invalid literal for int()`) ```bash # This occurs when SEMAPHORE_LIMIT or MAX_REFLEXION_ITERATIONS is set to empty string # Solution 1: Use in-memory tests (recommended) uv run pytest tests/test_inmemory_example.py -v # Solution 2: Set env vars explicitly SEMAPHORE_LIMIT=10 MAX_REFLEXION_ITERATIONS=0 python tests/run_tests.py smoke ``` **Root cause**: The graphiti_core/helpers.py module parses environment variables at import time. If these are set to empty strings (not unset), `int('')` fails. ## Test Reports ### Performance Report After running performance tests: ```python from test_fixtures import PerformanceBenchmark benchmark = PerformanceBenchmark() # ... run tests ... print(benchmark.report()) ``` ### Load Test Report Stress tests generate detailed reports: ``` LOAD TEST REPORT ================ Test Run 1: Total Operations: 100 Success Rate: 95.0% Throughput: 12.5 ops/s Latency (avg/p50/p95/p99/max): 0.8/0.7/1.5/2.1/3.2s ``` ## Contributing When adding new tests: 1. Use appropriate pytest markers 2. Include docstrings explaining test purpose 3. Use fixtures for common operations 4. Consider LLM latency in test design 5. Add timeout handling for long operations 6. Include performance metrics where relevant ## License See main project LICENSE file.