Skip to main content
Glama
chadkunsman

NetBox MCP Server

by chadkunsman
deployment.md16.5 kB
# Deployment Guide This guide covers deploying FastMCP servers to production environments, from simple cloud platforms to enterprise setups. ## Deployment Options Overview | Platform | Best For | Transport Support | Auto-scaling | Cost | |----------|----------|-------------------|--------------|------| | Google Cloud Run | Serverless, auto-scaling | Streamable HTTP only* | Yes | Pay-per-use | | Vercel | Node.js/Python, edge network | Streamable HTTP only* | Yes | Generous free tier | | Railway | Simple deployment | Both transports | Yes | Simple pricing | | Digital Ocean | Traditional hosting | Both transports | Manual | Predictable costs | | AWS ECS/Fargate | Enterprise, containers | Both transports | Yes | Complex pricing | *Legacy HTTP+SSE transport prevents scaling to zero due to persistent connections. ## Local Development Setup ### Development Server ```python # src/server.py import os from fastmcp import FastMCP mcp = FastMCP("Development Server") @mcp.tool() def example_tool() -> str: """Example development tool.""" return "Hello from development!" if __name__ == "__main__": # Development configuration transport = os.getenv("TRANSPORT", "stdio") port = int(os.getenv("PORT", "8000")) if transport == "http": mcp.run(transport="streamable-http", host="0.0.0.0", port=port) else: mcp.run() # Default STDIO for local testing ``` ### Environment Configuration ```bash # .env.development DEBUG=true LOG_LEVEL=debug TRANSPORT=stdio # .env.production DEBUG=false LOG_LEVEL=info TRANSPORT=http PORT=8000 ``` ## Cloud Run Deployment (Recommended) Best for: Serverless applications with automatic scaling ### 1. Create Dockerfile ```dockerfile FROM python:3.11-slim # Set working directory WORKDIR /app # Install system dependencies RUN apt-get update && apt-get install -y \ curl \ && rm -rf /var/lib/apt/lists/* # Install UV RUN pip install uv # Copy dependency files COPY pyproject.toml uv.lock ./ # Install dependencies RUN uv pip install --system --no-cache-dir -e . # Copy source code COPY src/ ./src/ COPY docs/ ./docs/ # Create non-root user RUN useradd --create-home --shell /bin/bash app RUN chown -R app:app /app USER app # Expose port EXPOSE 8000 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1 # Run the server CMD ["python", "src/server.py"] ``` ### 2. Production Server Configuration ```python # src/server.py import os import logging from fastmcp import FastMCP # Configure logging logging.basicConfig( level=logging.INFO if os.getenv("DEBUG") != "true" else logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) mcp = FastMCP( name="Production MCP Server", version="1.0.0" ) # Add health check endpoint @mcp.custom_route("/health", methods=["GET"]) async def health_check(request): from starlette.responses import JSONResponse return JSONResponse({"status": "healthy", "version": "1.0.0"}) # Your tools here @mcp.tool() def production_tool() -> dict: """Production-ready tool.""" return {"message": "Production server running"} if __name__ == "__main__": port = int(os.getenv("PORT", "8000")) mcp.run( transport="streamable-http", host="0.0.0.0", port=port ) ``` ### 3. Deploy to Cloud Run ```bash # Build and deploy gcloud run deploy mcp-server \ --source . \ --platform managed \ --region us-central1 \ --allow-unauthenticated \ --port 8000 \ --memory 512Mi \ --cpu 1 \ --max-instances 10 \ --set-env-vars DEBUG=false,LOG_LEVEL=info # Custom domain (optional) gcloud run domain-mappings create \ --service mcp-server \ --domain your-domain.com \ --region us-central1 ``` ## Vercel Deployment Best for: Simple deployment with global edge network ### 1. Create `vercel.json` ```json { "builds": [ { "src": "src/server.py", "use": "@vercel/python" } ], "routes": [ { "src": "/(.*)", "dest": "src/server.py" } ], "env": { "DEBUG": "false", "LOG_LEVEL": "info" } } ``` ### 2. Adapt Server for Vercel ```python # src/server.py - Vercel compatible from fastmcp import FastMCP mcp = FastMCP("Vercel MCP Server") @mcp.tool() def vercel_tool() -> str: """Tool running on Vercel.""" return "Hello from Vercel!" # Create ASGI app for Vercel app = mcp.http_app() # For local development if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000) ``` ### 3. Deploy ```bash # Install Vercel CLI npm i -g vercel # Deploy vercel --prod ``` ## Railway Deployment Best for: Simple deployment with database support ### 1. Create `railway.toml` ```toml [build] builder = "NIXPACKS" [deploy] healthcheckPath = "/health" healthcheckTimeout = 30 restartPolicyType = "ON_FAILURE" restartPolicyMaxRetries = 3 [env] PORT = { default = "8000" } DEBUG = { default = "false" } ``` ### 2. Deploy to Railway ```bash # Install Railway CLI npm install -g @railway/cli # Login and deploy railway login railway link railway up ``` ## Traditional VPS Deployment Best for: Full control and predictable costs ### 1. Server Setup (Ubuntu) ```bash # Update system sudo apt update && sudo apt upgrade -y # Install Python and dependencies sudo apt install -y python3.11 python3.11-venv python3-pip nginx # Install UV curl -LsSf https://astral.sh/uv/install.sh | sh # Create application directory sudo mkdir -p /opt/mcp-server sudo chown $USER:$USER /opt/mcp-server cd /opt/mcp-server # Clone your repository git clone https://github.com/your-username/your-mcp-server.git . # Install dependencies uv pip install --system -e . ``` ### 2. Systemd Service ```ini # /etc/systemd/system/mcp-server.service [Unit] Description=FastMCP Server After=network.target [Service] Type=simple User=www-data WorkingDirectory=/opt/mcp-server Environment=PATH=/usr/local/bin:/usr/bin:/bin Environment=PORT=8000 Environment=DEBUG=false ExecStart=/usr/bin/python3 src/server.py Restart=always RestartSec=10 [Install] WantedBy=multi-user.target ``` ```bash # Enable and start service sudo systemctl enable mcp-server sudo systemctl start mcp-server sudo systemctl status mcp-server ``` ### 3. Nginx Configuration ```nginx # /etc/nginx/sites-available/mcp-server server { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # WebSocket support for SSE proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } ``` ```bash # Enable site and SSL sudo ln -s /etc/nginx/sites-available/mcp-server /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx # Install SSL certificate (Let's Encrypt) sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d your-domain.com ``` ## Docker Deployment ### 1. Multi-stage Dockerfile ```dockerfile # Build stage FROM python:3.11-slim as builder WORKDIR /app # Install UV RUN pip install uv # Copy and install dependencies COPY pyproject.toml uv.lock ./ RUN uv pip install --system --no-cache-dir -e . # Production stage FROM python:3.11-slim # Install runtime dependencies RUN apt-get update && apt-get install -y \ curl \ && rm -rf /var/lib/apt/lists/* # Copy installed packages from builder COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages COPY --from=builder /usr/local/bin /usr/local/bin # Create non-root user RUN useradd --create-home --shell /bin/bash app WORKDIR /app COPY --chown=app:app src/ ./src/ USER app EXPOSE 8000 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1 CMD ["python", "src/server.py"] ``` ### 2. Docker Compose ```yaml # docker-compose.yml version: '3.8' services: mcp-server: build: . ports: - "8000:8000" environment: - DEBUG=false - LOG_LEVEL=info - DATABASE_URL=postgresql://user:pass@db:5432/mcpdb depends_on: - db restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3 db: image: postgres:15 environment: - POSTGRES_DB=mcpdb - POSTGRES_USER=user - POSTGRES_PASSWORD=pass volumes: - postgres_data:/var/lib/postgresql/data restart: unless-stopped nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./ssl:/etc/nginx/ssl depends_on: - mcp-server restart: unless-stopped volumes: postgres_data: ``` ## Environment Variables ### Required Configuration ```bash # Server Configuration PORT=8000 HOST=0.0.0.0 DEBUG=false LOG_LEVEL=info # Authentication (if using OAuth) OAUTH_CLIENT_ID=your_client_id OAUTH_CLIENT_SECRET=your_client_secret JWT_SECRET_KEY=your_very_secure_secret_here # Database (if needed) DATABASE_URL=postgresql://user:pass@localhost:5432/dbname # External APIs OPENAI_API_KEY=your_openai_key EXTERNAL_API_KEY=your_api_key # Monitoring SENTRY_DSN=your_sentry_dsn ``` ### Environment-Specific Files ```bash # .env.development DEBUG=true LOG_LEVEL=debug DATABASE_URL=sqlite:///dev.db # .env.staging DEBUG=false LOG_LEVEL=info DATABASE_URL=postgresql://user:pass@staging-db:5432/staging # .env.production DEBUG=false LOG_LEVEL=warning DATABASE_URL=postgresql://user:pass@prod-db:5432/production ``` ## Production Considerations ### 1. Monitoring and Logging ```python import logging import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration # Configure Sentry if os.getenv("SENTRY_DSN"): sentry_sdk.init( dsn=os.getenv("SENTRY_DSN"), integrations=[LoggingIntegration()], traces_sample_rate=0.1, environment=os.getenv("ENVIRONMENT", "production") ) # Configure structured logging logging.basicConfig( level=getattr(logging, os.getenv("LOG_LEVEL", "INFO").upper()), format='{"timestamp": "%(asctime)s", "level": "%(levelname)s", "message": "%(message)s"}' ) ``` ### 2. Database Connections ```python import asyncpg from contextlib import asynccontextmanager class DatabaseManager: def __init__(self, database_url: str): self.database_url = database_url self.pool = None async def connect(self): self.pool = await asyncpg.create_pool(self.database_url) async def disconnect(self): if self.pool: await self.pool.close() @asynccontextmanager async def get_connection(self): async with self.pool.acquire() as connection: yield connection # Use in your tools db = DatabaseManager(os.getenv("DATABASE_URL")) @mcp.tool() async def get_user_data(user_id: str) -> dict: """Get user data from database.""" async with db.get_connection() as conn: result = await conn.fetchrow( "SELECT * FROM users WHERE id = $1", user_id ) return dict(result) if result else {"error": "User not found"} ``` ### 3. Caching ```python import redis.asyncio as redis import json from typing import Optional class CacheManager: def __init__(self, redis_url: str): self.redis = redis.from_url(redis_url) async def get(self, key: str) -> Optional[dict]: value = await self.redis.get(key) return json.loads(value) if value else None async def set(self, key: str, value: dict, ttl: int = 3600): await self.redis.setex(key, ttl, json.dumps(value)) cache = CacheManager(os.getenv("REDIS_URL", "redis://localhost:6379")) @mcp.tool() async def cached_api_call(endpoint: str) -> dict: """API call with caching.""" cache_key = f"api:{endpoint}" # Check cache first cached_result = await cache.get(cache_key) if cached_result: return cached_result # Make API call result = await make_api_call(endpoint) # Cache result await cache.set(cache_key, result, ttl=1800) return result ``` ## Security in Production ### 1. HTTPS/TLS ```python # For uvicorn deployment if __name__ == "__main__": import uvicorn # Production with SSL uvicorn.run( app, host="0.0.0.0", port=8000, ssl_keyfile="/path/to/private.key", ssl_certfile="/path/to/certificate.crt" ) ``` ### 2. Rate Limiting ```python from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.util import get_remote_address limiter = Limiter(key_func=get_remote_address) app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) @app.post("/mcp") @limiter.limit("100/hour") async def mcp_endpoint(request: Request): # Your MCP handling logic pass ``` ### 3. CORS Configuration ```python from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=["https://your-allowed-domain.com"], allow_credentials=True, allow_methods=["GET", "POST", "DELETE"], allow_headers=["Authorization", "Content-Type", "MCP-Session-ID"], ) ``` ## CI/CD Pipeline ### GitHub Actions Example ```yaml # .github/workflows/deploy.yml name: Deploy to Production on: push: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: '3.11' - name: Install UV run: pip install uv - name: Install dependencies run: uv pip install --system -e .[dev] - name: Run tests run: pytest - name: Run linting run: ruff check . deploy: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Deploy to Cloud Run uses: google-github-actions/deploy-cloudrun@v1 with: service: mcp-server image: gcr.io/project-id/mcp-server region: us-central1 env_vars: | DEBUG=false LOG_LEVEL=info ``` ## Monitoring and Health Checks ### Health Check Endpoint ```python @mcp.custom_route("/health", methods=["GET"]) async def health_check(request): from starlette.responses import JSONResponse # Check database connection try: async with db.get_connection() as conn: await conn.fetchval("SELECT 1") db_status = "healthy" except Exception: db_status = "unhealthy" # Check external APIs try: # Test your external dependencies api_status = "healthy" except Exception: api_status = "unhealthy" status = "healthy" if db_status == "healthy" and api_status == "healthy" else "unhealthy" return JSONResponse({ "status": status, "checks": { "database": db_status, "external_api": api_status }, "version": "1.0.0", "timestamp": datetime.utcnow().isoformat() }) ``` ## Troubleshooting Deployment Issues ### Common Problems 1. **Port Binding Issues**: Ensure your app binds to `0.0.0.0`, not `127.0.0.1` 2. **Environment Variables**: Use proper environment variable loading 3. **Health Check Failures**: Implement proper health check endpoints 4. **Memory Limits**: Monitor memory usage and adjust container limits 5. **Database Connections**: Use connection pooling for better performance ### Debug Commands ```bash # Check logs docker logs container-name kubectl logs pod-name gcloud run logs read --service=mcp-server # Test endpoints curl -f http://localhost:8000/health curl -H "Authorization: Bearer token" http://localhost:8000/mcp # Monitor resources docker stats kubectl top pods ``` ## Next Steps - **Testing**: See [testing.md](testing.md) for production testing strategies - **Best Practices**: Review [best-practices.md](best-practices.md) for production optimization - **Authentication**: Check [authentication.md](authentication.md) for secure production auth

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/chadkunsman/netbox_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server