"""
Configuration management for the MCP server
Handles loading configuration from environment variables and files
"""
import os
import json
import logging
from typing import Dict, Any, Optional
from pathlib import Path
try:
from .error_handler import ConfigurationError
except ImportError:
# Handle case when run as script
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from utils.error_handler import ConfigurationError
logger = logging.getLogger(__name__)
def load_config() -> Dict[str, Any]:
"""
Load configuration from environment variables or config file
Environment variables take precedence over config file
"""
config = {}
# Try to load from environment variables first (more secure)
jira_config = load_jira_config_from_env()
gitlab_config = load_gitlab_config_from_env()
if jira_config and gitlab_config:
config = {
"jira": jira_config,
"gitlab": gitlab_config
}
logger.info("Configuration loaded from environment variables")
else:
# Fall back to config file
config = load_config_from_file()
logger.warning("Configuration loaded from file - consider using environment variables for better security")
# Validate configuration
validate_config(config)
return config
def load_jira_config_from_env() -> Optional[Dict[str, str]]:
"""Load Jira configuration from environment variables"""
base_url = os.getenv("JIRA_BASE_URL")
email = os.getenv("JIRA_EMAIL")
api_token = os.getenv("JIRA_API_TOKEN")
if all([base_url, email, api_token]):
return {
"base_url": base_url,
"email": email,
"api_token": api_token
}
return None
def load_gitlab_config_from_env() -> Optional[Dict[str, str]]:
"""Load GitLab configuration from environment variables"""
base_url = os.getenv("GITLAB_BASE_URL", "https://gitlab.com")
access_token = os.getenv("GITLAB_ACCESS_TOKEN")
if access_token:
return {
"base_url": base_url,
"access_token": access_token
}
return None
def load_config_from_file() -> Dict[str, Any]:
"""Load configuration from config.json file"""
config_path = Path("config.json")
if not config_path.exists():
raise ConfigurationError(
"Configuration file 'config.json' not found and environment variables not set",
{"config_path": str(config_path)}
)
try:
with open(config_path, 'r') as f:
config = json.load(f)
logger.info(f"Configuration loaded from {config_path}")
return config
except json.JSONDecodeError as e:
raise ConfigurationError(
f"Invalid JSON in configuration file: {str(e)}",
{"config_path": str(config_path), "json_error": str(e)}
)
except Exception as e:
raise ConfigurationError(
f"Failed to load configuration file: {str(e)}",
{"config_path": str(config_path)}
)
def validate_config(config: Dict[str, Any]) -> None:
"""Validate the loaded configuration"""
# Check for required top-level keys
required_keys = ["jira", "gitlab"]
missing_keys = [key for key in required_keys if key not in config]
if missing_keys:
raise ConfigurationError(
f"Missing required configuration sections: {', '.join(missing_keys)}",
{"missing_keys": missing_keys}
)
# Validate Jira configuration
jira_config = config["jira"]
required_jira_fields = ["base_url", "email", "api_token"]
missing_jira_fields = [field for field in required_jira_fields if field not in jira_config or not jira_config[field]]
if missing_jira_fields:
raise ConfigurationError(
f"Missing required Jira configuration fields: {', '.join(missing_jira_fields)}",
{"missing_fields": missing_jira_fields, "section": "jira"}
)
# Validate GitLab configuration
gitlab_config = config["gitlab"]
required_gitlab_fields = ["base_url", "access_token"]
missing_gitlab_fields = [field for field in required_gitlab_fields if field not in gitlab_config or not gitlab_config[field]]
if missing_gitlab_fields:
raise ConfigurationError(
f"Missing required GitLab configuration fields: {', '.join(missing_gitlab_fields)}",
{"missing_fields": missing_gitlab_fields, "section": "gitlab"}
)
# Validate URL formats
if not jira_config["base_url"].startswith(("http://", "https://")):
raise ConfigurationError(
"Jira base_url must start with http:// or https://",
{"invalid_url": jira_config["base_url"]}
)
if not gitlab_config["base_url"].startswith(("http://", "https://")):
raise ConfigurationError(
"GitLab base_url must start with http:// or https://",
{"invalid_url": gitlab_config["base_url"]}
)
logger.info("Configuration validation passed")
def get_env_template() -> str:
"""
Return a template for environment variables
Useful for documentation and setup
"""
return """
# Jira Configuration
JIRA_BASE_URL=https://yourcompany.atlassian.net
JIRA_EMAIL=your-email@example.com
JIRA_API_TOKEN=your-jira-api-token
# GitLab Configuration
GITLAB_BASE_URL=https://gitlab.com
GITLAB_ACCESS_TOKEN=your-gitlab-personal-access-token
"""
def create_sample_config() -> None:
"""Create a sample config.json file"""
sample_config = {
"jira": {
"base_url": "https://yourcompany.atlassian.net",
"email": "your-email@example.com",
"api_token": "your-jira-api-token"
},
"gitlab": {
"base_url": "https://gitlab.com",
"access_token": "your-gitlab-personal-access-token"
}
}
config_path = Path("config.json.sample")
with open(config_path, 'w') as f:
json.dump(sample_config, f, indent=2)
logger.info(f"Sample configuration created at {config_path}")
if __name__ == "__main__":
# Create sample config when run directly
create_sample_config()
print("Sample configuration file created: config.json.sample")
print("\nEnvironment variables template:")
print(get_env_template())