"""Configuration management for MCP JIRA Server."""
import os
from enum import Enum
from typing import Optional
from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class AuthMethod(str, Enum):
"""Supported authentication methods."""
KERBEROS = "kerberos"
ADFS = "adfs"
API_TOKEN = "api_token"
BASIC = "basic"
class Config(BaseSettings):
"""Application configuration loaded from environment variables."""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)
# JIRA Configuration
jira_url: str = Field(..., description="Base URL of JIRA instance")
jira_api_version: str = Field(default="latest", description="JIRA API version")
# Authentication
auth_method: AuthMethod = Field(
default=AuthMethod.KERBEROS, description="Authentication method to use"
)
# Kerberos settings
kerberos_principal: Optional[str] = Field(
default=None, description="Kerberos service principal name"
)
kerberos_keytab_path: Optional[str] = Field(
default=None, description="Path to Kerberos keytab file"
)
kerberos_mutual_auth: bool = Field(
default=True, description="Enable mutual authentication for Kerberos"
)
# ADFS settings
adfs_url: Optional[str] = Field(
default=None, description="ADFS server URL for token acquisition"
)
# API Token settings
jira_email: Optional[str] = Field(default=None, description="Email for API token auth")
jira_api_token: Optional[str] = Field(default=None, description="JIRA API token")
# Basic auth settings
jira_username: Optional[str] = Field(default=None, description="JIRA username")
jira_password: Optional[str] = Field(default=None, description="JIRA password")
# Application settings
log_level: str = Field(default="INFO", description="Logging level")
debug: bool = Field(default=False, description="Enable debug mode")
# Custom fields
custom_fields_cache_ttl: int = Field(
default=3600, description="Custom fields cache TTL in seconds"
)
# Request settings
request_timeout: int = Field(default=30, description="Request timeout in seconds")
max_retries: int = Field(default=3, description="Maximum number of retries")
retry_backoff_factor: float = Field(default=2.0, description="Backoff factor for retries")
@field_validator("jira_url")
@classmethod
def validate_jira_url(cls, v: str) -> str:
"""Ensure JIRA URL doesn't have trailing slash."""
return v.rstrip("/")
@field_validator("auth_method", mode="before")
@classmethod
def validate_auth_method(cls, v: str) -> str:
"""Validate and normalize auth method."""
return v.lower()
def validate_auth_config(self) -> None:
"""Validate that required auth configuration is present for selected method."""
if self.auth_method == AuthMethod.KERBEROS:
if not self.kerberos_principal:
raise ValueError(
"KERBEROS_PRINCIPAL is required when using Kerberos authentication"
)
elif self.auth_method == AuthMethod.ADFS:
# ADFS can work with just Kerberos credentials
# ADFS_URL is optional - if not provided, will attempt direct Kerberos to JIRA
if not self.kerberos_principal and not self.adfs_url:
raise ValueError(
"Either KERBEROS_PRINCIPAL or ADFS_URL is required for ADFS authentication"
)
elif self.auth_method == AuthMethod.API_TOKEN:
if not self.jira_email or not self.jira_api_token:
raise ValueError(
"JIRA_EMAIL and JIRA_API_TOKEN are required for API token authentication"
)
elif self.auth_method == AuthMethod.BASIC:
if not self.jira_username or not self.jira_password:
raise ValueError(
"JIRA_USERNAME and JIRA_PASSWORD are required for basic authentication"
)
# Global config instance
_config: Optional[Config] = None
def get_config() -> Config:
"""Get or create the global configuration instance."""
global _config
if _config is None:
_config = Config()
_config.validate_auth_config()
return _config
def reload_config() -> Config:
"""Reload configuration from environment."""
global _config
_config = Config()
_config.validate_auth_config()
return _config