from __future__ import annotations from functools import lru_cache from pathlib import Path from typing import Literal from dotenv import load_dotenv from pydantic import AliasChoices, Field, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict # Carrega o .env do projeto backoffice. Variáveis já exportadas no shell continuam com prioridade. _PROJECT_ROOT = Path(__file__).resolve().parents[2] load_dotenv(_PROJECT_ROOT / ".env", override=False) load_dotenv(_PROJECT_ROOT / ".env.backoffice_mcp", override=False) class BackofficeMCPSettings(BaseSettings): """Configurações do MCP Server de Backoffice. Suporta dois estilos de configuração: - novo/padronizado: BACKOFFICE_MCP_* - original develop TIM: PMID_*, SIEBEL_*, SPEECH_*, TAIS_*, ABRT_*, PORTABILITY_* O default agora é usar os clients TIM originais quando não for explicitado mock, para não mascarar erro de configuração com resposta simulada. """ model_config = SettingsConfigDict( env_file=str(_PROJECT_ROOT / ".env"), extra="ignore", case_sensitive=False, ) host: str = Field(default="0.0.0.0", validation_alias=AliasChoices("BACKOFFICE_MCP_HOST", "MCP_HOST")) port: int = Field(default=8010, validation_alias=AliasChoices("BACKOFFICE_MCP_PORT", "MCP_PORT")) log_level: str = Field(default="info", validation_alias=AliasChoices("BACKOFFICE_MCP_LOG_LEVEL", "LOG_LEVEL")) use_mock: bool = Field(default=True, validation_alias=AliasChoices("BACKOFFICE_MCP_USE_MOCK", "MCP_USE_MOCK", "USE_MOCK")) backend_type: Literal["mock", "tim_clients", "rest", "oracle"] = Field( default="mock", validation_alias=AliasChoices("BACKOFFICE_MCP_BACKEND_TYPE", "MCP_BACKEND_TYPE"), ) # REST genérico opcional. Não é usado quando backend_type=tim_clients. rest_base_url: str = Field(default="", validation_alias=AliasChoices("BACKOFFICE_MCP_REST_BASE_URL", "BACKOFFICE_REST_BASE_URL")) rest_timeout_seconds: float = Field(default=20.0, validation_alias=AliasChoices("BACKOFFICE_MCP_REST_TIMEOUT_SECONDS", "BACKOFFICE_REST_TIMEOUT_SECONDS")) rest_api_key: str = Field(default="", validation_alias=AliasChoices("BACKOFFICE_MCP_REST_API_KEY", "BACKOFFICE_REST_API_KEY")) rest_auth_header: str = Field(default="Authorization", validation_alias=AliasChoices("BACKOFFICE_MCP_REST_AUTH_HEADER", "BACKOFFICE_REST_AUTH_HEADER")) # Oracle backend opcional. oracle_user: str = Field(default="", validation_alias=AliasChoices("BACKOFFICE_MCP_ORACLE_USER", "ORACLE_USER")) oracle_password: str = Field(default="", validation_alias=AliasChoices("BACKOFFICE_MCP_ORACLE_PASSWORD", "ORACLE_PASSWORD")) oracle_dsn: str = Field(default="", validation_alias=AliasChoices("BACKOFFICE_MCP_ORACLE_DSN", "ORACLE_DSN")) oracle_wallet_location: str = Field(default="", validation_alias=AliasChoices("BACKOFFICE_MCP_ORACLE_WALLET_LOCATION", "ORACLE_WALLET_LOCATION")) oracle_wallet_password: str = Field(default="", validation_alias=AliasChoices("BACKOFFICE_MCP_ORACLE_WALLET_PASSWORD", "ORACLE_WALLET_PASSWORD")) # TIM/develop original. Esses campos existem para health/debug e validação rápida. pmid_api_host: str | None = Field(default=None, validation_alias=AliasChoices("PMID_API_HOST")) siebel_api_host: str | None = Field(default=None, validation_alias=AliasChoices("SIEBEL_API_HOST")) speech_prediction_base_url: str | None = Field(default=None, validation_alias=AliasChoices("SPEECH_PREDICTION_BASE_URL")) speech_history_base_url: str | None = Field(default=None, validation_alias=AliasChoices("SPEECH_HISTORY_BASE_URL")) tais_db_dsn: str | None = Field(default=None, validation_alias=AliasChoices("TAIS_DB_DSN")) tais_genai_endpoint: str | None = Field(default=None, validation_alias=AliasChoices("TAIS_GENAI_ENDPOINT")) fail_open_on_backend_error: bool = Field(default=True, validation_alias=AliasChoices("BACKOFFICE_MCP_FAIL_OPEN_ON_BACKEND_ERROR", "MCP_FAIL_OPEN_ON_BACKEND_ERROR")) @field_validator("rest_base_url") @classmethod def validate_rest_base_url(cls, v: str) -> str: value = (v or "").strip() if value and not value.startswith(("http://", "https://")): raise ValueError("BACKOFFICE_MCP_REST_BASE_URL precisa começar com http:// ou https://") return value def tim_clients_configured(self) -> bool: return bool(self.pmid_api_host or self.siebel_api_host or self.speech_prediction_base_url or self.tais_db_dsn) @lru_cache(maxsize=1) def get_settings() -> BackofficeMCPSettings: return BackofficeMCPSettings()