"""Configuration management for Midjourney MCP server."""
import os
import logging
import sys
from typing import Optional
class Config:
"""Configuration settings for the Midjourney MCP server."""
def __init__(self):
"""Initialize configuration from environment variables."""
# GPTNB API Configuration
self.gptnb_api_key = os.getenv("GPTNB_API_KEY", "")
self.gptnb_base_url = os.getenv("GPTNB_BASE_URL", "https://api.gptnb.ai")
# Request Configuration
self.timeout = int(os.getenv("TIMEOUT", "300"))
self.max_retries = int(os.getenv("MAX_RETRIES", "3"))
self.retry_delay = float(os.getenv("RETRY_DELAY", "1.0"))
# Optional Configuration
self.notify_hook = os.getenv("NOTIFY_HOOK")
# Midjourney Settings
self.default_suffix = os.getenv("DEFAULT_SUFFIX", "--v 6.1")
# Logging
self.log_level = os.getenv("LOG_LEVEL", "INFO")
# Validate configuration
self._validate()
def _validate(self):
"""Validate configuration values."""
if not self.gptnb_api_key:
raise ValueError("GPTNB_API_KEY environment variable is required")
if not self.gptnb_api_key.startswith("sk-"):
raise ValueError("GPTNB API key must start with 'sk-'")
if self.timeout <= 0:
raise ValueError("Timeout must be positive")
if self.max_retries < 0:
raise ValueError("Max retries must be non-negative")
if self.retry_delay < 0:
raise ValueError("Retry delay must be non-negative")
# Validate base URL format
if not self.gptnb_base_url.startswith(('http://', 'https://')):
raise ValueError("Base URL must start with http:// or https://")
# Validate log level
valid_log_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
if self.log_level.upper() not in valid_log_levels:
raise ValueError(f"Invalid log level: {self.log_level}. Must be one of {valid_log_levels}")
def __repr__(self):
"""String representation of config (without sensitive data)."""
return f"Config(api_key_configured={bool(self.gptnb_api_key)}, base_url={self.gptnb_base_url})"
# Global configuration instance
config = Config()
def get_config() -> Config:
"""Get the global configuration instance."""
return config
def reload_config() -> Config:
"""Reload configuration from environment."""
global config
config = Config()
return config
# ============================================================================
# Logging Configuration
# ============================================================================
def setup_logging(log_level: Optional[str] = None) -> None:
"""Setup logging configuration.
Args:
log_level: Log level override (uses config if None)
"""
config_instance = get_config()
level = log_level or config_instance.log_level
# Convert string level to logging constant
numeric_level = getattr(logging, level.upper(), logging.INFO)
# Create formatter
formatter = logging.Formatter(
fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Setup root logger
root_logger = logging.getLogger()
root_logger.setLevel(numeric_level)
# Remove existing handlers
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
# Create console handler
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(numeric_level)
console_handler.setFormatter(formatter)
root_logger.addHandler(console_handler)
# Set specific logger levels
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
# Set our package logger level
package_logger = logging.getLogger("midjourney_mcp")
package_logger.setLevel(numeric_level)
def get_logger(name: str) -> logging.Logger:
"""Get a logger with the given name.
Args:
name: Logger name
Returns:
Logger instance
"""
return logging.getLogger(f"midjourney_mcp.{name}")