"""
Configuration module for LMS MCP Server
Handles application settings and environment variables
"""
import os
from typing import Dict, Any, Optional
from dotenv import load_dotenv
import logging
logger = logging.getLogger(__name__)
# Load environment variables from .env file
load_dotenv()
class Config:
"""Configuration management for LMS MCP Server"""
def __init__(self):
# LMS Configuration
self.LMS_BASE_URL = os.getenv("LMS_BASE_URL", "https://lms.paf-iast.edu.pk")
self.LMS_LOGIN_URL = os.getenv(
"LMS_LOGIN_URL", f"{self.LMS_BASE_URL}/StudentAccount/Account/Login"
)
# Browser Configuration
self.BROWSER_HEADLESS = os.getenv("BROWSER_HEADLESS", "true").lower() == "true"
self.BROWSER_TIMEOUT = int(os.getenv("BROWSER_TIMEOUT", "30"))
self.PAGE_LOAD_TIMEOUT = int(os.getenv("PAGE_LOAD_TIMEOUT", "30"))
# CAPTCHA Configuration
self.CAPTCHA_SOLVER_TIMEOUT = int(os.getenv("CAPTCHA_SOLVER_TIMEOUT", "10"))
self.CAPTCHA_MAX_ATTEMPTS = int(os.getenv("CAPTCHA_MAX_ATTEMPTS", "3"))
self.TESSERACT_PATH = os.getenv("TESSERACT_PATH", "")
# Session Configuration
self.SESSION_EXPIRY_HOURS = int(os.getenv("SESSION_EXPIRY_HOURS", "24"))
self.SESSION_FILE = os.getenv("SESSION_FILE", "session.enc")
self.SESSION_KEY_FILE = os.getenv("SESSION_KEY_FILE", "session.key")
# Security Configuration
self.ENCRYPT_SESSIONS = os.getenv("ENCRYPT_SESSIONS", "true").lower() == "true"
self.SECURE_COOKIES = os.getenv("SECURE_COOKIES", "true").lower() == "true"
# Logging Configuration
self.LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
self.LOG_FILE = os.getenv("LOG_FILE", "lms_mcp.log")
self.LOG_MAX_SIZE = int(os.getenv("LOG_MAX_SIZE", "10485760")) # 10MB
# Performance Configuration
self.REQUEST_TIMEOUT = int(os.getenv("REQUEST_TIMEOUT", "30"))
self.MAX_RETRIES = int(os.getenv("MAX_RETRIES", "3"))
self.RETRY_DELAY = float(os.getenv("RETRY_DELAY", "1.0"))
# Development Configuration
self.DEBUG_MODE = os.getenv("DEBUG_MODE", "false").lower() == "true"
self.SAVE_SCREENSHOTS = os.getenv("SAVE_SCREENSHOTS", "false").lower() == "true"
self.SCREENSHOT_DIR = os.getenv("SCREENSHOT_DIR", "screenshots")
# LMS Specific Selectors (can be customized for different LMS systems)
self.SELECTORS = {
"username_field": os.getenv("USERNAME_SELECTOR", "input[name='Username']"),
"password_field": os.getenv("PASSWORD_SELECTOR", "input[name='Password']"),
"captcha_field": os.getenv(
"CAPTCHA_SELECTOR", "input[name='CaptchaInputText']"
),
"captcha_image": os.getenv(
"CAPTCHA_IMAGE_SELECTOR", "img[src*='DefaultCaptcha/Generate']"
),
"login_button": os.getenv(
"LOGIN_BUTTON_SELECTOR", "input[type='submit'][value='Login']"
),
"logout_link": os.getenv("LOGOUT_LINK_SELECTOR", "a[href*='Logout']"),
}
# Initialize directories
self._create_directories()
# Validate configuration
self._validate_config()
def _create_directories(self):
"""Create necessary directories if they don't exist"""
directories = []
if self.SAVE_SCREENSHOTS:
directories.append(self.SCREENSHOT_DIR)
# Create log directory if log file has a path
log_dir = os.path.dirname(self.LOG_FILE)
if log_dir:
directories.append(log_dir)
for directory in directories:
if directory and not os.path.exists(directory):
try:
os.makedirs(directory, exist_ok=True)
logger.info(f"Created directory: {directory}")
except Exception as e:
logger.error(f"Failed to create directory {directory}: {str(e)}")
def _validate_config(self):
"""Validate configuration settings"""
errors = []
# Validate URLs
if not self.LMS_BASE_URL.startswith(("http://", "https://")):
errors.append("LMS_BASE_URL must start with http:// or https://")
# Validate timeouts
if self.BROWSER_TIMEOUT <= 0:
errors.append("BROWSER_TIMEOUT must be positive")
if self.PAGE_LOAD_TIMEOUT <= 0:
errors.append("PAGE_LOAD_TIMEOUT must be positive")
# Validate session expiry
if self.SESSION_EXPIRY_HOURS <= 0:
errors.append("SESSION_EXPIRY_HOURS must be positive")
# Validate retry settings
if self.MAX_RETRIES < 0:
errors.append("MAX_RETRIES must be non-negative")
if self.RETRY_DELAY < 0:
errors.append("RETRY_DELAY must be non-negative")
# Log validation errors
if errors:
for error in errors:
logger.error(f"Configuration error: {error}")
raise ValueError(f"Configuration validation failed: {'; '.join(errors)}")
logger.info("Configuration validation passed")
def get_chrome_options(self) -> list:
"""Get Chrome browser options based on configuration"""
options = [
"--no-sandbox",
"--disable-dev-shm-usage",
"--disable-gpu",
"--window-size=1920,1080",
"--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
]
if self.BROWSER_HEADLESS:
options.append("--headless")
if not self.DEBUG_MODE:
options.extend(
["--disable-logging", "--disable-extensions", "--disable-plugins"]
)
return options
def get_request_config(self) -> Dict[str, Any]:
"""Get requests library configuration"""
return {
"timeout": self.REQUEST_TIMEOUT,
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
},
}
def get_tesseract_config(self) -> str:
"""Get Tesseract OCR configuration"""
config = "--oem 3 --psm 8"
# Add character whitelist for CAPTCHA
config += " -c tessedit_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZ"
return config
def update_from_dict(self, config_dict: Dict[str, Any]):
"""Update configuration from dictionary"""
for key, value in config_dict.items():
if hasattr(self, key):
setattr(self, key, value)
logger.info(f"Updated config: {key} = {value}")
else:
logger.warning(f"Unknown config key: {key}")
def to_dict(self) -> Dict[str, Any]:
"""Convert configuration to dictionary"""
config_dict = {}
for attr in dir(self):
if not attr.startswith("_") and not callable(getattr(self, attr)):
config_dict[attr] = getattr(self, attr)
return config_dict
def save_to_env_file(self, file_path: str = ".env"):
"""Save current configuration to .env file"""
try:
config_dict = self.to_dict()
with open(file_path, "w") as f:
f.write("# LMS MCP Server Configuration\n\n")
for key, value in config_dict.items():
if isinstance(value, (str, int, float, bool)):
f.write(f"{key}={value}\n")
logger.info(f"Configuration saved to {file_path}")
except Exception as e:
logger.error(f"Failed to save configuration: {str(e)}")
@classmethod
def from_env_file(cls, file_path: str = ".env"):
"""Create configuration instance from .env file"""
if os.path.exists(file_path):
load_dotenv(file_path)
return cls()
def __str__(self) -> str:
"""String representation of configuration"""
return (
f"LMS MCP Config (Base URL: {self.LMS_BASE_URL}, Debug: {self.DEBUG_MODE})"
)
def __repr__(self) -> str:
"""Detailed string representation"""
return f"Config({self.to_dict()})"
# Global configuration instance
config = Config()