Skip to main content
Glama
config.py10.8 kB
""" Server Configuration Module Following Uncle Bob's Clean Code principles: - Single source of truth for all configuration - No hardcoded values in application code - Environment variable overrides - Type-safe configuration access """ import os from dotenv import load_dotenv # Load environment variables load_dotenv() class ErrorConfig: """Configuration for error handling and logging.""" # Whether to expose detailed error information to API responses # Should ALWAYS be False in production EXPOSE_DETAILS: bool = os.getenv("ERROR_EXPOSE_DETAILS", "false").lower() == "true" # Log level for the application LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO").upper() # Whether to include request IDs in error responses INCLUDE_REQUEST_ID: bool = True # Whether to log stack traces for errors LOG_STACK_TRACES: bool = True class DatabaseConfig: """Configuration for MongoDB database.""" URI: str = os.getenv("MONGODB_URI", "mongodb://localhost:27017/selfmemory") TIMEOUT: int = int(os.getenv("MONGODB_TIMEOUT", "30")) MAX_POOL_SIZE: int = int(os.getenv("MONGODB_MAX_POOL_SIZE", "100")) # Transaction configuration TRANSACTION_TIMEOUT: int = int(os.getenv("MONGODB_TRANSACTION_TIMEOUT", "30")) RETRY_WRITES: bool = os.getenv("MONGODB_RETRY_WRITES", "true").lower() == "true" WRITE_CONCERN: str = os.getenv("MONGODB_WRITE_CONCERN", "majority") class SecurityConfig: """Configuration for security features.""" # CSRF Protection CSRF_SECRET_KEY: str | None = os.getenv("CSRF_SECRET_KEY") CSRF_COOKIE_SECURE: bool = os.getenv("CSRF_COOKIE_SECURE", "true").lower() == "true" CSRF_COOKIE_SAMESITE: str = os.getenv("CSRF_COOKIE_SAMESITE", "Strict") CSRF_COOKIE_HTTPONLY: bool = True CSRF_HEADER_NAME: str = "X-CSRF-Token" CSRF_COOKIE_NAME: str = "csrf_token" # Rate Limiting RATE_LIMIT_ENABLED: bool = ( os.getenv("RATE_LIMIT_ENABLED", "false").lower() == "true" ) RATE_LIMIT_STORAGE_URL: str | None = os.getenv("RATE_LIMIT_STORAGE_URL") # Token expiry INVITATION_TOKEN_EXPIRY_HOURS: int = int( os.getenv("INVITATION_TOKEN_EXPIRY_HOURS", "24") ) API_KEY_DEFAULT_EXPIRY_DAYS: int | None = ( int(os.getenv("API_KEY_DEFAULT_EXPIRY_DAYS")) if os.getenv("API_KEY_DEFAULT_EXPIRY_DAYS") else None ) class AuthConfig: """Configuration for authentication and API key verification.""" # Maximum number of Argon2 hash verifications per authentication attempt # Protects against performance attacks via prefix collision MAX_HASH_VERIFICATIONS: int = int(os.getenv("AUTH_MAX_HASH_VERIFICATIONS", "10")) # Threshold to warn about high prefix collision # If exceeded, logs warning about potential system issue COLLISION_WARNING_THRESHOLD: int = int( os.getenv("AUTH_COLLISION_WARNING_THRESHOLD", "50") ) class PaginationConfig: """Configuration for pagination.""" DEFAULT_LIMIT: int = int(os.getenv("PAGINATION_DEFAULT_LIMIT", "10")) MAX_LIMIT: int = int(os.getenv("PAGINATION_MAX_LIMIT", "100")) class EmailConfig: """Configuration for email/SMTP.""" SMTP_HOST: str | None = os.getenv("SMTP_HOST") SMTP_PORT: int = int(os.getenv("SMTP_PORT", "587")) SMTP_USERNAME: str | None = os.getenv("SMTP_USERNAME") SMTP_PASSWORD: str | None = os.getenv("SMTP_PASSWORD") SMTP_FROM_EMAIL: str = os.getenv("SMTP_FROM_EMAIL", "noreply@selfmemory.com") SMTP_FROM_NAME: str = os.getenv("SMTP_FROM_NAME", "SelfMemory") SMTP_USE_TLS: bool = os.getenv("SMTP_USE_TLS", "true").lower() == "true" SMTP_TIMEOUT: int = int(os.getenv("SMTP_TIMEOUT", "10")) class AppConfig: """General application configuration.""" FRONTEND_URL: str = os.getenv("FRONTEND_URL", "http://localhost:3000") BACKEND_URL: str = os.getenv("BACKEND_URL", "http://localhost:8000") ENVIRONMENT: str = os.getenv("ENVIRONMENT", "development") # Timezone configuration TIMEZONE: str = os.getenv("TIMEZONE", "UTC") DEFAULT_DISPLAY_TIMEZONE: str = os.getenv("DEFAULT_DISPLAY_TIMEZONE", "UTC") class ServerConfig: """Configuration for server runtime.""" HOST: str = os.getenv("SELFMEMORY_SERVER_HOST", "0.0.0.0") PORT: int = int(os.getenv("SELFMEMORY_SERVER_PORT", "8000")) class VectorStoreConfig: """Configuration for vector store.""" PROVIDER: str | None = os.getenv("VECTOR_STORE_PROVIDER") COLLECTION_NAME: str = os.getenv("QDRANT_COLLECTION_NAME", "memories") HOST: str | None = os.getenv("QDRANT_HOST") PORT: int | None = ( int(os.getenv("QDRANT_PORT")) if os.getenv("QDRANT_PORT") else None ) class EmbeddingConfig: """Configuration for embeddings.""" PROVIDER: str | None = os.getenv("EMBEDDING_PROVIDER") MODEL: str | None = os.getenv("EMBEDDING_MODEL") OLLAMA_BASE_URL: str | None = os.getenv("OLLAMA_BASE_URL") class ValidationConfig: """Configuration for input validation.""" # Organization name validation ORG_NAME_MIN_LENGTH: int = 2 ORG_NAME_MAX_LENGTH: int = 100 ORG_NAME_PATTERN: str = r"^[a-zA-Z0-9\s\-\_]+$" # Project name validation PROJECT_NAME_MIN_LENGTH: int = 2 PROJECT_NAME_MAX_LENGTH: int = 100 PROJECT_NAME_PATTERN: str = r"^[a-zA-Z0-9\s\-\_]+$" # Tag validation TAG_MIN_LENGTH: int = 1 TAG_MAX_LENGTH: int = 50 TAG_PATTERN: str = r"^[a-zA-Z0-9\-\_]+$" # Memory content validation MEMORY_CONTENT_MAX_LENGTH: int = 10000 class RateLimitConfig: """Configuration for rate limiting.""" # Invitation endpoints (strict) INVITATION_CREATE: str = "5/minute" INVITATION_ACCEPT: str = "3/minute" # Memory operations MEMORY_CREATE: str = "20/minute" MEMORY_READ: str = "60/minute" MEMORY_SEARCH: str = "30/minute" # Project/Organization creation PROJECT_CREATE: str = "10/minute" ORGANIZATION_CREATE: str = "10/minute" # Default for other operations DEFAULT: str = "120/minute" class HealthConfig: """Configuration for health checks.""" ENABLE_DETAILED_CHECKS: bool = ( os.getenv("HEALTH_ENABLE_DETAILED_CHECKS", "true").lower() == "true" ) TIMEOUT_SECONDS: int = int(os.getenv("HEALTH_TIMEOUT_SECONDS", "5")) MEMORY_THRESHOLD_MB: int = int(os.getenv("HEALTH_MEMORY_THRESHOLD_MB", "900")) class MetricsConfig: """Configuration for metrics and monitoring.""" ENABLED: bool = os.getenv("METRICS_ENABLED", "false").lower() == "true" ENDPOINT: str = "/metrics" INCLUDE_REQUEST_DURATION: bool = True INCLUDE_RESPONSE_SIZE: bool = True class LoggingConfig: """Configuration for structured logging.""" FORMAT: str = os.getenv("LOGGING_FORMAT", "json") # json or text LEVEL: str = os.getenv("LOG_LEVEL", "INFO").upper() INCLUDE_REQUEST_ID: bool = True SAMPLE_RATE: float = float(os.getenv("LOGGING_SAMPLE_RATE", "1.0")) class MCPConfig: """Configuration for Model Context Protocol (MCP) support.""" # Whether MCP is enabled ENABLED: bool = os.getenv("MCP_ENABLED", "true").lower() == "true" # MCP server URL (this backend's public URL) SERVER_URL: str = os.getenv("MCP_SERVER_URL", "http://localhost:8081") # Ory Hydra configuration for OAuth HYDRA_PUBLIC_URL: str = os.getenv("HYDRA_PUBLIC_URL", "http://127.0.0.1:4444") HYDRA_ADMIN_URL: str = os.getenv("HYDRA_ADMIN_URL", "http://127.0.0.1:4445") # MCP scopes - supports both MCP standard scopes and memory-specific scopes SCOPES_SUPPORTED: list[str] = [ "memories:read", "memories:write", "mcp:tools", "mcp:resources", ] # Resource documentation RESOURCE_DOCUMENTATION_URL: str = os.getenv( "MCP_RESOURCE_DOCUMENTATION_URL", "https://docs.selfmemory.com" ) # Main configuration object class Config: """Main configuration class that aggregates all config sections.""" error = ErrorConfig() database = DatabaseConfig() security = SecurityConfig() auth = AuthConfig() pagination = PaginationConfig() email = EmailConfig() app = AppConfig() server = ServerConfig() vector_store = VectorStoreConfig() embedding = EmbeddingConfig() validation = ValidationConfig() rate_limit = RateLimitConfig() health = HealthConfig() metrics = MetricsConfig() logging = LoggingConfig() mcp = MCPConfig() @classmethod def validate(cls) -> list[str]: """ Validate configuration on startup. Returns: list[str]: List of validation errors (empty if all valid) """ errors = [] # Check required configurations if not cls.database.URI: errors.append("MONGODB_URI is required") if cls.security.RATE_LIMIT_ENABLED and not cls.security.RATE_LIMIT_STORAGE_URL: errors.append( "RATE_LIMIT_STORAGE_URL is required when rate limiting is enabled" ) # Validate environment if cls.app.ENVIRONMENT not in ["development", "staging", "production"]: errors.append(f"Invalid ENVIRONMENT: {cls.app.ENVIRONMENT}") # Security checks for production if cls.app.ENVIRONMENT == "production": if cls.error.EXPOSE_DETAILS: errors.append("ERROR_EXPOSE_DETAILS must be false in production") if not cls.security.CSRF_SECRET_KEY: errors.append("CSRF_SECRET_KEY is required in production") return errors @classmethod def log_config(cls) -> None: """Log current configuration (excluding sensitive values).""" import logging logger = logging.getLogger(__name__) logger.info("=" * 50) logger.info("SERVER CONFIGURATION") logger.info("=" * 50) logger.info(f"Environment: {cls.app.ENVIRONMENT}") logger.info(f"Server: {cls.server.HOST}:{cls.server.PORT}") logger.info(f"Frontend URL: {cls.app.FRONTEND_URL}") logger.info(f"Backend URL: {cls.app.BACKEND_URL}") logger.info(f"Database Timeout: {cls.database.TIMEOUT}s") logger.info( f"Rate Limiting: {'Enabled' if cls.security.RATE_LIMIT_ENABLED else 'Disabled'}" ) logger.info(f"SMTP Configured: {'Yes' if cls.email.SMTP_HOST else 'No'}") logger.info(f"Vector Store: {cls.vector_store.PROVIDER or 'Not configured'}") logger.info(f"Embedding Provider: {cls.embedding.PROVIDER or 'Not configured'}") logger.info(f"Metrics: {'Enabled' if cls.metrics.ENABLED else 'Disabled'}") logger.info(f"Log Level: {cls.logging.LEVEL}") logger.info(f"Error Details Exposed: {cls.error.EXPOSE_DETAILS}") logger.info("=" * 50) # Create singleton config instance config = Config()

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/shrijayan/SelfMemory'

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