# MCP Service Production Deployment Guide ## Current Status ### What's Production-Ready The following components have been implemented and tested: - ✅ **Tool Infrastructure**: FastMCP-based tool registration and discovery - ✅ **Flask App Context Management**: Module-level singleton pattern - ✅ **Error Handling**: Comprehensive validation and error responses - ✅ **Pydantic Validation**: Type-safe request/response schemas - ✅ **Access Control**: RBAC and RLS enforcement through Superset's security manager - ✅ **Database Connection Pooling**: SQLAlchemy pool management - ✅ **Health Checks**: Basic health check tool for monitoring ### What's Development-Only The following features are suitable for development but **require configuration for production**: - ❌ **Authentication**: `MCP_DEV_USERNAME` single-user authentication (replace with JWT) - ❌ **Logging**: Basic debug logging (implement structured logging) - ❌ **Rate Limiting**: No rate limiting implemented (add per-user/per-tool limits) - ❌ **Monitoring**: No metrics export (add Prometheus/CloudWatch) - ❌ **Caching**: No caching layer (consider Redis for performance) - ❌ **HTTPS**: HTTP-only by default (must enable HTTPS for production) ## Required for Production ### 1. Authentication & Authorization #### JWT Authentication Setup **Required Configuration**: ```python # superset_config.py # Enable JWT authentication MCP_AUTH_ENABLED = True # JWT validation settings MCP_JWT_ISSUER = "https://auth.yourcompany.com" MCP_JWT_AUDIENCE = "superset-mcp" MCP_JWT_ALGORITHM = "RS256" # or "HS256" for shared secrets # Option A: Use JWKS endpoint (recommended for RS256) MCP_JWKS_URI = "https://auth.yourcompany.com/.well-known/jwks.json" # Option B: Static public key (RS256) MCP_JWT_PUBLIC_KEY = """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... -----END PUBLIC KEY-----""" # Option C: Shared secret (HS256 - less secure) MCP_JWT_ALGORITHM = "HS256" MCP_JWT_SECRET = "your-256-bit-secret-key" # Optional: Require specific scopes MCP_REQUIRED_SCOPES = ["superset:read"] # Disable development authentication MCP_DEV_USERNAME = None ``` **JWT Issuer Setup Examples**: **Auth0**: ```python MCP_JWT_ISSUER = "https://your-tenant.auth0.com/" MCP_JWT_AUDIENCE = "superset-mcp" MCP_JWKS_URI = "https://your-tenant.auth0.com/.well-known/jwks.json" ``` **Okta**: ```python MCP_JWT_ISSUER = "https://your-domain.okta.com/oauth2/default" MCP_JWT_AUDIENCE = "api://superset-mcp" MCP_JWKS_URI = "https://your-domain.okta.com/oauth2/default/v1/keys" ``` **AWS Cognito**: ```python MCP_JWT_ISSUER = "https://cognito-idp.us-east-1.amazonaws.com/your-pool-id" MCP_JWT_AUDIENCE = "your-app-client-id" MCP_JWKS_URI = "https://cognito-idp.us-east-1.amazonaws.com/your-pool-id/.well-known/jwks.json" ``` **Self-Hosted (Using Keycloak)**: ```python MCP_JWT_ISSUER = "https://keycloak.yourcompany.com/realms/superset" MCP_JWT_AUDIENCE = "superset-mcp" MCP_JWKS_URI = "https://keycloak.yourcompany.com/realms/superset/protocol/openid-connect/certs" ``` **Testing JWT Configuration**: ```bash # Validate JWT token curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \ https://mcp.yourcompany.com/health # Expected response if successful: # {"status": "healthy", "timestamp": "2025-01-01T10:30:45Z", ...} # Expected response if auth fails: # {"error": "Invalid token", "error_type": "AuthenticationError", ...} ``` #### Permission Checking Superset's existing RBAC is automatically enforced. Ensure roles are configured: ```python # In Superset UI: Security → List Roles # Recommended role configuration: # - Admin: Full access to all MCP tools # - Alpha: Create/edit charts and dashboards # - Gamma: Read-only access to shared resources # - Custom: Fine-grained permissions per use case ``` **Verify Permissions**: ```bash # Test as non-admin user curl -H "Authorization: Bearer NON_ADMIN_TOKEN" \ https://mcp.yourcompany.com/list_charts # Should only return charts the user has access to ``` ### 2. Performance & Reliability #### Rate Limiting **Implementation Options**: **Option A: Flask-Limiter** (application-level): ```python # superset_config.py from flask_limiter import Limiter from flask_limiter.util import get_remote_address # Add to MCP service initialization MCP_RATE_LIMITING = { "enabled": True, "storage_uri": "redis://localhost:6379/0", "default_limits": ["100 per minute", "1000 per hour"], "per_tool_limits": { "execute_sql": "10 per minute", "generate_chart": "20 per minute", "list_charts": "100 per minute", } } ``` **Option B: Nginx Rate Limiting** (infrastructure-level): ```nginx # /etc/nginx/conf.d/mcp-rate-limit.conf limit_req_zone $binary_remote_addr zone=mcp_limit:10m rate=100r/m; server { listen 443 ssl; server_name mcp.yourcompany.com; location / { limit_req zone=mcp_limit burst=20 nodelay; proxy_pass http://mcp_backend; } } ``` **Option C: API Gateway** (cloud-native): AWS API Gateway, Azure API Management, or Google Cloud Endpoints provide built-in rate limiting. #### Error Handling and Monitoring **Structured Error Responses**: All MCP tools return consistent error schemas: ```python { "error": "Resource not found", "error_type": "NotFoundError", "timestamp": "2025-01-01T10:30:45.123Z", "details": { "resource_type": "dashboard", "resource_id": 123 } } ``` **Error Tracking (Sentry)**: ```python # superset_config.py import sentry_sdk from sentry_sdk.integrations.flask import FlaskIntegration sentry_sdk.init( dsn="https://your-dsn@sentry.io/project-id", integrations=[FlaskIntegration()], environment="production", traces_sample_rate=0.1, # 10% of transactions ) ``` **Metrics Export (Prometheus)**: ```python # Future implementation - add to superset_config.py from prometheus_flask_exporter import PrometheusMetrics MCP_PROMETHEUS_ENABLED = True MCP_PROMETHEUS_PATH = "/metrics" ``` **Example Prometheus metrics**: ``` # HELP mcp_requests_total Total MCP tool requests # TYPE mcp_requests_total counter mcp_requests_total{tool="list_charts",status="success"} 1234 # HELP mcp_request_duration_seconds MCP request duration # TYPE mcp_request_duration_seconds histogram mcp_request_duration_seconds_bucket{tool="list_charts",le="0.5"} 1000 mcp_request_duration_seconds_bucket{tool="list_charts",le="1.0"} 1200 ``` #### Performance Optimization **Database Query Optimization**: ```python # superset_config.py # Connection pool sizing SQLALCHEMY_POOL_SIZE = 10 # Connections per worker SQLALCHEMY_MAX_OVERFLOW = 10 # Additional connections allowed SQLALCHEMY_POOL_TIMEOUT = 30 # Seconds to wait for connection SQLALCHEMY_POOL_RECYCLE = 3600 # Recycle connections after 1 hour # Query optimization SQLALCHEMY_ECHO = False # Disable SQL logging in production ``` **Caching Strategy**: ```python # superset_config.py # Enable Superset's caching CACHE_CONFIG = { "CACHE_TYPE": "RedisCache", "CACHE_REDIS_URL": "redis://localhost:6379/1", "CACHE_DEFAULT_TIMEOUT": 300, # 5 minutes } # Cache query results DATA_CACHE_CONFIG = { "CACHE_TYPE": "RedisCache", "CACHE_REDIS_URL": "redis://localhost:6379/2", "CACHE_DEFAULT_TIMEOUT": 3600, # 1 hour } ``` **MCP-Specific Caching** (future enhancement): ```python # Cache tool responses in Redis MCP_CACHE_CONFIG = { "enabled": True, "backend": "redis", "url": "redis://localhost:6379/3", "ttl": 300, # 5 minutes "cache_tools": [ "list_dashboards", "list_charts", "list_datasets", "get_dataset_info", ] } ``` #### Load Testing **Run load tests before production deployment**: **Using Locust**: ```python # locustfile.py from locust import HttpUser, task, between import jwt import time class MCPUser(HttpUser): wait_time = between(1, 3) def on_start(self): # Generate JWT token self.token = generate_jwt_token() @task(3) def list_charts(self): self.client.post( "/list_charts", headers={"Authorization": f"Bearer {self.token}"}, json={"request": {"page": 1, "page_size": 10}} ) @task(2) def get_chart_info(self): self.client.post( "/get_chart_info", headers={"Authorization": f"Bearer {self.token}"}, json={"request": {"identifier": 1}} ) @task(1) def generate_chart(self): self.client.post( "/generate_chart", headers={"Authorization": f"Bearer {self.token}"}, json={ "request": { "dataset_id": 1, "config": { "chart_type": "table", "columns": [{"name": "col1"}] } } } ) ``` **Run load test**: ```bash locust -f locustfile.py --host https://mcp.yourcompany.com # Test targets: # - 100 concurrent users # - < 2 second p95 response time # - < 1% error rate ``` ## Deployment Architecture ### Production Deployment Overview ```mermaid graph TB subgraph "External" Clients[MCP Clients
Claude, Automation Tools] AuthProvider[Auth Provider
Auth0, Okta, Cognito] end subgraph "DMZ / Edge" LB[Load Balancer
Nginx / ALB] WAF[WAF
Optional] end subgraph "Application Tier" MCP1[MCP Instance 1] MCP2[MCP Instance 2] MCP3[MCP Instance 3] Superset[Superset Web Server] end subgraph "Data Tier" DB[(PostgreSQL
Superset Metadata)] Redis[(Redis
Cache)] end subgraph "Monitoring" Prometheus[Prometheus] Grafana[Grafana] Logs[Log Aggregator
ELK, Splunk] end Clients --> |HTTPS| WAF WAF --> LB Clients --> |Get JWT| AuthProvider LB --> MCP1 LB --> MCP2 LB --> MCP3 MCP1 --> DB MCP2 --> DB MCP3 --> DB MCP1 --> Redis MCP2 --> Redis MCP3 --> Redis Superset --> DB MCP1 -.->|Metrics| Prometheus MCP2 -.->|Metrics| Prometheus MCP3 -.->|Metrics| Prometheus Prometheus --> Grafana MCP1 -.->|Logs| Logs MCP2 -.->|Logs| Logs MCP3 -.->|Logs| Logs ``` ## Deployment Guide ### Installation Requirements **System Dependencies**: ```bash # Ubuntu/Debian apt-get update apt-get install -y \ python3.11 \ python3.11-dev \ python3-pip \ build-essential \ libssl-dev \ libffi-dev \ libsasl2-dev \ libldap2-dev # RHEL/CentOS yum install -y \ python311 \ python311-devel \ gcc \ gcc-c++ \ openssl-devel \ libffi-devel ``` **Python Package Installation**: ```bash # Create virtual environment python3.11 -m venv /opt/superset/venv source /opt/superset/venv/bin/activate # Install Superset with MCP support pip install apache-superset[fastmcp] # Or from requirements.txt pip install -r requirements/production.txt ``` **Verify Installation**: ```bash superset version superset mcp --help ``` ### Configuration **Create Production Config**: ```python # /opt/superset/superset_config.py # Database connection SQLALCHEMY_DATABASE_URI = "postgresql://user:pass@db-host:5432/superset" # Secret key (generate with: openssl rand -base64 42) SECRET_KEY = "your-secret-key-here" # MCP Service Configuration MCP_AUTH_ENABLED = True MCP_JWT_ISSUER = "https://auth.yourcompany.com" MCP_JWT_AUDIENCE = "superset-mcp" MCP_JWKS_URI = "https://auth.yourcompany.com/.well-known/jwks.json" MCP_DEV_USERNAME = None # Disable dev auth # Service binding MCP_SERVICE_HOST = "0.0.0.0" # Listen on all interfaces MCP_SERVICE_PORT = 5008 # Security settings MCP_SESSION_CONFIG = { "SESSION_COOKIE_HTTPONLY": True, "SESSION_COOKIE_SECURE": True, # Requires HTTPS "SESSION_COOKIE_SAMESITE": "Strict", "PERMANENT_SESSION_LIFETIME": 3600, # 1 hour } # Database connection pool SQLALCHEMY_POOL_SIZE = 10 SQLALCHEMY_MAX_OVERFLOW = 10 SQLALCHEMY_POOL_TIMEOUT = 30 # Superset webserver address (for screenshot generation) SUPERSET_WEBSERVER_ADDRESS = "https://superset.yourcompany.com" WEBDRIVER_BASEURL = "https://superset.yourcompany.com/" # Enable HTTPS ENABLE_PROXY_FIX = True ``` **Set Environment Variables**: ```bash # /opt/superset/.env export SUPERSET_CONFIG_PATH=/opt/superset/superset_config.py export FLASK_APP=superset ``` ### Process Management #### Systemd (Recommended) **Service File**: ```ini # /etc/systemd/system/superset-mcp.service [Unit] Description=Superset MCP Service After=network.target postgresql.service redis.service Requires=postgresql.service [Service] Type=simple User=superset Group=superset WorkingDirectory=/opt/superset Environment="SUPERSET_CONFIG_PATH=/opt/superset/superset_config.py" Environment="FLASK_APP=superset" ExecStart=/opt/superset/venv/bin/superset mcp run --port 5008 # Restart policy Restart=always RestartSec=10s # Resource limits LimitNOFILE=65536 MemoryLimit=2G # Logging StandardOutput=journal StandardError=journal SyslogIdentifier=superset-mcp [Install] WantedBy=multi-user.target ``` **Enable and Start Service**: ```bash # Reload systemd systemctl daemon-reload # Enable service to start on boot systemctl enable superset-mcp # Start service systemctl start superset-mcp # Check status systemctl status superset-mcp # View logs journalctl -u superset-mcp -f ``` #### Supervisord **Configuration**: ```ini # /etc/supervisor/conf.d/superset-mcp.conf [program:superset-mcp] command=/opt/superset/venv/bin/superset mcp run --port 5008 directory=/opt/superset user=superset autostart=true autorestart=true redirect_stderr=true stdout_logfile=/var/log/superset/mcp.log stdout_logfile_maxbytes=50MB stdout_logfile_backups=10 environment=SUPERSET_CONFIG_PATH="/opt/superset/superset_config.py",FLASK_APP="superset" ``` **Start Service**: ```bash supervisorctl reread supervisorctl update supervisorctl start superset-mcp supervisorctl status superset-mcp ``` #### Docker **Dockerfile**: ```dockerfile FROM apache/superset:latest # Install MCP dependencies RUN pip install apache-superset[fastmcp] # Copy production config COPY superset_config.py /app/pythonpath/ # Expose MCP port EXPOSE 5008 # Run MCP service CMD ["superset", "mcp", "run", "--port", "5008"] ``` **Build and Run**: ```bash # Build image docker build -t superset-mcp:latest . # Run container docker run -d \ --name superset-mcp \ -p 5008:5008 \ -v /opt/superset/superset_config.py:/app/pythonpath/superset_config.py:ro \ -e SUPERSET_CONFIG_PATH=/app/pythonpath/superset_config.py \ superset-mcp:latest # View logs docker logs -f superset-mcp ``` #### Kubernetes **Deployment**: ```yaml # superset-mcp-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: superset-mcp labels: app: superset-mcp spec: replicas: 3 selector: matchLabels: app: superset-mcp template: metadata: labels: app: superset-mcp spec: containers: - name: mcp image: apache/superset:latest command: ["superset", "mcp", "run", "--port", "5008"] ports: - containerPort: 5008 name: mcp env: - name: SUPERSET_CONFIG_PATH value: /app/pythonpath/superset_config.py - name: FLASK_APP value: superset volumeMounts: - name: config mountPath: /app/pythonpath readOnly: true resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "2Gi" cpu: "2000m" livenessProbe: httpGet: path: /health port: 5008 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /health port: 5008 initialDelaySeconds: 10 periodSeconds: 5 volumes: - name: config configMap: name: superset-config --- apiVersion: v1 kind: Service metadata: name: superset-mcp spec: selector: app: superset-mcp ports: - port: 5008 targetPort: 5008 name: mcp type: ClusterIP --- apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: superset-mcp-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: superset-mcp minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 ``` **Deploy to Kubernetes**: ```bash # Create ConfigMap from superset_config.py kubectl create configmap superset-config \ --from-file=superset_config.py=/opt/superset/superset_config.py # Apply deployment kubectl apply -f superset-mcp-deployment.yaml # Check status kubectl get pods -l app=superset-mcp kubectl logs -l app=superset-mcp -f ``` ### Reverse Proxy Configuration #### Nginx ```nginx # /etc/nginx/sites-available/mcp.yourcompany.com upstream mcp_backend { # Health checks server mcp-1:5008 max_fails=3 fail_timeout=30s; server mcp-2:5008 max_fails=3 fail_timeout=30s; server mcp-3:5008 max_fails=3 fail_timeout=30s; } # Rate limiting limit_req_zone $binary_remote_addr zone=mcp_limit:10m rate=100r/m; server { listen 80; server_name mcp.yourcompany.com; # Redirect HTTP to HTTPS return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name mcp.yourcompany.com; # SSL configuration ssl_certificate /etc/ssl/certs/mcp.yourcompany.com.crt; ssl_certificate_key /etc/ssl/private/mcp.yourcompany.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; # Logging access_log /var/log/nginx/mcp-access.log combined; error_log /var/log/nginx/mcp-error.log warn; location / { # Rate limiting limit_req zone=mcp_limit burst=20 nodelay; # Proxy configuration proxy_pass http://mcp_backend; proxy_http_version 1.1; 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; # Timeouts proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; # Buffering proxy_buffering off; } # Health check endpoint (bypass rate limiting) location /health { proxy_pass http://mcp_backend; access_log off; } } ``` **Enable Site**: ```bash ln -s /etc/nginx/sites-available/mcp.yourcompany.com /etc/nginx/sites-enabled/ nginx -t systemctl reload nginx ``` #### Apache ```apache # /etc/apache2/sites-available/mcp.yourcompany.com.conf ServerName mcp.yourcompany.com Redirect permanent / https://mcp.yourcompany.com/ ServerName mcp.yourcompany.com SSLEngine on SSLCertificateFile /etc/ssl/certs/mcp.yourcompany.com.crt SSLCertificateKeyFile /etc/ssl/private/mcp.yourcompany.com.key SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite HIGH:!aNULL:!MD5 # Security headers Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" Header always set X-Frame-Options "DENY" Header always set X-Content-Type-Options "nosniff" # Proxy configuration ProxyPreserveHost On ProxyPass / http://localhost:5008/ ProxyPassReverse / http://localhost:5008/ # Timeouts ProxyTimeout 60 ErrorLog ${APACHE_LOG_DIR}/mcp-error.log CustomLog ${APACHE_LOG_DIR}/mcp-access.log combined ``` **Enable Site**: ```bash a2enmod ssl proxy proxy_http headers a2ensite mcp.yourcompany.com apachectl configtest systemctl reload apache2 ``` ### Monitoring and Alerting #### Health Checks **Manual Health Check**: ```bash curl https://mcp.yourcompany.com/health ``` **Expected Response**: ```json { "status": "healthy", "timestamp": "2025-01-01T10:30:45.123Z", "version": "1.0.0" } ``` #### Prometheus Monitoring **Prometheus Config**: ```yaml # /etc/prometheus/prometheus.yml scrape_configs: - job_name: 'superset-mcp' scrape_interval: 15s static_configs: - targets: ['mcp-1:5008', 'mcp-2:5008', 'mcp-3:5008'] metrics_path: /metrics ``` **Grafana Dashboard**: Create dashboard with panels for: - Request rate per tool - Request latency (p50, p95, p99) - Error rate - Active connections - Memory/CPU usage #### Alerting Rules **Prometheus Alerting**: ```yaml # /etc/prometheus/rules/mcp-alerts.yml groups: - name: mcp-service interval: 30s rules: - alert: MCPHighErrorRate expr: rate(mcp_requests_total{status="error"}[5m]) > 0.05 for: 5m labels: severity: warning annotations: summary: "High error rate on MCP service" description: "Error rate is {{ $value }} req/sec" - alert: MCPHighLatency expr: histogram_quantile(0.95, mcp_request_duration_seconds) > 2 for: 10m labels: severity: warning annotations: summary: "High latency on MCP service" description: "P95 latency is {{ $value }} seconds" - alert: MCPServiceDown expr: up{job="superset-mcp"} == 0 for: 1m labels: severity: critical annotations: summary: "MCP service is down" description: "Instance {{ $labels.instance }} is unreachable" ``` #### CloudWatch Monitoring (AWS) **CloudWatch Agent Config**: ```json { "logs": { "logs_collected": { "files": { "collect_list": [ { "file_path": "/var/log/superset/mcp.log", "log_group_name": "/aws/superset/mcp", "log_stream_name": "{instance_id}", "timezone": "UTC" } ] } } }, "metrics": { "namespace": "SupersetMCP", "metrics_collected": { "cpu": { "measurement": [ {"name": "cpu_usage_idle", "rename": "CPU_IDLE", "unit": "Percent"} ], "totalcpu": false }, "mem": { "measurement": [ {"name": "mem_used_percent", "rename": "MEM_USED", "unit": "Percent"} ] } } } } ``` **CloudWatch Alarms**: ```bash # Create alarm for high error rate aws cloudwatch put-metric-alarm \ --alarm-name mcp-high-error-rate \ --alarm-description "MCP error rate > 5%" \ --metric-name ErrorRate \ --namespace SupersetMCP \ --statistic Average \ --period 300 \ --threshold 5 \ --comparison-operator GreaterThanThreshold \ --evaluation-periods 2 ``` ## Migration Path (Development → Production) ### Pre-Deployment Checklist **Configuration**: - [ ] `MCP_AUTH_ENABLED = True` - [ ] JWT issuer, audience, and keys configured - [ ] `MCP_DEV_USERNAME` set to `None` - [ ] `SESSION_COOKIE_SECURE = True` - [ ] HTTPS enabled on load balancer/reverse proxy - [ ] Database connection pool sized appropriately - [ ] Superset webserver address updated for production URL **Security**: - [ ] TLS 1.2+ enforced - [ ] Security headers configured (HSTS, X-Frame-Options, etc.) - [ ] Firewall rules restrict access to MCP service - [ ] Service account credentials rotated - [ ] Secrets stored in secure vault (not in code) **Monitoring**: - [ ] Health check endpoint accessible - [ ] Metrics exported to monitoring system - [ ] Alerts configured for critical conditions - [ ] Log aggregation configured - [ ] Dashboards created for key metrics **Performance**: - [ ] Load testing completed successfully - [ ] Database queries optimized - [ ] Caching configured (if needed) - [ ] Rate limiting enabled - [ ] Connection pools tuned **Operations**: - [ ] Process manager configured (systemd/supervisord/k8s) - [ ] Auto-restart on failure enabled - [ ] Log rotation configured - [ ] Backup and disaster recovery plan documented - [ ] Runbook for common issues created ### Testing Production Setup **1. Verify Authentication**: ```bash # Test with valid JWT curl -H "Authorization: Bearer VALID_TOKEN" \ https://mcp.yourcompany.com/health # Test with invalid JWT curl -H "Authorization: Bearer INVALID_TOKEN" \ https://mcp.yourcompany.com/health # Expected: 401 Unauthorized ``` **2. Verify Authorization**: ```bash # Test with limited permissions user curl -H "Authorization: Bearer LIMITED_USER_TOKEN" \ https://mcp.yourcompany.com/list_charts # Expected: Only returns charts user can access # Test permission denial curl -H "Authorization: Bearer LIMITED_USER_TOKEN" \ https://mcp.yourcompany.com/generate_dashboard # Expected: 403 Forbidden if user lacks permission ``` **3. Verify HTTPS**: ```bash # Should redirect to HTTPS curl -I http://mcp.yourcompany.com # Should work with HTTPS curl -I https://mcp.yourcompany.com ``` **4. Verify Rate Limiting**: ```bash # Send many requests rapidly for i in {1..150}; do curl -H "Authorization: Bearer TOKEN" \ https://mcp.yourcompany.com/health & done wait # Expected: Some requests return 429 Too Many Requests ``` **5. Monitor Logs**: ```bash # Systemd journalctl -u superset-mcp -f # Docker docker logs -f superset-mcp # Kubernetes kubectl logs -l app=superset-mcp -f ``` ### Rollback Plan **If issues occur after deployment**: 1. **Immediate Rollback**: ```bash # Systemd systemctl stop superset-mcp # Restore previous configuration cp /opt/superset/superset_config.py.backup /opt/superset/superset_config.py systemctl start superset-mcp # Kubernetes kubectl rollout undo deployment/superset-mcp ``` 2. **Partial Rollback** (rollback auth only): ```python # Temporarily re-enable dev auth MCP_AUTH_ENABLED = False MCP_DEV_USERNAME = "admin" ``` 3. **Investigate and Fix**: - Review logs for errors - Check JWT configuration - Verify network connectivity - Test database connection - Validate Superset configuration ## Troubleshooting ### Common Issues **Issue**: "Invalid token" errors **Diagnosis**: ```bash # Decode JWT to inspect claims echo "YOUR_JWT_TOKEN" | cut -d'.' -f2 | base64 -d | jq . # Check issuer, audience, expiration ``` **Solution**: - Verify `MCP_JWT_ISSUER` matches token's `iss` claim - Verify `MCP_JWT_AUDIENCE` matches token's `aud` claim - Check token hasn't expired (`exp` claim) - Ensure JWKS URI is accessible from MCP server --- **Issue**: "User not found" errors **Diagnosis**: ```bash # Check if user exists in Superset superset fab list-users | grep username ``` **Solution**: - Create user in Superset: `superset fab create-user` - Ensure JWT `sub` claim matches Superset username - Or configure user auto-provisioning (future feature) --- **Issue**: High latency **Diagnosis**: ```bash # Check database connection pool # Look for "QueuePool limit" errors in logs journalctl -u superset-mcp | grep -i pool # Check database performance # Monitor slow queries in database logs ``` **Solution**: - Increase `SQLALCHEMY_POOL_SIZE` - Add database indexes on frequently queried columns - Enable query result caching - Optimize dataset queries --- **Issue**: Service crashes on startup **Diagnosis**: ```bash # Check logs journalctl -u superset-mcp -n 100 # Common causes: # - Missing configuration # - Database connection failure # - Port already in use ``` **Solution**: - Verify all required config keys present - Test database connection: `superset db upgrade` - Check port availability: `netstat -tuln | grep 5008` --- **Issue**: Permission denied errors **Diagnosis**: ```bash # Check user's roles superset fab list-users | grep -A 5 username # Check role permissions in Superset UI # Security → List Roles → [Role Name] → Permissions ``` **Solution**: - Grant required permissions to user's role - Verify RLS rules not too restrictive - Check dataset permissions ## Performance Tuning ### Database Connection Pool **Optimal Settings** (4 workers): ```python SQLALCHEMY_POOL_SIZE = 5 # 5 connections per worker SQLALCHEMY_MAX_OVERFLOW = 5 # 5 extra connections when busy # Total: 4 workers × (5 + 5) = 40 max connections ``` **Monitoring**: ```sql -- PostgreSQL: Check active connections SELECT COUNT(*) FROM pg_stat_activity WHERE state = 'active'; -- PostgreSQL: Check connection limit SHOW max_connections; ``` ### Caching **Enable Redis Caching**: ```python # superset_config.py CACHE_CONFIG = { "CACHE_TYPE": "RedisCache", "CACHE_REDIS_URL": "redis://localhost:6379/1", "CACHE_DEFAULT_TIMEOUT": 300, } DATA_CACHE_CONFIG = { "CACHE_TYPE": "RedisCache", "CACHE_REDIS_URL": "redis://localhost:6379/2", "CACHE_DEFAULT_TIMEOUT": 3600, } ``` **Cache Hit Rate Monitoring**: ```bash # Redis: Monitor cache performance redis-cli INFO stats | grep -E 'keyspace_hits|keyspace_misses' ``` ### Request Timeouts ```python # superset_config.py # SQLLab query timeout SQLLAB_TIMEOUT = 300 # 5 minutes # SQL query timeout SQLLAB_ASYNC_TIME_LIMIT_SEC = 300 # Superset webserver request timeout SUPERSET_WEBSERVER_TIMEOUT = 60 ``` ## References - **Superset Configuration**: https://superset.apache.org/docs/configuration/configuring-superset - **Superset Installation**: https://superset.apache.org/docs/installation/installing-superset-from-scratch - **FastMCP Documentation**: https://github.com/jlowin/fastmcp - **JWT Best Practices**: https://tools.ietf.org/html/rfc8725 - **Prometheus Monitoring**: https://prometheus.io/docs/ - **Nginx Configuration**: https://nginx.org/en/docs/ - **Kubernetes Deployment**: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/