# Authentication Guide for Graphiti MCP Server
This guide explains how to configure and use nonce-based authentication for the Graphiti MCP Server.
## Overview
The Graphiti MCP Server supports nonce token authentication via query parameters. This provides a simple yet secure way to authenticate requests without the complexity of OAuth flows.
## ⚠️ SECURITY WARNING: Public Access Without Authentication
**Starting from this version, the server will REFUSE to start** if you attempt to bind to `0.0.0.0` (all network interfaces) without proper security configuration.
### Why This Matters
Binding to `0.0.0.0` makes your server accessible from **any network interface**, including:
- Your local network (LAN)
- Potentially the public internet (if not behind a firewall)
Running without authentication in this configuration would allow **anyone** to:
- Read all data from your knowledge graph
- Add, modify, or delete data
- Clear your entire graph
### What You Need To Do
If you want to run the server publicly accessible, you **must** do ONE of the following:
| Option | Environment Variable | Security Level |
|--------|---------------------|----------------|
| **1. Enable authentication** | `MCP_SERVER_NONCE_TOKENS=your-secret-token` | ✅ Recommended |
| **2. Restrict allowed hosts** | `ALLOWED_HOSTS=your-domain.com` | ✅ Good |
| **3. Explicit opt-out** | `ALLOW_UNAUTHENTICATED_PUBLIC_ACCESS=true` | ⚠️ **DANGEROUS** |
### For Local Development
If you only need local access, use `--host 127.0.0.1` instead of `0.0.0.0`:
```bash
python -m graphiti_mcp_server --host 127.0.0.1 --transport sse
```
This binds only to localhost and does **not** require authentication configuration.
### If You Really Need Public Access Without Authentication
**This is NOT RECOMMENDED**, but if you understand the risks and explicitly want to run without security:
```bash
export ALLOW_UNAUTHENTICATED_PUBLIC_ACCESS=true
python -m graphiti_mcp_server --host 0.0.0.0 --transport sse
```
You will see these warnings in the logs:
```
WARNING - Host is 0.0.0.0 but ALLOWED_HOSTS not set - disabling DNS rebinding protection
WARNING - SECURITY WARNING: Running with ALLOW_UNAUTHENTICATED_PUBLIC_ACCESS=true. Server accepts requests from ANY source without authentication!
```
---
## Prerequisites
### Dependencies
The authentication feature requires `fastapi` and `uvicorn`, which are included in the `pyproject.toml`:
```toml
dependencies = [
# ... other dependencies
"fastapi>=0.115.0",
"uvicorn>=0.32.1"
]
```
### Installation
**With venv (local development):**
```bash
cd mcp_server
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -r requirements.txt
# Or if using pip with pyproject.toml:
pip install -e .
```
**With Docker:**
The Dockerfile automatically installs all dependencies from `pyproject.toml`.
## Configuration
### Environment Variable Setup
Set the `MCP_SERVER_NONCE_TOKENS` environment variable with comma-separated token values:
```bash
export MCP_SERVER_NONCE_TOKENS="token1,token2,token3"
```
**Example with real tokens:**
```bash
export MCP_SERVER_NONCE_TOKENS="ShjDhieHfxxxxxx,AnotherSecretToken123,MyToken456"
```
### Generating Secure Tokens
You can generate secure random tokens using Python:
```bash
python3 -c "import secrets; print(secrets.token_hex(32))"
```
Or use this command to generate multiple tokens:
```bash
for i in {1..3}; do python3 -c "import secrets; print(secrets.token_hex(32))"; done
```
### Docker Setup
If using Docker Compose, add the environment variable to your `docker-compose.yml`:
```yaml
services:
mcp-server:
environment:
- MCP_SERVER_NONCE_TOKENS=token1,token2,token3
- NEO4J_URI=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=password
- OPENAI_API_KEY=${OPENAI_API_KEY}
```
Or use an `.env` file:
```bash
# .env file
MCP_SERVER_NONCE_TOKENS=ShjDhieHfxxxxxx,AnotherSecretToken123,MyToken456
NEO4J_URI=bolt://neo4j:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=password
OPENAI_API_KEY=your-openai-api-key
```
## Usage
### Starting the Server
Start the MCP server with HTTP-based transport (required for HTTP authentication):
```bash
cd mcp_server
# Using Streamable HTTP transport (MCP 2025-06-18 standard, recommended)
python3 graphiti_mcp_server.py --transport streamable-http
# Or using legacy SSE transport
python3 graphiti_mcp_server.py --transport sse
```
**With authentication enabled**, the server will log:
```
INFO - 🔒 AUTHENTICATION ENABLED: Loaded 3 nonce token(s) for authentication
INFO - 🔒 Requests must include valid nonce token (?nonce=<token>)
INFO - 🔒 Wrapping Streamable HTTP app with authentication middleware
INFO - Running MCP server with Streamable HTTP transport on 0.0.0.0:8000/mcp
```
**Without authentication** (MCP_SERVER_NONCE_TOKENS not set), you'll see:
```
WARNING - ⚠️ AUTHENTICATION DISABLED: MCP_SERVER_NONCE_TOKENS not configured
WARNING - ⚠️ Server will accept ALL requests without authentication!
INFO - Running MCP server with Streamable HTTP transport on 0.0.0.0:8000/mcp
```
### Making Authenticated Requests
Add the `nonce` query parameter to your requests:
```bash
# Using Streamable HTTP transport (MCP 2025-06-18)
curl "http://localhost:8000/mcp?nonce=your-token-here"
# Using legacy SSE transport
curl "http://localhost:8000/sse?nonce=your-token-here"
```
```bash
# Example with a real token
curl "http://localhost:8000/mcp?nonce=ShjDhieHfxxxxxx"
```
### Authentication Logging
Each authentication attempt is logged to help you monitor access.
**Server startup logs** (authentication enabled):
```
INFO - 🔒 AUTHENTICATION ENABLED: Loaded 2 nonce token(s) for authentication
INFO - 🔒 Requests must include valid nonce token (?nonce=<token>)
INFO - 🔒 Wrapping Streamable HTTP app with authentication middleware
DEBUG - 🔍 Streamable HTTP app type: <class 'starlette.applications.Starlette'>
DEBUG - 🔍 Streamable HTTP app id: 140234567890123
INFO - 🔒 Middleware registered. App has 1 middleware(s)
INFO - Running MCP server with Streamable HTTP transport on 0.0.0.0:8000/mcp
```
**Per-request logging** (when DEBUG level enabled):
```
DEBUG - 🔍 MIDDLEWARE CALLED: GET /mcp
INFO - ✓ Authentication successful with nonce token
```
**Failed authentication (invalid token):**
```
DEBUG - 🔍 MIDDLEWARE CALLED: POST /messages/
WARNING - ✗ Authentication failed: Invalid nonce token provided
WARNING - 🔍 MIDDLEWARE BLOCKED: POST /messages/ - Invalid nonce token
```
**Failed authentication (missing token):**
```
DEBUG - 🔍 MIDDLEWARE CALLED: GET /mcp
WARNING - ✗ Authentication failed: No nonce token provided
WARNING - 🔍 MIDDLEWARE BLOCKED: GET /mcp - Not authenticated
```
### MCP Client Configuration
Configure your MCP client to include the nonce token in requests. Example for Claude Desktop or similar MCP clients:
```json
{
"mcpServers": {
"graphiti": {
"url": "http://localhost:8000/mcp?nonce=ShjDhieHfxxxxxx",
"transport": "streamable-http"
}
}
}
```
For legacy SSE transport:
```json
{
"mcpServers": {
"graphiti": {
"url": "http://localhost:8000/sse?nonce=ShjDhieHfxxxxxx",
"transport": "sse"
}
}
}
```
## Security Features
### Middleware-Based Authentication
The authentication is implemented as **Starlette middleware** that intercepts HTTP requests before they reach endpoints. This ensures:
- The initial connection (`/mcp` for Streamable HTTP or `/sse` for legacy SSE) requires authentication via nonce token
- Internal MCP endpoints (`/messages/`, `/register`, etc.) are protected by session management
- Early rejection of unauthorized requests
**How it works:**
1. Client connects to `/mcp?nonce=<token>` (Streamable HTTP) or `/sse?nonce=<token>` (legacy SSE) - **Authentication required**
2. If valid, a session is established
3. Subsequent requests to `/messages/`, `/register` use the authenticated session
4. No need to include nonce on every request after initial connection
### X-Group-Id Header Support
The middleware also supports extracting `group_id` values from the `X-Group-Id` HTTP header. The header supports comma-separated values which act as an **allowlist** for permitted group_ids.
**Single group_id:**
- The header value becomes the **fixed group_id** for all tool calls
- Any `group_id` passed in tool call parameters will be **ignored**
**Multiple group_ids (comma-separated):**
- Only these group_ids are **permitted** for tool calls
- Tool parameters that match an allowed group_id are accepted
- Tool parameters not in the allowlist are **rejected** with an error message showing the allowed group_ids
- If no tool parameter is provided, the first allowed group_id is used
This enables secure multi-tenant deployments where the allowed group_ids are set by the infrastructure.
**Error messages** include the allowed group_ids when a request is rejected:
```
group_id 'wrong-tenant' is not permitted. Allowed group_ids: ['tenant-a', 'tenant-b']
```
**Example with single group_id:**
```bash
# Using Streamable HTTP transport (MCP 2025-06-18)
curl "http://localhost:8000/mcp?nonce=your-token" \
-H "X-Group-Id: tenant-123"
# Using legacy SSE transport
curl "http://localhost:8000/sse?nonce=your-token" \
-H "X-Group-Id: tenant-123"
```
**Example with multiple allowed group_ids:**
```bash
curl "http://localhost:8000/mcp?nonce=your-token" \
-H "X-Group-Id: tenant-a, tenant-b, tenant-c"
```
**Priority order with single group_id:**
1. Header group_id is always used (tool parameter ignored)
**Priority order with multiple group_ids (allowlist):**
1. Tool parameter (if in allowlist)
2. CLI default (if in allowlist)
3. First entry in allowlist (fallback)
**Priority order without header:**
1. Tool parameter
2. CLI default `--group-id`
3. Empty string (fallback)
### Constant-Time Comparison
The implementation uses `secrets.compare_digest()` to prevent timing attacks when validating tokens. This ensures that token comparison takes the same amount of time regardless of whether the token matches or not.
### Multiple Token Support
You can configure multiple tokens for different clients or purposes:
```bash
export MCP_SERVER_NONCE_TOKENS="client1-token,client2-token,admin-token"
```
Each token is validated independently, and any valid token will authenticate the request.
## Authentication Behavior
### When Tokens Are Configured
If `MCP_SERVER_NONCE_TOKENS` is set:
- Requests **must** include a valid `nonce` query parameter
- Requests without a nonce or with an invalid nonce receive `401 Unauthorized`
- Valid nonce grants access with `client_id: "nonce:<token>"`
### When Tokens Are Not Configured
If `MCP_SERVER_NONCE_TOKENS` is not set or empty:
- **Authentication is bypassed** (backward compatible)
- All requests are allowed
- Client ID is set to `"unauthenticated"`
## Error Responses
### Invalid Nonce Token
**Request:**
```bash
curl "http://localhost:8000/sse?nonce=invalid-token"
```
**Response:**
```json
{
"error": "Invalid nonce token"
}
```
Status Code: `401 Unauthorized`
### Missing Authentication
**Request:**
```bash
curl "http://localhost:8000/sse"
```
**Response (when tokens are configured):**
```json
{
"error": "Not authenticated"
}
```
Status Code: `401 Unauthorized`
## Testing Authentication
### Test Token Validation
```bash
# Set test tokens
export MCP_SERVER_NONCE_TOKENS="test-token-123,test-token-456"
# Start server
python3 graphiti_mcp_server.py --transport sse
# Test valid token
curl "http://localhost:8000/sse?nonce=test-token-123"
# Expected: Connection established
# Test invalid token
curl "http://localhost:8000/sse?nonce=wrong-token"
# Expected: 401 Unauthorized
# Test missing token
curl "http://localhost:8000/sse"
# Expected: 401 Unauthorized
```
## Best Practices
1. **Use Strong Tokens**: Generate cryptographically secure random tokens (minimum 32 characters)
2. **Rotate Tokens Regularly**: Update tokens periodically for enhanced security
3. **Limit Token Distribution**: Only share tokens with authorized clients
4. **Use HTTPS in Production**: Always use HTTPS/TLS to encrypt tokens in transit
5. **Environment Variables**: Never commit tokens to version control; use environment variables
6. **Separate Tokens per Client**: Use different tokens for different clients to enable selective revocation
## Troubleshooting
### Middleware Not Being Called
**Issue:** Requests bypass authentication (no middleware logs)
**Symptoms:**
```
# You see this:
INFO - Running MCP server with SSE transport on 0.0.0.0:8000
# But NOT this:
DEBUG - 🔍 MIDDLEWARE CALLED: GET /sse
```
**Solution:**
1. Check that `MCP_SERVER_NONCE_TOKENS` is set:
```bash
echo $MCP_SERVER_NONCE_TOKENS
```
2. Verify transport is SSE (not stdio):
```bash
python graphiti_mcp_server.py --transport sse
```
3. Enable DEBUG logging to see middleware calls:
- The code now uses `logging.DEBUG` by default
- Check logs for "🔍 MIDDLEWARE CALLED" messages
4. Verify middleware registration:
```
# Should see in logs:
INFO - 🔒 Registering authentication middleware on SSE app
INFO - 🔒 Middleware registered. App has 1 middleware(s)
```
### Server Doesn't Start
**Issue:** Server fails to start with authentication errors
**Solution:** Check that environment variables are properly set:
```bash
echo $MCP_SERVER_NONCE_TOKENS
```
### Authentication Always Fails
**Issue:** Valid tokens are rejected
**Common causes:**
1. **Docker Compose Environment Variable Quotes**
```yaml
# ❌ WRONG - includes quotes in the token value
- MCP_SERVER_NONCE_TOKENS="token123"
# ✅ CORRECT - no quotes
- MCP_SERVER_NONCE_TOKENS=token123
```
2. **Token Whitespace/Format Issues**
- Verify token has no extra whitespace
- Check URL encoding of special characters
- Ensure token matches exactly (case-sensitive)
3. **Debugging Steps**
```bash
# Inside the container, check what token was actually set
docker exec <container> env | grep MCP_SERVER_NONCE_TOKENS
# Should output EXACTLY the token without quotes:
# MCP_SERVER_NONCE_TOKENS=token123
# NOT: MCP_SERVER_NONCE_TOKENS="token123"
```
4. **Confirm Middleware Is Active**
- Look for "🔍 MIDDLEWARE CALLED" in DEBUG logs
- If you see this, middleware is working
- If token still fails, it's a token mismatch issue
### No Authentication Required
**Issue:** Server accepts requests without tokens
**Solution:** This is expected behavior when `MCP_SERVER_NONCE_TOKENS` is not set. Set the environment variable to enable authentication.
## Example: Complete Setup
### Local Setup
```bash
# 1. Generate secure tokens
TOKEN1=$(python3 -c "import secrets; print(secrets.token_hex(32))")
TOKEN2=$(python3 -c "import secrets; print(secrets.token_hex(32))")
# 2. Configure environment
export MCP_SERVER_NONCE_TOKENS="$TOKEN1,$TOKEN2"
export NEO4J_URI=bolt://localhost:7687
export NEO4J_USER=neo4j
export NEO4J_PASSWORD=password
export OPENAI_API_KEY=your-openai-api-key
# 3. Start server
cd mcp_server
python3 graphiti_mcp_server.py --transport sse --group-id my-app
# 4. Test connection
curl "http://localhost:8000/sse?nonce=$TOKEN1"
# 5. Save token for client configuration
echo "Your MCP client URL: http://localhost:8000/sse?nonce=$TOKEN1"
```
### Docker Setup with Authentication
```bash
# 1. Generate secure token
TOKEN=$(python3 -c "import secrets; print(secrets.token_hex(32))")
# 2. Build Docker image
cd mcp_server
docker build -f Dockerfile.new -t graphiti-mcp-server .
# 3. Run with authentication
docker run -d \
--name graphiti-mcp \
-p 8000:8000 \
-e MCP_SERVER_NONCE_TOKENS="$TOKEN" \
-e NEO4J_URI=bolt://neo4j:7687 \
-e NEO4J_USER=neo4j \
-e NEO4J_PASSWORD=password \
-e OPENAI_API_KEY=your-openai-api-key \
graphiti-mcp-server
# 4. Check logs to verify authentication is enabled
docker logs graphiti-mcp
# Expected output:
# INFO - 🔒 AUTHENTICATION ENABLED: Loaded 1 nonce token(s) for authentication
# INFO - 🔒 Requests must include valid nonce token (?nonce=<token>)
# INFO - Running MCP server with SSE transport on 0.0.0.0:8000
# 5. Test authentication
curl "http://localhost:8000/sse?nonce=$TOKEN"
# 6. Test without token (should fail)
curl "http://localhost:8000/sse"
# Expected: 401 Unauthorized
```
## Additional Resources
- [FastAPI Security Documentation](https://fastapi.tiangolo.com/tutorial/security/)
- [Python Secrets Module](https://docs.python.org/3/library/secrets.html)
- [MCP Protocol Specification](https://modelcontextprotocol.io/)