"""Env validation policy: fail-fast per mandatory, soft per optional. Usage al boot di ogni mcp `__main__.py`: from mcp_common.env_validation import require_env, optional_env, summarize creds_file = require_env("CREDENTIALS_FILE", "deribit credentials JSON path") host = optional_env("HOST", default="0.0.0.0") summarize(["CREDENTIALS_FILE", "HOST", "PORT"]) """ from __future__ import annotations import logging import os import sys logger = logging.getLogger(__name__) class MissingEnvError(RuntimeError): """Mandatory env var absent or empty.""" def require_env(name: str, description: str = "") -> str: val = (os.environ.get(name) or "").strip() if not val: msg = f"missing mandatory env var: {name}" if description: msg += f" ({description})" logger.error(msg) raise MissingEnvError(msg) return val def optional_env(name: str, *, default: str = "") -> str: val = (os.environ.get(name) or "").strip() if not val: if default: logger.info("env %s not set, using default=%r", name, default) return default return val def summarize(names: list[str]) -> None: sensitive_tokens = ("SECRET", "KEY", "TOKEN", "PASSWORD", "CREDENTIAL", "WALLET") for n in names: val = os.environ.get(n) if val is None: logger.info("env[%s]: ", n) continue if any(t in n.upper() for t in sensitive_tokens): logger.info("env[%s]: ", n, len(val)) else: logger.info("env[%s]: %s", n, val) def fail_fast_if_missing(names: list[str]) -> None: missing: list[str] = [] for n in names: if not (os.environ.get(n) or "").strip(): missing.append(n) if missing: logger.error("boot aborted: missing mandatory env vars: %s", missing) print( f"FATAL: missing mandatory env vars: {missing}", file=sys.stderr, ) sys.exit(2)