@@ -1379,6 +1379,205 @@ def _apply_pydantic_schemas() -> None:
13791379_apply_pydantic_schemas ()
13801380
13811381
1382+ def _generate_pydantic_output_schemas () -> dict [str , dict [str , Any ]]:
1383+ """Generate JSON output schemas from Pydantic response models.
1384+
1385+ Maps tool names to their corresponding response Pydantic types and
1386+ generates JSON Schema via ``model_json_schema()`` / ``TypeAdapter``.
1387+
1388+ Unlike the input schema generator, this does **not** skip ``anyOf``
1389+ union types — ``outputSchema`` on ``tools/list`` is informational
1390+ and ``anyOf`` is valid there (clients advertise what they can return,
1391+ not what they require as input).
1392+
1393+ The result is applied to ``ADCP_TOOL_DEFINITIONS`` at import time by
1394+ :func:`_apply_pydantic_output_schemas`.
1395+ """
1396+ try :
1397+ from pydantic import TypeAdapter
1398+
1399+ from adcp .types import (
1400+ AcquireRightsResponse ,
1401+ ActivateSignalResponse ,
1402+ BuildCreativeResponse ,
1403+ CalibrateContentResponse ,
1404+ CheckGovernanceResponse ,
1405+ ComplyTestControllerResponse ,
1406+ ContextMatchResponse ,
1407+ CreateCollectionListResponse ,
1408+ CreateContentStandardsResponse ,
1409+ CreateMediaBuyResponse ,
1410+ CreatePropertyListResponse ,
1411+ DeleteCollectionListResponse ,
1412+ DeletePropertyListResponse ,
1413+ GetAccountFinancialsResponse ,
1414+ GetAdcpCapabilitiesResponse ,
1415+ GetBrandIdentityResponse ,
1416+ GetCollectionListResponse ,
1417+ GetContentStandardsResponse ,
1418+ GetCreativeDeliveryResponse ,
1419+ GetCreativeFeaturesResponse ,
1420+ GetMediaBuyArtifactsResponse ,
1421+ GetMediaBuyDeliveryResponse ,
1422+ GetMediaBuysResponse ,
1423+ GetPlanAuditLogsResponse ,
1424+ GetProductsResponse ,
1425+ GetPropertyListResponse ,
1426+ GetRightsResponse ,
1427+ GetSignalsResponse ,
1428+ IdentityMatchResponse ,
1429+ ListAccountsResponse ,
1430+ ListCollectionListsResponse ,
1431+ ListContentStandardsResponse ,
1432+ ListCreativeFormatsResponse ,
1433+ ListCreativesResponse ,
1434+ ListPropertyListsResponse ,
1435+ LogEventResponse ,
1436+ PreviewCreativeResponse ,
1437+ ProvidePerformanceFeedbackResponse ,
1438+ ReportPlanOutcomeResponse ,
1439+ ReportUsageResponse ,
1440+ SiGetOfferingResponse ,
1441+ SiInitiateSessionResponse ,
1442+ SiSendMessageResponse ,
1443+ SiTerminateSessionResponse ,
1444+ SyncAccountsResponse ,
1445+ SyncAudiencesResponse ,
1446+ SyncCatalogsResponse ,
1447+ SyncCreativesResponse ,
1448+ SyncEventSourcesResponse ,
1449+ SyncGovernanceResponse ,
1450+ SyncPlansResponse ,
1451+ UpdateCollectionListResponse ,
1452+ UpdateContentStandardsResponse ,
1453+ UpdateMediaBuyResponse ,
1454+ UpdatePropertyListResponse ,
1455+ UpdateRightsResponse ,
1456+ ValidateContentDeliveryResponse ,
1457+ )
1458+ except ImportError :
1459+ return {}
1460+
1461+ _tool_to_response : dict [str , Any ] = {
1462+ # Catalog
1463+ "get_products" : GetProductsResponse ,
1464+ "list_creative_formats" : ListCreativeFormatsResponse ,
1465+ # Creative
1466+ "sync_creatives" : SyncCreativesResponse ,
1467+ "list_creatives" : ListCreativesResponse ,
1468+ "build_creative" : BuildCreativeResponse ,
1469+ "preview_creative" : PreviewCreativeResponse ,
1470+ "get_creative_delivery" : GetCreativeDeliveryResponse ,
1471+ # Media Buy
1472+ "create_media_buy" : CreateMediaBuyResponse ,
1473+ "update_media_buy" : UpdateMediaBuyResponse ,
1474+ "get_media_buy_delivery" : GetMediaBuyDeliveryResponse ,
1475+ "get_media_buys" : GetMediaBuysResponse ,
1476+ # Signals
1477+ "get_signals" : GetSignalsResponse ,
1478+ "activate_signal" : ActivateSignalResponse ,
1479+ # Account
1480+ "list_accounts" : ListAccountsResponse ,
1481+ "sync_accounts" : SyncAccountsResponse ,
1482+ "get_account_financials" : GetAccountFinancialsResponse ,
1483+ "report_usage" : ReportUsageResponse ,
1484+ # Events & Catalogs
1485+ "log_event" : LogEventResponse ,
1486+ "sync_event_sources" : SyncEventSourcesResponse ,
1487+ "sync_audiences" : SyncAudiencesResponse ,
1488+ "sync_catalogs" : SyncCatalogsResponse ,
1489+ "sync_governance" : SyncGovernanceResponse ,
1490+ # Feedback
1491+ "provide_performance_feedback" : ProvidePerformanceFeedbackResponse ,
1492+ # Protocol Discovery
1493+ "get_adcp_capabilities" : GetAdcpCapabilitiesResponse ,
1494+ # Compliance
1495+ "comply_test_controller" : ComplyTestControllerResponse ,
1496+ # Content Standards
1497+ "create_content_standards" : CreateContentStandardsResponse ,
1498+ "get_content_standards" : GetContentStandardsResponse ,
1499+ "list_content_standards" : ListContentStandardsResponse ,
1500+ "update_content_standards" : UpdateContentStandardsResponse ,
1501+ "calibrate_content" : CalibrateContentResponse ,
1502+ "validate_content_delivery" : ValidateContentDeliveryResponse ,
1503+ "get_media_buy_artifacts" : GetMediaBuyArtifactsResponse ,
1504+ # Governance
1505+ "get_creative_features" : GetCreativeFeaturesResponse ,
1506+ "sync_plans" : SyncPlansResponse ,
1507+ "check_governance" : CheckGovernanceResponse ,
1508+ "report_plan_outcome" : ReportPlanOutcomeResponse ,
1509+ "get_plan_audit_logs" : GetPlanAuditLogsResponse ,
1510+ # Property Lists
1511+ "create_property_list" : CreatePropertyListResponse ,
1512+ "get_property_list" : GetPropertyListResponse ,
1513+ "list_property_lists" : ListPropertyListsResponse ,
1514+ "update_property_list" : UpdatePropertyListResponse ,
1515+ "delete_property_list" : DeletePropertyListResponse ,
1516+ # Collection Lists
1517+ "create_collection_list" : CreateCollectionListResponse ,
1518+ "get_collection_list" : GetCollectionListResponse ,
1519+ "list_collection_lists" : ListCollectionListsResponse ,
1520+ "update_collection_list" : UpdateCollectionListResponse ,
1521+ "delete_collection_list" : DeleteCollectionListResponse ,
1522+ # Sponsored Intelligence
1523+ "si_get_offering" : SiGetOfferingResponse ,
1524+ "si_initiate_session" : SiInitiateSessionResponse ,
1525+ "si_send_message" : SiSendMessageResponse ,
1526+ "si_terminate_session" : SiTerminateSessionResponse ,
1527+ # Brand
1528+ "get_brand_identity" : GetBrandIdentityResponse ,
1529+ "get_rights" : GetRightsResponse ,
1530+ "acquire_rights" : AcquireRightsResponse ,
1531+ "update_rights" : UpdateRightsResponse ,
1532+ # TMP
1533+ "context_match" : ContextMatchResponse ,
1534+ "identity_match" : IdentityMatchResponse ,
1535+ }
1536+
1537+ schemas : dict [str , dict [str , Any ]] = {}
1538+ for tool_name , response_type in _tool_to_response .items ():
1539+ try :
1540+ if isinstance (response_type , type ) and hasattr (response_type , "model_json_schema" ):
1541+ schema = response_type .model_json_schema ()
1542+ else :
1543+ adapter = TypeAdapter (response_type )
1544+ schema = adapter .json_schema ()
1545+
1546+ schema .pop ("title" , None )
1547+
1548+ # Inline every $ref into its $defs body — same rationale as for
1549+ # inputSchema (MCP clients that don't resolve $ref see empty
1550+ # schemas). For outputSchema, anyOf at root is valid (union
1551+ # responses advertise what a tool may return), so we don't skip
1552+ # union types here.
1553+ schema = _inline_refs (schema )
1554+
1555+ schemas [tool_name ] = schema
1556+ except Exception :
1557+ logger .debug (
1558+ "Pydantic output schema generation failed for %s, skipping" ,
1559+ tool_name ,
1560+ exc_info = True ,
1561+ )
1562+
1563+ return schemas
1564+
1565+
1566+ # Generate output schemas once at import time
1567+ _PYDANTIC_OUTPUT_SCHEMAS = _generate_pydantic_output_schemas ()
1568+
1569+
1570+ def _apply_pydantic_output_schemas () -> None :
1571+ """Write Pydantic-generated outputSchemas into ADCP_TOOL_DEFINITIONS."""
1572+ for tool_def in ADCP_TOOL_DEFINITIONS :
1573+ name = tool_def ["name" ]
1574+ if name in _PYDANTIC_OUTPUT_SCHEMAS :
1575+ tool_def ["outputSchema" ] = _PYDANTIC_OUTPUT_SCHEMAS [name ]
1576+
1577+
1578+ _apply_pydantic_output_schemas ()
1579+
1580+
13821581def _is_sdk_base_class (cls_name : str ) -> bool :
13831582 """True when ``cls_name`` is registered in ``_HANDLER_TOOLS``.
13841583
0 commit comments