diff --git a/src/core/schema_adapters.py b/src/core/schema_adapters.py index 8707a6d32..5e3f5f6a9 100644 --- a/src/core/schema_adapters.py +++ b/src/core/schema_adapters.py @@ -445,6 +445,22 @@ class ListAuthorizedPropertiesResponse(AdCPBaseModel): last_updated: str | None = Field(None, description="ISO 8601 timestamp of when authorization list was last updated") errors: list[Any] | None = Field(None, description="Task-specific errors and warnings") + def dict(self, **kwargs): + """Override dict to use model_dump with exclude_none=True for AdCP compliance. + + FastMCP may use dict() for serialization instead of model_dump(). + This ensures optional fields with None values are excluded from the response. + """ + return self.model_dump(**kwargs) + + def __iter__(self): + """Override iteration to exclude None values for AdCP compliance. + + When dict() or json.dumps() iterates over the model, it will only include + non-None fields. This ensures the response is spec-compliant. + """ + return iter(self.model_dump().items()) + def __str__(self) -> str: """Return human-readable message for protocol layer. diff --git a/src/core/tools/properties.py b/src/core/tools/properties.py index b41f728a2..b8334e5b5 100644 --- a/src/core/tools/properties.py +++ b/src/core/tools/properties.py @@ -198,11 +198,14 @@ def _list_authorized_properties_impl( # Create response with AdCP spec-compliant fields # Note: Optional fields (advertising_policies, errors, etc.) should be omitted if not set, # not set to None or empty values. AdCPBaseModel.model_dump() uses exclude_none=True by default. - response = ListAuthorizedPropertiesResponse( - publisher_domains=publisher_domains, # Required per AdCP v2.4 spec - advertising_policies=advertising_policies_text, - # errors omitted - only include if there are actual errors - ) + # Build response dict with only non-None values + response_data = {"publisher_domains": publisher_domains} # Required per AdCP v2.4 spec + + # Only add optional fields if they have actual values + if advertising_policies_text: + response_data["advertising_policies"] = advertising_policies_text + + response = ListAuthorizedPropertiesResponse(**response_data) # Log audit audit_logger = get_audit_logger("AdCP", tenant_id)