Skip to main content
Glama

Grants Search MCP Server

#!/usr/bin/env python3 """ Deployment validation script for Grants MCP Cloud Run deployment. This script validates the deployment configuration before pushing to production. """ import json import os import sys import yaml import subprocess from pathlib import Path from typing import Dict, List, Tuple, Optional def validate_github_workflow() -> Tuple[bool, List[str]]: """Validate GitHub Actions workflow file.""" errors = [] workflow_path = Path(".github/workflows/deploy.yml") if not workflow_path.exists(): errors.append("GitHub workflow file not found at .github/workflows/deploy.yml") return False, errors try: with open(workflow_path, 'r') as f: workflow = yaml.safe_load(f) # Check required fields - 'on' might be parsed differently by YAML required_fields = ['name', 'env', 'jobs'] for field in required_fields: if field not in workflow: errors.append(f"Missing required field '{field}' in workflow") # Check for 'on' field specifically (can be True in YAML) if 'on' not in workflow and True not in workflow: errors.append("Missing required field 'on' in workflow") # Check environment variables env = workflow.get('env', {}) expected_env = { 'PROJECT_ID': 'grants-mcp', 'REGION': 'us-central1', 'SERVICE': 'grants-mcp' } for key, expected_value in expected_env.items(): if env.get(key) != expected_value: errors.append(f"Environment variable {key} should be '{expected_value}', got '{env.get(key)}'") # Check for required secrets usage workflow_str = str(workflow) required_secrets = ['GCP_SA_KEY', 'SIMPLER_GRANTS_API_KEY'] for secret in required_secrets: if f"secrets.{secret}" not in workflow_str: errors.append(f"Required secret '{secret}' not found in workflow") print("✅ GitHub workflow validation passed") except yaml.YAMLError as e: errors.append(f"Invalid YAML syntax in workflow file: {e}") except Exception as e: errors.append(f"Error validating workflow: {e}") return len(errors) == 0, errors def validate_dockerfile() -> Tuple[bool, List[str]]: """Validate Dockerfile configuration.""" errors = [] dockerfile_path = Path("Dockerfile") if not dockerfile_path.exists(): errors.append("Dockerfile not found") return False, errors try: with open(dockerfile_path, 'r') as f: content = f.read() # Check for multi-stage build if "FROM python:3.11-slim as builder" not in content: errors.append("Multi-stage build not properly configured") # Check for non-root user if "RUN groupadd -r appuser && useradd -r -g appuser appuser" not in content: errors.append("Non-root user not configured") # Check for health check if "HEALTHCHECK" not in content: errors.append("Health check not configured") # Check for required environment variables required_env_vars = ['PYTHONUNBUFFERED=1', 'MCP_TRANSPORT=http', 'PORT=8080'] for env_var in required_env_vars: if env_var not in content: errors.append(f"Missing environment variable: {env_var}") # Check for security best practices if "USER appuser" not in content: errors.append("Container should switch to non-root user") print("✅ Dockerfile validation passed") except Exception as e: errors.append(f"Error validating Dockerfile: {e}") return len(errors) == 0, errors def validate_server_health_endpoint() -> Tuple[bool, List[str]]: """Validate server has health endpoint.""" errors = [] server_path = Path("src/mcp_server/server.py") if not server_path.exists(): errors.append("Server file not found") return False, errors try: with open(server_path, 'r') as f: content = f.read() # Check for health endpoint if '@self.mcp.get("/health")' not in content: errors.append("Health endpoint not found in server") # Check for health check function if 'async def health_check():' not in content: errors.append("Health check function not implemented") # Check for required health response fields required_fields = ['"status"', '"service"', '"version"', '"timestamp"', '"transport"'] for field in required_fields: if field not in content: errors.append(f"Health endpoint missing required field: {field}") print("✅ Server health endpoint validation passed") except Exception as e: errors.append(f"Error validating server: {e}") return len(errors) == 0, errors def validate_requirements() -> Tuple[bool, List[str]]: """Validate requirements.txt exists and has required dependencies.""" errors = [] req_path = Path("requirements.txt") if not req_path.exists(): errors.append("requirements.txt not found") return False, errors try: with open(req_path, 'r') as f: content = f.read().lower() required_deps = ['fastmcp', 'httpx', 'pydantic', 'uvicorn'] for dep in required_deps: if dep not in content: errors.append(f"Missing required dependency: {dep}") print("✅ Requirements validation passed") except Exception as e: errors.append(f"Error validating requirements: {e}") return len(errors) == 0, errors def validate_documentation() -> Tuple[bool, List[str]]: """Validate required documentation exists.""" errors = [] required_docs = [ "CLOUD_DEPLOYMENT.md", "USER_ONBOARDING.md", "README.md" ] for doc in required_docs: if not Path(doc).exists(): errors.append(f"Missing documentation: {doc}") if len(errors) == 0: print("✅ Documentation validation passed") return len(errors) == 0, errors def check_git_status() -> Tuple[bool, List[str]]: """Check git status and ensure we're on main branch.""" errors = [] try: # Check current branch result = subprocess.run(['git', 'branch', '--show-current'], capture_output=True, text=True, check=True) current_branch = result.stdout.strip() if current_branch != 'main': errors.append(f"Not on main branch (currently on: {current_branch})") # Check for uncommitted changes result = subprocess.run(['git', 'status', '--porcelain'], capture_output=True, text=True, check=True) if result.stdout.strip(): print("⚠️ Uncommitted changes detected - ensure all changes are committed before deployment") # Check if we can push (remote exists) result = subprocess.run(['git', 'remote', '-v'], capture_output=True, text=True, check=True) if not result.stdout.strip(): errors.append("No git remote configured - cannot deploy without remote repository") if len(errors) == 0: print("✅ Git status validation passed") except subprocess.CalledProcessError as e: errors.append(f"Git command failed: {e}") return len(errors) == 0, errors def validate_environment_template() -> Tuple[bool, List[str]]: """Validate environment template exists.""" errors = [] env_example_path = Path(".env.example") if not env_example_path.exists(): errors.append(".env.example file not found") return False, errors try: with open(env_example_path, 'r') as f: content = f.read() if "SIMPLER_GRANTS_API_KEY=" not in content: errors.append("SIMPLER_GRANTS_API_KEY not found in .env.example") print("✅ Environment template validation passed") except Exception as e: errors.append(f"Error validating environment template: {e}") return len(errors) == 0, errors def print_deployment_checklist(): """Print the deployment checklist for the user.""" print("\n🚀 DEPLOYMENT CHECKLIST") print("=" * 50) print("\nBEFORE PUSHING TO MAIN BRANCH:") print("1. Add GitHub Repository Secrets:") print(" - Go to: https://github.com/Tar-ive/grants-mcp/settings/secrets/actions") print(" - Add 'GCP_SA_KEY' (entire contents of service account JSON)") print(" - Add 'SIMPLER_GRANTS_API_KEY' (your API key)") print("\n2. Ensure Google Cloud Project Setup:") print(" - Project ID: grants-mcp") print(" - Service account 'github-deployer' created with proper IAM roles") print(" - Service account 'grants-mcp-runner' created for runtime") print(" - APIs enabled: run.googleapis.com, artifactregistry.googleapis.com") print("\n3. Push to main branch:") print(" git push origin main") print("\n4. Monitor deployment:") print(" - Check GitHub Actions tab for workflow progress") print(" - Deployment will provide service URL upon completion") print("\n5. Test deployment:") print(" - Health check: curl <service-url>/health") print(" - MCP tools: curl -X POST <service-url>/mcp -H 'Content-Type: application/json' -d '{\"jsonrpc\":\"2.0\",\"method\":\"tools/list\",\"id\":1}'") def main(): """Run all validation checks.""" print("🔍 VALIDATING DEPLOYMENT CONFIGURATION") print("=" * 50) validators = [ ("GitHub Workflow", validate_github_workflow), ("Dockerfile", validate_dockerfile), ("Server Health Endpoint", validate_server_health_endpoint), ("Requirements", validate_requirements), ("Documentation", validate_documentation), ("Git Status", check_git_status), ("Environment Template", validate_environment_template), ] all_passed = True all_errors = [] for name, validator in validators: try: passed, errors = validator() if not passed: all_passed = False all_errors.extend([f"{name}: {error}" for error in errors]) except Exception as e: all_passed = False all_errors.append(f"{name}: Validation failed with exception: {e}") print("\n" + "=" * 50) if all_passed: print("🎉 ALL VALIDATIONS PASSED!") print("Configuration is ready for deployment.") print_deployment_checklist() else: print("❌ VALIDATION ERRORS FOUND:") for error in all_errors: print(f" • {error}") print(f"\nPlease fix {len(all_errors)} error(s) before deployment.") return 1 return 0 if __name__ == "__main__": sys.exit(main())

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/Tar-ive/grants-mcp'

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