from __future__ import annotations from functools import lru_cache from pydantic_settings import BaseSettings, SettingsConfigDict def parse_allowed_mcp_clients(raw: str) -> dict[str, set[str]]: out: dict[str, set[str]] = {} for part in raw.split(","): part = part.strip() if not part: continue client_id, _, redirect_uri = part.partition("|") client_id = client_id.strip() redirect_uri = redirect_uri.strip() if not client_id or not redirect_uri: continue out.setdefault(client_id, set()).add(redirect_uri) return out class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", extra="ignore") public_base_url: str = "http://127.0.0.1:48080" listen_host: str = "0.0.0.0" listen_port: int = 48080 upstream_mcp_url: str = "http://gitea-mcp:8080/mcp" oidc_issuer: str = "" oidc_client_id: str = "" oidc_client_secret: str = "" oidc_scopes: str = ( "openid profile email write:repository write:issue write:user" " write:organization write:notification write:package write:misc" ) allowed_mcp_clients: str = "" token_encryption_key: str = "" data_dir: str = "./data" dcr_enabled: bool = True dcr_max_clients: int = 2000 # Forward bearer tokens the bridge did not issue (e.g. Gitea PATs) unchanged. passthrough_unknown_tokens: bool = True # Optional Host header for the upstream request (gitea-mcp rejects other hosts). upstream_host_header: str = "" @property def issuer(self) -> str: return self.public_base_url.rstrip("/") @property def mcp_resource_url(self) -> str: return f"{self.issuer}/mcp" @property def allowed_clients(self) -> dict[str, set[str]]: return parse_allowed_mcp_clients(self.allowed_mcp_clients) @property def session_secret(self) -> bytes: return self.token_encryption_key.encode("utf-8") @lru_cache def get_settings() -> Settings: return Settings()