feat(mcp_server): Add FastMCP Cloud deployment support
Add tooling and configuration for deploying the Graphiti MCP server to FastMCP Cloud with Neo4j or FalkorDB backends. Changes: - Add DATABASE_PROVIDER env var support in config.yaml for runtime database selection (neo4j or falkordb) - Add EMBEDDING_DIM, EMBEDDER_PROVIDER, EMBEDDER_MODEL env var support for embedder configuration - Add python-dotenv and pydantic to pyproject.toml dependencies - Bump version to 1.0.2 - Rewrite .env.example with comprehensive documentation for both local development and FastMCP Cloud deployment - Add verification script (scripts/verify_fastmcp_cloud_readiness.py) that checks 6 deployment prerequisites - Add deployment guide (docs/FASTMCP_CLOUD_DEPLOYMENT.md) The server can now be configured entirely via environment variables, making it compatible with FastMCP Cloud which ignores config files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
dc6b65ae85
commit
4ab47c097e
5 changed files with 914 additions and 32 deletions
|
|
@ -1,25 +1,99 @@
|
|||
# Graphiti MCP Server Environment Configuration
|
||||
# =============================================
|
||||
#
|
||||
# For LOCAL development: Copy this file to .env and fill in your values
|
||||
# For FASTMCP CLOUD: Set these in the FastMCP Cloud UI (NOT in .env files)
|
||||
#
|
||||
# FastMCP Cloud ignores .env files - you MUST set secrets in the Cloud UI
|
||||
|
||||
# Neo4j Database Configuration
|
||||
# These settings are used to connect to your Neo4j database
|
||||
# =============================================================================
|
||||
# DATABASE CONFIGURATION (Choose ONE provider)
|
||||
# =============================================================================
|
||||
|
||||
# Database Provider Selection
|
||||
# Options: neo4j, falkordb
|
||||
DATABASE_PROVIDER=neo4j
|
||||
|
||||
# --- Neo4j Configuration ---
|
||||
# For local development: bolt://localhost:7687
|
||||
# For Neo4j Aura (cloud): neo4j+s://xxxxx.databases.neo4j.io
|
||||
NEO4J_URI=bolt://localhost:7687
|
||||
NEO4J_USER=neo4j
|
||||
NEO4J_PASSWORD=demodemo
|
||||
NEO4J_PASSWORD=your_neo4j_password_here
|
||||
NEO4J_DATABASE=neo4j
|
||||
|
||||
# OpenAI API Configuration
|
||||
# Required for LLM operations
|
||||
# --- FalkorDB Configuration ---
|
||||
# For local development: redis://localhost:6379
|
||||
# For FalkorDB Cloud: redis://username:password@host:port
|
||||
# FALKORDB_URI=redis://localhost:6379
|
||||
# FALKORDB_PASSWORD=
|
||||
# FALKORDB_DATABASE=default_db
|
||||
# FALKORDB_USER=
|
||||
|
||||
# =============================================================================
|
||||
# LLM PROVIDER CONFIGURATION (Required)
|
||||
# =============================================================================
|
||||
|
||||
# OpenAI (Default)
|
||||
OPENAI_API_KEY=your_openai_api_key_here
|
||||
MODEL_NAME=gpt-4.1-mini
|
||||
|
||||
# Optional: Only needed for non-standard OpenAI endpoints
|
||||
# OPENAI_BASE_URL=https://api.openai.com/v1
|
||||
# Optional: Override default model
|
||||
# LLM_MODEL=gpt-4.1-mini
|
||||
|
||||
# Optional: Group ID for namespacing graph data
|
||||
# GROUP_ID=my_project
|
||||
# --- Alternative LLM Providers ---
|
||||
|
||||
# Anthropic
|
||||
# ANTHROPIC_API_KEY=sk-ant-...
|
||||
|
||||
# Google Gemini
|
||||
# GOOGLE_API_KEY=...
|
||||
# GOOGLE_PROJECT_ID=
|
||||
# GOOGLE_LOCATION=us-central1
|
||||
|
||||
# Groq
|
||||
# GROQ_API_KEY=...
|
||||
|
||||
# Azure OpenAI
|
||||
# AZURE_OPENAI_API_KEY=...
|
||||
# AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
|
||||
# AZURE_OPENAI_DEPLOYMENT=your-deployment-name
|
||||
# AZURE_OPENAI_API_VERSION=2024-10-21
|
||||
# USE_AZURE_AD=false
|
||||
|
||||
# =============================================================================
|
||||
# EMBEDDER CONFIGURATION (Optional - defaults to OpenAI)
|
||||
# =============================================================================
|
||||
|
||||
# Voyage AI (recommended by Anthropic for Claude integrations)
|
||||
# VOYAGE_API_KEY=...
|
||||
# Note: Voyage AI uses 1024 dimensions by default
|
||||
|
||||
# Embedding dimensions (must match your embedding model)
|
||||
# OpenAI text-embedding-3-small: 1536
|
||||
# Voyage AI voyage-3: 1024
|
||||
# EMBEDDING_DIM=1536
|
||||
|
||||
# =============================================================================
|
||||
# GRAPHITI CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
# Group ID for namespacing graph data
|
||||
GRAPHITI_GROUP_ID=main
|
||||
|
||||
# User ID for tracking operations
|
||||
USER_ID=mcp_user
|
||||
|
||||
# Episode ID prefix (optional)
|
||||
# EPISODE_ID_PREFIX=
|
||||
|
||||
# =============================================================================
|
||||
# PERFORMANCE TUNING
|
||||
# =============================================================================
|
||||
|
||||
# Concurrency Control
|
||||
# Controls how many episodes can be processed simultaneously
|
||||
# Default: 10 (suitable for OpenAI Tier 3, mid-tier Anthropic)
|
||||
#
|
||||
# Adjust based on your LLM provider's rate limits:
|
||||
# - OpenAI Tier 1 (free): 1-2
|
||||
# - OpenAI Tier 2: 5-8
|
||||
|
|
@ -27,23 +101,28 @@ MODEL_NAME=gpt-4.1-mini
|
|||
# - OpenAI Tier 4: 20-50
|
||||
# - Anthropic default: 5-8
|
||||
# - Anthropic high tier: 15-30
|
||||
# - Ollama (local): 1-5
|
||||
#
|
||||
# See README.md "Concurrency and LLM Provider 429 Rate Limit Errors" for details
|
||||
SEMAPHORE_LIMIT=10
|
||||
|
||||
# Optional: Path configuration for Docker
|
||||
# PATH=/root/.local/bin:${PATH}
|
||||
|
||||
# Optional: Memory settings for Neo4j (used in Docker Compose)
|
||||
# NEO4J_server_memory_heap_initial__size=512m
|
||||
# NEO4J_server_memory_heap_max__size=1G
|
||||
# NEO4J_server_memory_pagecache_size=512m
|
||||
|
||||
# Azure OpenAI configuration
|
||||
# Optional: Only needed for Azure OpenAI endpoints
|
||||
# AZURE_OPENAI_ENDPOINT=your_azure_openai_endpoint_here
|
||||
# AZURE_OPENAI_API_VERSION=2025-01-01-preview
|
||||
# AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4o-gpt-4o-mini-deployment
|
||||
# AZURE_OPENAI_EMBEDDING_API_VERSION=2023-05-15
|
||||
# AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME=text-embedding-3-large-deployment
|
||||
# AZURE_OPENAI_USE_MANAGED_IDENTITY=false
|
||||
# =============================================================================
|
||||
# FASTMCP CLOUD DEPLOYMENT CHECKLIST
|
||||
# =============================================================================
|
||||
# When deploying to FastMCP Cloud, set these in the Cloud UI:
|
||||
#
|
||||
# REQUIRED:
|
||||
# - OPENAI_API_KEY (or your chosen LLM provider key)
|
||||
# - Database credentials (Neo4j or FalkorDB)
|
||||
# - For Neo4j Aura: NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD
|
||||
# - For FalkorDB Cloud: FALKORDB_URI, FALKORDB_USER, FALKORDB_PASSWORD
|
||||
#
|
||||
# OPTIONAL:
|
||||
# - DATABASE_PROVIDER (defaults to falkordb)
|
||||
# - SEMAPHORE_LIMIT (defaults to 10)
|
||||
# - GRAPHITI_GROUP_ID (defaults to main)
|
||||
#
|
||||
# IMPORTANT: FastMCP Cloud IGNORES:
|
||||
# - .env files
|
||||
# - config.yaml files
|
||||
# - if __name__ == "__main__" blocks
|
||||
# =============================================================================
|
||||
|
|
|
|||
|
|
@ -43,9 +43,9 @@ llm:
|
|||
api_url: ${GROQ_API_URL:https://api.groq.com/openai/v1}
|
||||
|
||||
embedder:
|
||||
provider: "openai" # Options: openai, azure_openai, gemini, voyage
|
||||
model: "text-embedding-3-small"
|
||||
dimensions: 1536
|
||||
provider: ${EMBEDDER_PROVIDER:openai} # Options: openai, azure_openai, gemini, voyage
|
||||
model: ${EMBEDDER_MODEL:text-embedding-3-small}
|
||||
dimensions: ${EMBEDDING_DIM:1536} # OpenAI: 1536, Voyage: 1024
|
||||
|
||||
providers:
|
||||
openai:
|
||||
|
|
@ -71,7 +71,7 @@ embedder:
|
|||
model: "voyage-3"
|
||||
|
||||
database:
|
||||
provider: "falkordb" # Default: falkordb. Options: neo4j, falkordb
|
||||
provider: ${DATABASE_PROVIDER:falkordb} # Options: neo4j, falkordb. Set DATABASE_PROVIDER env var to override
|
||||
|
||||
providers:
|
||||
falkordb:
|
||||
|
|
|
|||
451
mcp_server/docs/FASTMCP_CLOUD_DEPLOYMENT.md
Normal file
451
mcp_server/docs/FASTMCP_CLOUD_DEPLOYMENT.md
Normal file
|
|
@ -0,0 +1,451 @@
|
|||
# FastMCP Cloud Deployment Guide
|
||||
|
||||
This guide covers deploying the Graphiti MCP server to FastMCP Cloud, a managed hosting platform for MCP servers.
|
||||
|
||||
## Overview
|
||||
|
||||
FastMCP Cloud is a managed platform that:
|
||||
- Automatically builds and deploys your MCP server from GitHub
|
||||
- Provides a unique HTTPS URL for your server
|
||||
- Handles SSL certificates and authentication
|
||||
- Auto-redeploys on pushes to `main` branch
|
||||
- Creates preview deployments for pull requests
|
||||
|
||||
**Note:** FastMCP Cloud is currently **free while in beta**.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before deploying to FastMCP Cloud, you need:
|
||||
|
||||
1. **GitHub Account** - FastMCP Cloud integrates with GitHub repos
|
||||
2. **Cloud Database** - Neo4j Aura or FalkorDB Cloud (must be internet-accessible)
|
||||
3. **API Keys** - OpenAI (required), Anthropic (optional)
|
||||
4. **Verified Repo** - Run the verification script first
|
||||
|
||||
### Pre-Deployment Verification
|
||||
|
||||
Run the verification script to check your server is ready:
|
||||
|
||||
```bash
|
||||
cd mcp_server
|
||||
uv run python scripts/verify_fastmcp_cloud_readiness.py
|
||||
```
|
||||
|
||||
This checks:
|
||||
- Server is discoverable via `fastmcp inspect`
|
||||
- Dependencies are properly declared in `pyproject.toml`
|
||||
- Environment variables are documented
|
||||
- No secrets are committed to git
|
||||
- Server can be imported successfully
|
||||
- Entrypoint format is correct
|
||||
|
||||
All checks should pass before deploying.
|
||||
|
||||
## Deployment Steps
|
||||
|
||||
### Step 0: Validate Your Server Locally
|
||||
|
||||
**Before deploying to FastMCP Cloud, validate with BOTH static and runtime checks.**
|
||||
|
||||
#### Static Validation: `fastmcp inspect`
|
||||
|
||||
Checks that your server module can be imported and tools are registered:
|
||||
|
||||
```bash
|
||||
cd mcp_server
|
||||
uv run fastmcp inspect src/graphiti_mcp_server.py:mcp
|
||||
```
|
||||
|
||||
**Expected successful output:**
|
||||
|
||||
```
|
||||
Name: Graphiti Agent Memory
|
||||
Version: <version>
|
||||
Tools: 9 found
|
||||
- add_memory: Add an episode to memory
|
||||
- search_nodes: Search for nodes in the graph memory
|
||||
- search_memory_facts: Search the graph memory for relevant facts
|
||||
- get_episodes: Get episodes from the graph memory
|
||||
- get_entity_edge: Get an entity edge from the graph memory by its UUID
|
||||
- delete_episode: Delete an episode from the graph memory
|
||||
- delete_entity_edge: Delete an entity edge from the graph memory
|
||||
- clear_graph: Clear all data from the graph for specified group IDs
|
||||
- get_status: Get the status of the Graphiti MCP server
|
||||
```
|
||||
|
||||
#### Runtime Validation: `fastmcp dev` (ESSENTIAL!)
|
||||
|
||||
**The `inspect` command only checks imports - it does NOT catch runtime initialization issues.**
|
||||
|
||||
Run the interactive inspector to test actual server initialization:
|
||||
|
||||
```bash
|
||||
cd mcp_server
|
||||
uv run fastmcp dev src/graphiti_mcp_server.py:mcp
|
||||
```
|
||||
|
||||
This starts your server and opens an interactive web UI at `http://localhost:6274`.
|
||||
|
||||
**Critical test in the web UI:**
|
||||
|
||||
1. Open `http://localhost:6274` in your browser
|
||||
2. Click the "get_status" tool
|
||||
3. Click "Execute"
|
||||
4. **Expected:** `{"status": "ok", "message": "Graphiti MCP server is running and connected to Neo4j database"}`
|
||||
5. **If you see:** `{"status": "error", "message": "Graphiti service not initialized"}` - **DO NOT DEPLOY**
|
||||
|
||||
### Step 1: Set Up Cloud Database
|
||||
|
||||
#### Option A: Neo4j Aura (Recommended for Neo4j users)
|
||||
|
||||
1. Visit [Neo4j Aura](https://neo4j.com/cloud/aura/)
|
||||
2. Create a free instance
|
||||
3. Note your connection details:
|
||||
- URI: `neo4j+s://xxxxx.databases.neo4j.io`
|
||||
- Username: `neo4j`
|
||||
- Password: (generated)
|
||||
|
||||
#### Option B: FalkorDB Cloud
|
||||
|
||||
1. Visit [FalkorDB Cloud](https://cloud.falkordb.com)
|
||||
2. Create an instance
|
||||
3. Note your connection details:
|
||||
- URI: `redis://username:password@host:port`
|
||||
- Database: `default_db`
|
||||
|
||||
**Important:** Local databases (localhost) will NOT work with FastMCP Cloud. You must use a cloud-hosted database.
|
||||
|
||||
### Step 2: Prepare Your Repository
|
||||
|
||||
1. **Ensure `pyproject.toml` is complete**
|
||||
|
||||
FastMCP Cloud automatically detects dependencies from `pyproject.toml`:
|
||||
```toml
|
||||
[project]
|
||||
dependencies = [
|
||||
"fastmcp>=2.13.3",
|
||||
"graphiti-core[falkordb]>=0.23.1",
|
||||
"pydantic>=2.0.0",
|
||||
"pydantic-settings>=2.0.0",
|
||||
"python-dotenv>=1.0.0",
|
||||
# ... other dependencies
|
||||
]
|
||||
```
|
||||
|
||||
2. **Verify `.env` is in `.gitignore`**
|
||||
|
||||
```bash
|
||||
git check-ignore -v .env
|
||||
# Should output: .gitignore:XX:.env .env
|
||||
```
|
||||
|
||||
3. **Commit and push your code**
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Prepare for FastMCP Cloud deployment"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### Step 3: Create FastMCP Cloud Project
|
||||
|
||||
1. **Visit [fastmcp.cloud](https://fastmcp.cloud)**
|
||||
|
||||
2. **Sign in with your GitHub account**
|
||||
|
||||
3. **Create a new project:**
|
||||
- Click "Create Project"
|
||||
- Select your repository
|
||||
- Repository can be public or private
|
||||
|
||||
4. **Configure project settings:**
|
||||
|
||||
| Setting | Value | Notes |
|
||||
|---------|-------|-------|
|
||||
| **Name** | `graphiti-mcp` | Used in your deployment URL |
|
||||
| **Entrypoint** | `mcp_server/src/graphiti_mcp_server.py:mcp` | Points to module-level server instance |
|
||||
| **Authentication** | Enabled | Recommended for production |
|
||||
|
||||
**Important:** The entrypoint must point to a **module-level** `FastMCP` instance.
|
||||
|
||||
### Step 4: Configure Environment Variables
|
||||
|
||||
Set these environment variables in the FastMCP Cloud UI (**NOT** in `.env` files):
|
||||
|
||||
#### For Neo4j:
|
||||
|
||||
```bash
|
||||
# Required
|
||||
OPENAI_API_KEY=sk-...
|
||||
NEO4J_URI=neo4j+s://xxxxx.databases.neo4j.io
|
||||
NEO4J_USER=neo4j
|
||||
NEO4J_PASSWORD=your-password
|
||||
DATABASE_PROVIDER=neo4j
|
||||
|
||||
# Optional
|
||||
SEMAPHORE_LIMIT=10
|
||||
GRAPHITI_GROUP_ID=main
|
||||
```
|
||||
|
||||
#### For FalkorDB:
|
||||
|
||||
```bash
|
||||
# Required
|
||||
OPENAI_API_KEY=sk-...
|
||||
FALKORDB_URI=redis://host:port
|
||||
FALKORDB_USER=your-username
|
||||
FALKORDB_PASSWORD=your-password
|
||||
FALKORDB_DATABASE=default_db
|
||||
DATABASE_PROVIDER=falkordb
|
||||
|
||||
# Optional
|
||||
SEMAPHORE_LIMIT=10
|
||||
GRAPHITI_GROUP_ID=main
|
||||
```
|
||||
|
||||
**Security Note:** Environment variables set in FastMCP Cloud UI are encrypted at rest and never logged.
|
||||
|
||||
### Step 5: Deploy
|
||||
|
||||
1. **Click "Deploy"**
|
||||
|
||||
FastMCP Cloud will:
|
||||
1. Clone your repository
|
||||
2. Detect dependencies from `pyproject.toml`
|
||||
3. Install dependencies using `uv`
|
||||
4. Build your FastMCP server
|
||||
5. Deploy to a unique URL
|
||||
6. Make it immediately available
|
||||
|
||||
2. **Monitor the build**
|
||||
|
||||
Watch the build logs in the FastMCP Cloud UI. The build typically takes 2-5 minutes.
|
||||
|
||||
3. **Note your deployment URL**
|
||||
|
||||
Your server will be accessible at:
|
||||
```
|
||||
https://your-project-name.fastmcp.app/mcp
|
||||
```
|
||||
|
||||
### Step 6: Verify Deployment
|
||||
|
||||
1. **Test with `fastmcp inspect`**
|
||||
|
||||
```bash
|
||||
fastmcp inspect https://your-project-name.fastmcp.app/mcp
|
||||
```
|
||||
|
||||
You should see your server info and 9 tools.
|
||||
|
||||
2. **Connect from Claude Desktop**
|
||||
|
||||
FastMCP Cloud provides auto-generated configuration. Click "Connect" in the UI and copy the configuration.
|
||||
|
||||
3. **Test add_memory tool**
|
||||
|
||||
Use Claude Desktop or an MCP client to test:
|
||||
```
|
||||
Add a memory: "John prefers dark mode UI"
|
||||
```
|
||||
|
||||
## Configuration Differences
|
||||
|
||||
### FastMCP Cloud vs Local Development
|
||||
|
||||
| Aspect | FastMCP Cloud | Local Development |
|
||||
|--------|---------------|-------------------|
|
||||
| **Entry point** | Module-level instance only | `if __name__ == "__main__"` runs |
|
||||
| **Dependencies** | Auto-detected from `pyproject.toml` | Installed via `uv sync` |
|
||||
| **Environment** | Set in Cloud UI | Loaded from `.env` file |
|
||||
| **Transport** | Managed by platform | Configured via CLI args |
|
||||
| **HTTPS** | Automatic | Manual setup |
|
||||
| **Authentication** | Built-in OAuth | Configure manually |
|
||||
|
||||
### What Gets Ignored
|
||||
|
||||
FastMCP Cloud **ignores**:
|
||||
|
||||
- `if __name__ == "__main__"` blocks
|
||||
- `.env` files (use Cloud UI instead)
|
||||
- `fastmcp.json` config files (use Cloud UI)
|
||||
- YAML config files (use environment variables)
|
||||
- Docker configurations
|
||||
- CLI arguments
|
||||
|
||||
FastMCP Cloud **uses**:
|
||||
|
||||
- Module-level `FastMCP` instance (`mcp = FastMCP(...)`)
|
||||
- `pyproject.toml` or `requirements.txt`
|
||||
- Environment variables from Cloud UI
|
||||
- Code from your `main` branch
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Failures
|
||||
|
||||
**Issue:** Dependencies fail to install
|
||||
|
||||
```
|
||||
Solution:
|
||||
1. Verify pyproject.toml syntax
|
||||
2. Check dependency versions are available on PyPI
|
||||
3. Remove any local-only dependencies (like editable installs)
|
||||
4. Check that python-dotenv is included
|
||||
```
|
||||
|
||||
**Issue:** Module import errors
|
||||
|
||||
```
|
||||
Solution:
|
||||
1. Ensure all imports use relative paths from src/
|
||||
2. Check that config/, models/, etc. have __init__.py files
|
||||
3. Verify the entrypoint format: mcp_server/src/graphiti_mcp_server.py:mcp
|
||||
```
|
||||
|
||||
### Runtime Errors
|
||||
|
||||
**Issue:** "API key is not configured"
|
||||
|
||||
```
|
||||
Solution:
|
||||
1. Verify environment variables are set in FastMCP Cloud UI
|
||||
2. Check variable names match exactly (case-sensitive)
|
||||
3. Redeploy after adding environment variables
|
||||
```
|
||||
|
||||
**Issue:** Database connection failures
|
||||
|
||||
```
|
||||
Solution:
|
||||
1. Verify database is internet-accessible (not localhost!)
|
||||
2. Check credentials are correct
|
||||
3. For Neo4j Aura: Use neo4j+s:// protocol
|
||||
4. For FalkorDB: Check firewall allows FastMCP Cloud IPs
|
||||
```
|
||||
|
||||
**Issue:** 429 Rate Limit Errors
|
||||
|
||||
```
|
||||
Solution:
|
||||
1. Lower SEMAPHORE_LIMIT based on your API tier:
|
||||
- OpenAI Tier 1: SEMAPHORE_LIMIT=1-2
|
||||
- OpenAI Tier 2: SEMAPHORE_LIMIT=5-8
|
||||
- OpenAI Tier 3: SEMAPHORE_LIMIT=10-15
|
||||
```
|
||||
|
||||
**Issue:** "Graphiti service not initialized"
|
||||
|
||||
```
|
||||
Solution:
|
||||
1. This means initialization failed silently
|
||||
2. Check database credentials
|
||||
3. Check LLM API key
|
||||
4. Run fastmcp dev locally to debug
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use Environment Variables
|
||||
|
||||
All configuration should use environment variables:
|
||||
|
||||
```python
|
||||
# Good - FastMCP Cloud compatible
|
||||
import os
|
||||
api_key = os.environ.get('OPENAI_API_KEY')
|
||||
|
||||
# Bad - Won't work on FastMCP Cloud
|
||||
api_key = 'sk-hardcoded-key'
|
||||
```
|
||||
|
||||
### 2. Module-Level Server Instance
|
||||
|
||||
```python
|
||||
# Good - FastMCP Cloud can discover this
|
||||
from fastmcp import FastMCP
|
||||
mcp = FastMCP("Graphiti Agent Memory")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# This block is IGNORED by FastMCP Cloud
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
### 3. Test Locally First
|
||||
|
||||
Always test locally before deploying:
|
||||
|
||||
```bash
|
||||
# Run verification script
|
||||
cd mcp_server
|
||||
uv run python scripts/verify_fastmcp_cloud_readiness.py
|
||||
|
||||
# Test with fastmcp dev
|
||||
uv run fastmcp dev src/graphiti_mcp_server.py:mcp
|
||||
```
|
||||
|
||||
### 4. Monitor Resource Usage
|
||||
|
||||
- **Neo4j Aura free tier:** Limited connections
|
||||
- **FalkorDB free tier:** 100 MB limit
|
||||
- **OpenAI rate limits:** Tier-dependent
|
||||
- **SEMAPHORE_LIMIT:** Tune based on API tier
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Secrets Management
|
||||
|
||||
- **DO:** Set secrets in FastMCP Cloud UI
|
||||
- **DO:** Add `.env` to `.gitignore`
|
||||
- **DO:** Use `.env.example` for documentation
|
||||
- **DON'T:** Commit `.env` files
|
||||
- **DON'T:** Hardcode API keys
|
||||
- **DON'T:** Store secrets in YAML configs
|
||||
|
||||
### Authentication
|
||||
|
||||
FastMCP Cloud provides built-in authentication:
|
||||
|
||||
- **Enabled:** Only org members can connect (recommended)
|
||||
- **Disabled:** Public access (use for demos only)
|
||||
|
||||
Enable authentication for production deployments.
|
||||
|
||||
## Summary Checklist
|
||||
|
||||
Before deploying to FastMCP Cloud:
|
||||
|
||||
- [ ] Run `uv run python scripts/verify_fastmcp_cloud_readiness.py`
|
||||
- [ ] All checks pass
|
||||
- [ ] Cloud database is running (Neo4j Aura or FalkorDB Cloud)
|
||||
- [ ] API keys are ready (OpenAI required)
|
||||
- [ ] Code is pushed to GitHub `main` branch
|
||||
- [ ] `.env` is in `.gitignore`
|
||||
- [ ] No secrets committed to repo
|
||||
|
||||
During deployment:
|
||||
|
||||
- [ ] Create project on fastmcp.cloud
|
||||
- [ ] Configure entrypoint: `mcp_server/src/graphiti_mcp_server.py:mcp`
|
||||
- [ ] Enable authentication
|
||||
- [ ] Set all required environment variables in UI
|
||||
- [ ] Deploy and monitor build logs
|
||||
|
||||
After deployment:
|
||||
|
||||
- [ ] Test with `fastmcp inspect <URL>`
|
||||
- [ ] Connect from Claude Desktop
|
||||
- [ ] Test add_memory and search tools
|
||||
- [ ] Monitor database usage
|
||||
- [ ] Monitor API rate limits
|
||||
|
||||
## Resources
|
||||
|
||||
- **FastMCP Cloud:** [fastmcp.cloud](https://fastmcp.cloud)
|
||||
- **FastMCP Docs:** [gofastmcp.com](https://gofastmcp.com)
|
||||
- **FastMCP Discord:** [discord.com/invite/aGsSC3yDF4](https://discord.com/invite/aGsSC3yDF4)
|
||||
- **Neo4j Aura:** [neo4j.com/cloud/aura](https://neo4j.com/cloud/aura)
|
||||
- **FalkorDB Cloud:** [cloud.falkordb.com](https://cloud.falkordb.com)
|
||||
- **Verification Script:** [`scripts/verify_fastmcp_cloud_readiness.py`](../scripts/verify_fastmcp_cloud_readiness.py)
|
||||
|
||||
You're now ready to deploy!
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "mcp-server"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
description = "Graphiti MCP Server"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<4"
|
||||
|
|
@ -8,8 +8,10 @@ dependencies = [
|
|||
"fastmcp>=2.13.3",
|
||||
"openai>=1.91.0",
|
||||
"graphiti-core[falkordb]>=0.23.1",
|
||||
"pydantic>=2.0.0",
|
||||
"pydantic-settings>=2.0.0",
|
||||
"pyyaml>=6.0",
|
||||
"python-dotenv>=1.0.0",
|
||||
"typing-extensions>=4.0.0",
|
||||
]
|
||||
|
||||
|
|
|
|||
350
mcp_server/scripts/verify_fastmcp_cloud_readiness.py
Normal file
350
mcp_server/scripts/verify_fastmcp_cloud_readiness.py
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Verify FastMCP Cloud deployment readiness.
|
||||
|
||||
This script checks that your Graphiti MCP server is ready for FastMCP Cloud deployment.
|
||||
Run this before deploying to catch common issues.
|
||||
|
||||
Usage:
|
||||
cd mcp_server
|
||||
uv run python scripts/verify_fastmcp_cloud_readiness.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def print_header(title: str) -> None:
|
||||
"""Print a section header."""
|
||||
print(f'\n{"=" * 60}')
|
||||
print(f' {title}')
|
||||
print('=' * 60)
|
||||
|
||||
|
||||
def print_result(check: str, passed: bool, details: str = '') -> None:
|
||||
"""Print a check result."""
|
||||
status = '✅ PASSED' if passed else '❌ FAILED'
|
||||
print(f'{status}: {check}')
|
||||
if details:
|
||||
for line in details.split('\n'):
|
||||
print(f' {line}')
|
||||
|
||||
|
||||
def check_server_discoverability() -> bool:
|
||||
"""Check if fastmcp inspect can discover the server."""
|
||||
print_header('Check 1: Server Discoverability')
|
||||
|
||||
# Find the server file
|
||||
script_dir = Path(__file__).parent
|
||||
mcp_server_dir = script_dir.parent
|
||||
server_file = mcp_server_dir / 'src' / 'graphiti_mcp_server.py'
|
||||
|
||||
if not server_file.exists():
|
||||
print_result('Server file exists', False, f'Not found: {server_file}')
|
||||
return False
|
||||
|
||||
print_result('Server file exists', True, str(server_file))
|
||||
|
||||
# Try to run fastmcp inspect
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['uv', 'run', 'fastmcp', 'inspect', f'{server_file}:mcp'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=mcp_server_dir,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
# Count tools in output
|
||||
output = result.stdout
|
||||
tool_count = output.count('- ') if '- ' in output else 0
|
||||
print_result(
|
||||
'fastmcp inspect succeeds',
|
||||
True,
|
||||
f'Server discovered with {tool_count} tools',
|
||||
)
|
||||
return True
|
||||
else:
|
||||
print_result(
|
||||
'fastmcp inspect succeeds',
|
||||
False,
|
||||
f'Error: {result.stderr[:200] if result.stderr else result.stdout[:200]}',
|
||||
)
|
||||
return False
|
||||
except subprocess.TimeoutExpired:
|
||||
print_result('fastmcp inspect succeeds', False, 'Command timed out')
|
||||
return False
|
||||
except FileNotFoundError:
|
||||
print_result(
|
||||
'fastmcp inspect succeeds',
|
||||
False,
|
||||
'fastmcp not found. Run: uv sync',
|
||||
)
|
||||
return False
|
||||
except Exception as e:
|
||||
print_result('fastmcp inspect succeeds', False, f'Error: {e}')
|
||||
return False
|
||||
|
||||
|
||||
def check_dependencies() -> bool:
|
||||
"""Check that all dependencies are declared in pyproject.toml."""
|
||||
print_header('Check 2: Dependencies')
|
||||
|
||||
script_dir = Path(__file__).parent
|
||||
mcp_server_dir = script_dir.parent
|
||||
pyproject_file = mcp_server_dir / 'pyproject.toml'
|
||||
|
||||
if not pyproject_file.exists():
|
||||
print_result('pyproject.toml exists', False)
|
||||
return False
|
||||
|
||||
print_result('pyproject.toml exists', True)
|
||||
|
||||
# Read and check for critical dependencies
|
||||
content = pyproject_file.read_text()
|
||||
critical_deps = [
|
||||
'fastmcp',
|
||||
'graphiti-core',
|
||||
'pydantic',
|
||||
'pydantic-settings',
|
||||
'python-dotenv',
|
||||
]
|
||||
|
||||
missing = []
|
||||
for dep in critical_deps:
|
||||
if dep not in content:
|
||||
missing.append(dep)
|
||||
|
||||
if missing:
|
||||
print_result(
|
||||
'Critical dependencies declared',
|
||||
False,
|
||||
f'Missing: {", ".join(missing)}',
|
||||
)
|
||||
return False
|
||||
|
||||
print_result('Critical dependencies declared', True, ', '.join(critical_deps))
|
||||
return True
|
||||
|
||||
|
||||
def check_env_documentation() -> bool:
|
||||
"""Check that environment variables are documented."""
|
||||
print_header('Check 3: Environment Variable Documentation')
|
||||
|
||||
script_dir = Path(__file__).parent
|
||||
mcp_server_dir = script_dir.parent
|
||||
env_example = mcp_server_dir / '.env.example'
|
||||
|
||||
if not env_example.exists():
|
||||
print_result('.env.example exists', False)
|
||||
return False
|
||||
|
||||
content = env_example.read_text()
|
||||
required_vars = ['OPENAI_API_KEY', 'NEO4J_URI', 'FALKORDB_URI']
|
||||
|
||||
documented = [var for var in required_vars if var in content]
|
||||
|
||||
print_result('.env.example exists', True)
|
||||
print_result(
|
||||
'Required vars documented',
|
||||
len(documented) == len(required_vars),
|
||||
f'Found: {", ".join(documented)}',
|
||||
)
|
||||
|
||||
return len(documented) == len(required_vars)
|
||||
|
||||
|
||||
def check_secrets_safety() -> bool:
|
||||
"""Check that no secrets are committed."""
|
||||
print_header('Check 4: Secrets Safety')
|
||||
|
||||
script_dir = Path(__file__).parent
|
||||
mcp_server_dir = script_dir.parent
|
||||
env_file = mcp_server_dir / '.env'
|
||||
gitignore_file = mcp_server_dir.parent / '.gitignore'
|
||||
|
||||
# Check if .env exists (it shouldn't be committed)
|
||||
if env_file.exists():
|
||||
# Check if it's in gitignore
|
||||
if gitignore_file.exists():
|
||||
gitignore_content = gitignore_file.read_text()
|
||||
if '.env' in gitignore_content:
|
||||
print_result('.env in .gitignore', True)
|
||||
else:
|
||||
print_result('.env in .gitignore', False, 'Add .env to .gitignore!')
|
||||
return False
|
||||
else:
|
||||
print_result('.gitignore exists', False)
|
||||
return False
|
||||
else:
|
||||
print_result('.env not present (good for cloud)', True)
|
||||
|
||||
# Check for hardcoded secrets in server file
|
||||
server_file = mcp_server_dir / 'src' / 'graphiti_mcp_server.py'
|
||||
if server_file.exists():
|
||||
content = server_file.read_text()
|
||||
secret_patterns = ['sk-', 'api_key=', 'password=']
|
||||
found_secrets = []
|
||||
for pattern in secret_patterns:
|
||||
if pattern in content.lower() and f"'{pattern}" in content:
|
||||
found_secrets.append(pattern)
|
||||
|
||||
if found_secrets:
|
||||
print_result(
|
||||
'No hardcoded secrets in server',
|
||||
False,
|
||||
f'Found patterns: {found_secrets}',
|
||||
)
|
||||
return False
|
||||
print_result('No hardcoded secrets in server', True)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def check_server_import() -> bool:
|
||||
"""Check that the server can be imported successfully."""
|
||||
print_header('Check 5: Server Import')
|
||||
|
||||
script_dir = Path(__file__).parent
|
||||
mcp_server_dir = script_dir.parent
|
||||
src_dir = mcp_server_dir / 'src'
|
||||
|
||||
# Add src to path temporarily
|
||||
sys.path.insert(0, str(src_dir))
|
||||
|
||||
try:
|
||||
# Try importing the module
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
'graphiti_mcp_server',
|
||||
src_dir / 'graphiti_mcp_server.py',
|
||||
)
|
||||
if spec and spec.loader:
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
# Don't actually execute - just check if it can be loaded
|
||||
print_result('Server module loadable', True)
|
||||
|
||||
# Check for mcp object
|
||||
server_content = (src_dir / 'graphiti_mcp_server.py').read_text()
|
||||
if 'mcp = FastMCP(' in server_content:
|
||||
print_result('Module-level mcp instance found', True)
|
||||
else:
|
||||
print_result(
|
||||
'Module-level mcp instance found',
|
||||
False,
|
||||
'Need: mcp = FastMCP(...) at module level',
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
else:
|
||||
print_result('Server module loadable', False, 'Could not create module spec')
|
||||
return False
|
||||
except Exception as e:
|
||||
print_result('Server module loadable', False, f'Import error: {e}')
|
||||
return False
|
||||
finally:
|
||||
sys.path.pop(0)
|
||||
|
||||
|
||||
def check_entrypoint_format() -> bool:
|
||||
"""Check that the entrypoint format is correct for FastMCP Cloud."""
|
||||
print_header('Check 6: Entrypoint Format')
|
||||
|
||||
script_dir = Path(__file__).parent
|
||||
mcp_server_dir = script_dir.parent
|
||||
server_file = mcp_server_dir / 'src' / 'graphiti_mcp_server.py'
|
||||
|
||||
if not server_file.exists():
|
||||
print_result('Server file exists', False)
|
||||
return False
|
||||
|
||||
content = server_file.read_text()
|
||||
|
||||
# Check for module-level FastMCP instance
|
||||
has_module_level_mcp = 'mcp = FastMCP(' in content
|
||||
|
||||
# Check for if __name__ == "__main__" block (should exist but is ignored)
|
||||
has_main_block = "if __name__ == '__main__':" in content or 'if __name__ == "__main__":' in content
|
||||
|
||||
print_result(
|
||||
'Module-level mcp instance',
|
||||
has_module_level_mcp,
|
||||
'Required for FastMCP Cloud discovery',
|
||||
)
|
||||
|
||||
if has_main_block:
|
||||
print_result(
|
||||
'__main__ block present',
|
||||
True,
|
||||
'Note: This is IGNORED by FastMCP Cloud',
|
||||
)
|
||||
|
||||
# Print the expected entrypoint
|
||||
if has_module_level_mcp:
|
||||
print(f'\n Expected entrypoint for FastMCP Cloud:')
|
||||
print(f' src/graphiti_mcp_server.py:mcp')
|
||||
|
||||
return has_module_level_mcp
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run all verification checks."""
|
||||
print('\n' + '=' * 60)
|
||||
print(' FastMCP Cloud Deployment Readiness Check')
|
||||
print(' Graphiti MCP Server')
|
||||
print('=' * 60)
|
||||
|
||||
checks = [
|
||||
('Server Discoverability', check_server_discoverability),
|
||||
('Dependencies', check_dependencies),
|
||||
('Environment Documentation', check_env_documentation),
|
||||
('Secrets Safety', check_secrets_safety),
|
||||
('Server Import', check_server_import),
|
||||
('Entrypoint Format', check_entrypoint_format),
|
||||
]
|
||||
|
||||
results = []
|
||||
for name, check_fn in checks:
|
||||
try:
|
||||
passed = check_fn()
|
||||
results.append((name, passed))
|
||||
except Exception as e:
|
||||
print(f'\n❌ Error in {name}: {e}')
|
||||
results.append((name, False))
|
||||
|
||||
# Summary
|
||||
print_header('Summary')
|
||||
passed_count = sum(1 for _, passed in results if passed)
|
||||
total_count = len(results)
|
||||
|
||||
for name, passed in results:
|
||||
status = '✅' if passed else '❌'
|
||||
print(f' {status} {name}')
|
||||
|
||||
print(f'\n Total: {passed_count}/{total_count} checks passed')
|
||||
|
||||
if passed_count == total_count:
|
||||
print('\n' + '=' * 60)
|
||||
print(' 🚀 READY FOR FASTMCP CLOUD DEPLOYMENT!')
|
||||
print('=' * 60)
|
||||
print('\nNext steps:')
|
||||
print(' 1. Push code to GitHub')
|
||||
print(' 2. Visit https://fastmcp.cloud')
|
||||
print(' 3. Create project with entrypoint: src/graphiti_mcp_server.py:mcp')
|
||||
print(' 4. Set environment variables in FastMCP Cloud UI')
|
||||
print(' 5. Deploy!')
|
||||
print('\nSee docs/FASTMCP_CLOUD_DEPLOYMENT.md for detailed instructions.')
|
||||
else:
|
||||
print('\n' + '=' * 60)
|
||||
print(' ⚠️ NOT READY - Please fix the failing checks above')
|
||||
print('=' * 60)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Add table
Reference in a new issue