Merge branch 'main' into graphid-isolation

This commit is contained in:
Naseem Ali 2025-10-23 13:58:41 +03:00 committed by GitHub
commit 445ea2e45c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 810 additions and 453 deletions

View file

@ -69,6 +69,14 @@ COPY ./server/graph_service ./graph_service
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev
# Install falkordb if requested
ARG INSTALL_FALKORDB=false
RUN --mount=type=cache,target=/root/.cache/uv \
if [ "$INSTALL_FALKORDB" = "true" ]; then \
WHEEL=$(ls /tmp/*.whl | head -n 1); \
uv pip install "$WHEEL[falkordb]"; \
fi
# Change ownership to app user
RUN chown -R app:app /app

View file

@ -77,6 +77,23 @@ We're excited to open-source Graphiti, believing its potential reaches far beyon
<a href="https://arxiv.org/abs/2501.13956"><img src="images/arxiv-screenshot.png" alt="Zep: A Temporal Knowledge Graph Architecture for Agent Memory" width="700px"></a>
</p>
## Zep vs Graphiti
| Aspect | Zep | Graphiti |
|--------|-----|----------|
| **What they are** | Complete managed platform for AI memory | Open-source graph framework |
| **User & conversation management** | Built-in users, threads, and message storage | Build your own |
| **Retrieval & performance** | Pre-configured, production-ready retrieval with sub-200ms performance at scale | Custom implementation required; performance depends on your setup |
| **Developer tools** | Dashboard with graph visualization, debug logs, API logs; SDKs for Python, TypeScript, and Go | Build your own tools |
| **Enterprise features** | SLAs, support, security guarantees | Self-managed |
| **Deployment** | Fully managed or in your cloud | Self-hosted only |
### When to choose which
**Choose Zep** if you want a turnkey, enterprise-grade platform with security, performance, and support baked in.
**Choose Graphiti** if you want a flexible OSS core and you're comfortable building/operating the surrounding system.
## Why Graphiti?
Traditional RAG approaches often rely on batch processing and static data summarization, making them inefficient for
@ -239,6 +256,22 @@ The quickstart demonstrates:
The example is fully documented with clear explanations of each functionality and includes a comprehensive README with
setup instructions and next steps.
### Running with Docker Compose
You can use Docker Compose to quickly start the required services:
- **Neo4j Docker:**
```sh
docker compose up
```
This will start the Neo4j Docker service and related components.
- **FalkorDB Docker:**
```sh
docker compose --profile falkordb up
```
This will start the FalkorDB Docker service and related components.
## MCP Server
The `mcp_server` directory contains a Model Context Protocol (MCP) server implementation for Graphiti. This server

View file

@ -1,5 +1,6 @@
services:
graph:
profiles: [""]
build:
context: .
ports:
@ -24,8 +25,10 @@ services:
- NEO4J_USER=${NEO4J_USER}
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- PORT=8000
- db_backend=neo4j
neo4j:
image: neo4j:5.26.2
profiles: [""]
healthcheck:
test:
[
@ -44,5 +47,46 @@ services:
environment:
- NEO4J_AUTH=${NEO4J_USER}/${NEO4J_PASSWORD}
falkordb:
image: falkordb/falkordb:latest
profiles: ["falkordb"]
ports:
- "6379:6379"
volumes:
- falkordb_data:/data
environment:
- FALKORDB_ARGS=--port 6379 --cluster-enabled no
healthcheck:
test: ["CMD", "redis-cli", "-p", "6379", "ping"]
interval: 1s
timeout: 10s
retries: 10
start_period: 3s
graph-falkordb:
build:
args:
INSTALL_FALKORDB: "true"
context: .
profiles: ["falkordb"]
ports:
- "8001:8001"
depends_on:
falkordb:
condition: service_healthy
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8001/healthcheck')"]
interval: 10s
timeout: 5s
retries: 3
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- FALKORDB_HOST=falkordb
- FALKORDB_PORT=6379
- FALKORDB_DATABASE=default_db
- GRAPHITI_BACKEND=falkordb
- PORT=8001
- db_backend=falkordb
volumes:
neo4j_data:
falkordb_data:

View file

@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
import datetime
import asyncio
import logging
from typing import TYPE_CHECKING, Any
@ -261,7 +262,29 @@ class FalkorDriver(GraphDriver):
# Create a new instance of FalkorDriver with the same connection but a different database
cloned = FalkorDriver(falkor_db=self.client, database=database)
return cloned
return cloned
async def health_check(self) -> None:
"""Check FalkorDB connectivity by running a simple query."""
try:
await self.execute_query("MATCH (n) RETURN 1 LIMIT 1")
return None
except Exception as e:
print(f"FalkorDB health check failed: {e}")
raise
@staticmethod
def convert_datetimes_to_strings(obj):
if isinstance(obj, dict):
return {k: FalkorDriver.convert_datetimes_to_strings(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [FalkorDriver.convert_datetimes_to_strings(item) for item in obj]
elif isinstance(obj, tuple):
return tuple(FalkorDriver.convert_datetimes_to_strings(item) for item in obj)
elif isinstance(obj, datetime):
return obj.isoformat()
else:
return obj
def sanitize(self, query: str) -> str:
"""

View file

@ -106,3 +106,12 @@ class Neo4jDriver(GraphDriver):
for query in index_queries
]
)
async def health_check(self) -> None:
"""Check Neo4j connectivity by running the driver's verify_connectivity method."""
try:
await self.client.verify_connectivity()
return None
except Exception as e:
print(f"Neo4j health check failed: {e}")
raise

View file

@ -1,11 +1,20 @@
# Graphiti MCP Server Environment Configuration
# Neo4j Database Configuration
# Database Configuration
# Choose between 'neo4j' or 'falkordb'
DATABASE_TYPE=falkordb
# These settings are used to connect to your Neo4j database
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=demodemo
# These settings are used to connect to your FalkorDB database
FALKORDB_PORT=6379
FALKORDB_HOST=localhost
FALKORDB_USER=
FALKORDB_PASSWORD=
# OpenAI API Configuration
# Required for LLM operations
OPENAI_API_KEY=your_openai_api_key_here

View file

@ -30,8 +30,13 @@ RUN groupadd -r app && useradd -r -d /app -g app app
COPY pyproject.toml uv.lock ./
# Install dependencies first (better layer caching)
ARG INSTALL_FALKORDB=false
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev
if [ "$INSTALL_FALKORDB" = "true" ]; then \
uv sync --frozen --no-dev --extra falkordb; \
else \
uv sync --frozen --no-dev; \
fi
# Copy application code
COPY graphiti_mcp_server.py ./
@ -43,7 +48,7 @@ RUN chown -Rv app:app /app
USER app
# Expose port
EXPOSE 8000
EXPOSE $PORT
# Command to run the application
CMD ["uv", "run", "graphiti_mcp_server.py"]

View file

@ -82,11 +82,21 @@ uv sync
## Configuration
The server uses the following environment variables:
The server supports both Neo4j and FalkorDB as database backends. Use the `DATABASE_TYPE` environment variable to choose between them.
#### Neo4j Configuration (default)
- `NEO4J_URI`: URI for the Neo4j database (default: `bolt://localhost:7687`)
- `NEO4J_USER`: Neo4j username (default: `neo4j`)
- `NEO4J_PASSWORD`: Neo4j password (default: `demodemo`)
#### FalkorDB Configuration
- `DATABASE_TYPE`: Set to `falkordb`
- `FALKORDB_HOST`: FalkorDB host (default: `localhost`)
- `FALKORDB_PORT`: FalkorDB port (default: `6379`)
- `FALKORDB_USERNAME`: FalkorDB username (optional)
- `FALKORDB_PASSWORD`: FalkorDB password (optional)
- `OPENAI_API_KEY`: OpenAI API key (required for LLM operations)
- `OPENAI_BASE_URL`: Optional base URL for OpenAI API
- `MODEL_NAME`: OpenAI model name to use for LLM operations.
@ -115,7 +125,7 @@ uv run graphiti_mcp_server.py
With options:
```bash
uv run graphiti_mcp_server.py --model gpt-4.1-mini --transport sse
uv run graphiti_mcp_server.py --model gpt-4.1-mini --transport sse --database-type falkordb --port 8001
```
Available arguments:
@ -124,6 +134,7 @@ Available arguments:
- `--small-model`: Overrides the `SMALL_MODEL_NAME` environment variable.
- `--temperature`: Overrides the `LLM_TEMPERATURE` environment variable.
- `--transport`: Choose the transport method (sse or stdio, default: sse)
- `--database-type`: Choose database backend (neo4j or falkordb, default: neo4j)
- `--group-id`: Set a namespace for the graph (optional). If not provided, defaults to "default".
- `--destroy-graph`: If set, destroys all Graphiti graphs on startup.
- `--use-custom-entities`: Enable entity extraction using the predefined ENTITY_TYPES
@ -175,11 +186,30 @@ The Docker Compose setup includes a Neo4j container with the following default c
- URI: `bolt://neo4j:7687` (from within the Docker network)
- Memory settings optimized for development use
To run only Neo4j with its MCP server:
```bash
docker compose up
```
- Neo4j MCP server on port 8000
#### FalkorDB Configuration
The Docker Compose setup includes a FalkorDB container with the following default configuration:
- Host: `falkordb`
- Port: `6379`
- No authentication by default
To run only FalkorDB with its MCP server:
```bash
docker compose --profile falkordb up
```
- FalkorDB MCP server on port 8001
#### Running with Docker Compose
A Graphiti MCP container is available at: `zepai/knowledge-graph-mcp`. The latest build of this container is used by the Compose setup below.
Start the services using Docker Compose:
Start the services using Docker Compose For Neo4j:
```bash
docker compose up
@ -191,13 +221,25 @@ Or if you're using an older version of Docker Compose:
docker-compose up
```
This will start both the Neo4j database and the Graphiti MCP server. The Docker setup:
For FalkorDB:
```bash
docker compose --profile falkordb up
```
Or if you're using an older version of Docker Compose:
```bash
docker-compose --profile falkordb up
```
This will start the database(s) and the Graphiti MCP server(s). The Docker setup:
- Uses `uv` for package management and running the server
- Installs dependencies from the `pyproject.toml` file
- Connects to the Neo4j container using the environment variables
- Exposes the server on port 8000 for HTTP-based SSE transport
- Includes a healthcheck for Neo4j to ensure it's fully operational before starting the MCP server
- Connects to the database container using the environment variables
- Exposes the server on port 8000 (Neo4j) or 8001 (FalkorDB) for HTTP-based SSE transport
- Includes healthchecks to ensure databases are fully operational before starting the MCP server
## Integrating with MCP Clients

View file

@ -0,0 +1,8 @@
{
"mcpServers": {
"graphiti": {
"transport": "sse",
"url": "http://localhost:8001/sse"
}
}
}

View file

@ -1,5 +1,6 @@
services:
neo4j:
profiles: [""]
image: neo4j:5.26.0
ports:
- "7474:7474" # HTTP
@ -19,7 +20,8 @@ services:
retries: 5
start_period: 30s
graphiti-mcp:
graphiti-mcp-neo4j:
profiles: [""]
image: zepai/knowledge-graph-mcp:latest
build:
context: .
@ -31,6 +33,8 @@ services:
neo4j:
condition: service_healthy
environment:
- PORT=8000
- DATABASE_TYPE=neo4j
- NEO4J_URI=${NEO4J_URI:-bolt://neo4j:7687}
- NEO4J_USER=${NEO4J_USER:-neo4j}
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-demodemo}
@ -42,6 +46,50 @@ services:
- "8000:8000" # Expose the MCP server via HTTP for SSE transport
command: ["uv", "run", "graphiti_mcp_server.py", "--transport", "sse"]
falkordb:
profiles: ["falkordb"]
image: falkordb/falkordb:latest
ports:
- "6379:6379"
command: ["falkordb-server", "--loadmodule", "/FalkorDB/bin/src/falkordb.so"]
volumes:
- falkordb_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
graphiti-mcp-falkordb:
profiles: ["falkordb"]
build:
args:
INSTALL_FALKORDB: "true"
context: .
dockerfile: Dockerfile
env_file:
- path: .env
required: false
depends_on:
falkordb:
condition: service_healthy
environment:
- PORT=8001
- DATABASE_TYPE=falkordb
- FALKORDB_HOST=falkordb
- FALKORDB_PORT=6379
- FALKORDB_USER=${FALKORDB_USER:-}
- FALKORDB_PASSWORD=${FALKORDB_PASSWORD:-}
- OPENAI_API_KEY=${OPENAI_API_KEY}
- MODEL_NAME=${MODEL_NAME}
- PATH=/root/.local/bin:${PATH}
- SEMAPHORE_LIMIT=${SEMAPHORE_LIMIT:-10}
ports:
- "8001:8001" # Expose the MCP server via HTTP for SSE transport
command: ["uv", "run", "graphiti_mcp_server.py", "--transport", "sse"]
volumes:
neo4j_data:
neo4j_logs:
falkordb_data:

View file

@ -10,15 +10,17 @@ import os
import sys
from collections.abc import Callable
from datetime import datetime, timezone
from typing import Any, TypedDict, cast
from typing import Any, cast
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from dotenv import load_dotenv
from mcp.server.fastmcp import FastMCP
from openai import AsyncAzureOpenAI
from pydantic import BaseModel, Field
from typing_extensions import TypedDict
from graphiti_core import Graphiti
from graphiti_core.driver.neo4j_driver import Neo4jDriver
from graphiti_core.edges import EntityEdge
from graphiti_core.embedder.azure_openai import AzureOpenAIEmbedderClient
from graphiti_core.embedder.client import EmbedderClient
@ -465,6 +467,21 @@ class Neo4jConfig(BaseModel):
password=os.environ.get('NEO4J_PASSWORD', 'password'),
)
class FalkorConfig(BaseModel):
"""Configuration for FalkorDB database connection."""
host: str = 'localhost'
port: int = 6379
user: str = ''
password: str = ''
@classmethod
def from_env(cls) -> 'FalkorConfig':
host = os.environ.get('FALKORDB_HOST', 'localhost')
port = int(os.environ.get('FALKORDB_PORT', 6379))
user = os.environ.get('FALKORDB_USER', '')
password = os.environ.get('FALKORDB_PASSWORD', '')
return cls(host=host, port=port, user=user, password=password)
class GraphitiConfig(BaseModel):
"""Configuration for Graphiti client.
@ -475,18 +492,35 @@ class GraphitiConfig(BaseModel):
llm: GraphitiLLMConfig = Field(default_factory=GraphitiLLMConfig)
embedder: GraphitiEmbedderConfig = Field(default_factory=GraphitiEmbedderConfig)
neo4j: Neo4jConfig = Field(default_factory=Neo4jConfig)
falkordb: FalkorConfig = Field(default_factory=FalkorConfig)
group_id: str | None = None
use_custom_entities: bool = False
destroy_graph: bool = False
database_type: str = 'neo4j'
@classmethod
def from_env(cls) -> 'GraphitiConfig':
"""Create a configuration instance from environment variables."""
return cls(
llm=GraphitiLLMConfig.from_env(),
embedder=GraphitiEmbedderConfig.from_env(),
neo4j=Neo4jConfig.from_env(),
)
db_type = os.environ.get('DATABASE_TYPE')
if not db_type:
raise ValueError('DATABASE_TYPE environment variable must be set (e.g., "neo4j" or "falkordb")')
if db_type == 'neo4j':
return cls(
llm=GraphitiLLMConfig.from_env(),
embedder=GraphitiEmbedderConfig.from_env(),
neo4j=Neo4jConfig.from_env(),
database_type=db_type,
)
elif db_type == 'falkordb':
return cls(
llm=GraphitiLLMConfig.from_env(),
embedder=GraphitiEmbedderConfig.from_env(),
falkordb=FalkorConfig.from_env(),
database_type=db_type,
)
else:
raise ValueError(f'Unsupported DATABASE_TYPE: {db_type}')
@classmethod
def from_cli_and_env(cls, args: argparse.Namespace) -> 'GraphitiConfig':
@ -587,13 +621,38 @@ async def initialize_graphiti():
if not config.neo4j.uri or not config.neo4j.user or not config.neo4j.password:
raise ValueError('NEO4J_URI, NEO4J_USER, and NEO4J_PASSWORD must be set')
# Validate FalkorDB configuration
if config.database_type == 'falkordb' and (not config.falkordb.host or not config.falkordb.port):
raise ValueError('FALKORDB_HOST and FALKORDB_PORT must be set for FalkorDB')
embedder_client = config.embedder.create_client()
# Construct the driver based on the database_type
driver = None
if config.database_type == 'neo4j':
driver = Neo4jDriver(
uri=config.neo4j.uri,
user=config.neo4j.user,
password=config.neo4j.password,
)
elif config.database_type == 'falkordb':
from graphiti_core.driver.falkordb_driver import FalkorDriver
host = config.falkordb.host if hasattr(config.falkordb, 'host') else 'localhost'
port = int(config.falkordb.port) if hasattr(config.falkordb, 'port') else 6379
username = config.falkordb.user or None
password = config.falkordb.password or None
driver = FalkorDriver(
host=host,
port=port,
username=username,
password=password,
)
else:
raise ValueError(f'Unsupported database type: {config.database_type}')
# Initialize Graphiti client
graphiti_client = Graphiti(
uri=config.neo4j.uri,
user=config.neo4j.user,
password=config.neo4j.password,
graph_driver=driver,
llm_client=llm_client,
embedder=embedder_client,
max_coroutines=SEMAPHORE_LIMIT,
@ -606,7 +665,7 @@ async def initialize_graphiti():
# Initialize the graph database with Graphiti's indices
await graphiti_client.build_indices_and_constraints()
logger.info('Graphiti client initialized successfully')
logger.info(f'Graphiti client initialized successfully with {config.database_type}')
# Log configuration details for transparency
if llm_client:
@ -616,6 +675,7 @@ async def initialize_graphiti():
logger.info('No LLM client configured - entity extraction will be limited')
logger.info(f'Using group_id: {config.group_id}')
logger.info(f'Using database type: {config.database_type}')
logger.info(
f'Custom entity extraction: {"enabled" if config.use_custom_entities else "disabled"}'
)
@ -1131,7 +1191,7 @@ async def clear_graph() -> SuccessResponse | ErrorResponse:
@mcp.resource('http://graphiti/status')
async def get_status() -> StatusResponse:
"""Get the status of the Graphiti MCP server and Neo4j connection."""
"""Get the status of the Graphiti MCP server and database connection."""
global graphiti_client
if graphiti_client is None:
@ -1145,14 +1205,14 @@ async def get_status() -> StatusResponse:
client = cast(Graphiti, graphiti_client)
# Test database connection
await client.driver.client.verify_connectivity() # type: ignore
await client.driver.health_check() # type: ignore # type: ignore
return StatusResponse(
status='ok', message='Graphiti MCP server is running and connected to Neo4j'
status='ok', message=f'Graphiti MCP server is running and connected to {config.database_type}'
)
except Exception as e:
error_msg = str(e)
logger.error(f'Error checking Neo4j connection: {error_msg}')
logger.error(f'Error checking {config.database_type} connection: {error_msg}')
return StatusResponse(
status='error',
message=f'Graphiti MCP server is running but Neo4j connection failed: {error_msg}',
@ -1200,6 +1260,17 @@ async def initialize_server() -> MCPConfig:
default=os.environ.get('MCP_SERVER_HOST'),
help='Host to bind the MCP server to (default: MCP_SERVER_HOST environment variable)',
)
parser.add_argument(
'--port',
type=int,
default=int(os.environ.get('PORT', 8000)),
help='Port to run the MCP server on (default: 8000 or value of PORT env variable)',
)
parser.add_argument(
'--database-type',
choices=['neo4j', 'falkordb'],
help='Type of database to use (default: neo4j)',
)
args = parser.parse_args()
@ -1226,6 +1297,11 @@ async def initialize_server() -> MCPConfig:
# Set MCP server host from CLI or env
mcp.settings.host = args.host
if args.port:
logger.info(f'Setting MCP server port to: {args.port}')
# Set MCP server port from CLI or env
mcp.settings.port = args.port
# Return MCP configuration
return MCPConfig.from_cli(args)

View file

@ -7,7 +7,9 @@ requires-python = ">=3.10,<4"
dependencies = [
"mcp>=1.5.0",
"openai>=1.68.2",
"graphiti-core>=0.14.0",
"graphiti-core==0.18.8",
"azure-identity>=1.21.0",
"graphiti-core",
]
[project.optional-dependencies]
falkordb = ["falkordb>=1.1.2,<2.0.0"]

892
mcp_server/uv.lock generated

File diff suppressed because it is too large Load diff

View file

@ -423,6 +423,14 @@
"created_at": "2025-10-15T16:29:33Z",
"repoId": 840056306,
"pullRequestNo": 1005
},
{
"name": "dontang97",
"id": 88384441,
"comment_id": 3431443627,
"created_at": "2025-10-22T09:52:01Z",
"repoId": 840056306,
"pullRequestNo": 1020
}
]
}