"""MCP is opt-in; an incomplete OAuth resource must never start listening.""" from pathlib import Path from ipaddress import IPv4Address, IPv4Network from urllib.parse import urlsplit from pydantic import Field, model_validator from pydantic_settings import BaseSettings, SettingsConfigDict class McpSettings(BaseSettings): enabled: bool = False issuer_uri: str = "" dev_http_issuer_uri: str = "" environment: str = Field(default="production", validation_alias="ENVIRONMENT") resource_uri: str = "" audience: str = "tjwater-mcp" host_client_id: str = "tjwater-agent-mcp" allowed_hosts: list[str] = Field( default_factory=lambda: ["127.0.0.1:*", "localhost:*"] ) allowed_origins: list[str] = Field(default_factory=list) context_ttl_seconds: int = Field(default=600, ge=30, le=600) query_timeout_seconds: int = Field(default=30, ge=1, le=60) max_calculation_timeout_seconds: int = Field(default=3600, ge=120, le=86400) model_config = SettingsConfigDict( env_prefix="TJWATER_MCP_", extra="ignore", populate_by_name=True, env_file=Path(__file__).resolve().parents[2] / ".env", ) @model_validator(mode="after") def validate_resource(self): if self.dev_http_issuer_uri: dev_url = urlsplit(self.dev_http_issuer_uri) try: address = IPv4Address(dev_url.hostname or "") private_ipv4 = any(address in IPv4Network(network) for network in ( "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" )) except ValueError: private_ipv4 = False if ( self.environment != "development" or dev_url.scheme != "http" or not private_ipv4 or dev_url.username or dev_url.password or dev_url.query or dev_url.fragment or self.dev_http_issuer_uri != self.issuer_uri ): raise ValueError("development HTTP issuer requires development environment and an exact private IPv4 issuer") if not self.enabled: return self for index, value in enumerate((self.issuer_uri, self.resource_uri)): url = urlsplit(value) if ( url.scheme not in {"http", "https"} or not url.hostname or url.username or url.password or url.query or url.fragment ): raise ValueError("MCP requires explicit issuer and resource URIs") if url.scheme == "http" and url.hostname not in { "localhost", "127.0.0.1", "::1", }: if index != 0 or value != self.dev_http_issuer_uri: raise ValueError("non-loopback MCP and OAuth require HTTPS") if urlsplit(self.resource_uri).path != "/mcp": raise ValueError("MCP resource URI must identify the /mcp endpoint") if not self.audience.strip() or not self.host_client_id.strip(): raise ValueError("MCP audience and host client ID are required") return self