adcp 2.2.0__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. {adcp-2.2.0/src/adcp.egg-info → adcp-2.3.0}/PKG-INFO +38 -2
  2. {adcp-2.2.0 → adcp-2.3.0}/README.md +36 -0
  3. {adcp-2.2.0 → adcp-2.3.0}/pyproject.toml +2 -2
  4. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/__init__.py +5 -1
  5. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/adagents.py +122 -0
  6. {adcp-2.2.0 → adcp-2.3.0/src/adcp.egg-info}/PKG-INFO +38 -2
  7. {adcp-2.2.0 → adcp-2.3.0}/tests/test_adagents.py +362 -0
  8. {adcp-2.2.0 → adcp-2.3.0}/tests/test_cli.py +22 -0
  9. {adcp-2.2.0 → adcp-2.3.0}/LICENSE +0 -0
  10. {adcp-2.2.0 → adcp-2.3.0}/setup.cfg +0 -0
  11. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/__main__.py +0 -0
  12. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/client.py +0 -0
  13. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/config.py +0 -0
  14. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/exceptions.py +0 -0
  15. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/protocols/__init__.py +0 -0
  16. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/protocols/a2a.py +0 -0
  17. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/protocols/base.py +0 -0
  18. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/protocols/mcp.py +0 -0
  19. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/py.typed +0 -0
  20. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/simple.py +0 -0
  21. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/testing/__init__.py +0 -0
  22. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/testing/test_helpers.py +0 -0
  23. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/__init__.py +0 -0
  24. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/aliases.py +0 -0
  25. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/base.py +0 -0
  26. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/core.py +0 -0
  27. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated.py +0 -0
  28. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/__init__.py +0 -0
  29. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/activate_signal_request.py +0 -0
  30. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/activate_signal_response.py +0 -0
  31. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/activation_key.py +0 -0
  32. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/adagents.py +0 -0
  33. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/asset_type.py +0 -0
  34. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/audio_asset.py +0 -0
  35. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/brand_manifest.py +0 -0
  36. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/brand_manifest_ref.py +0 -0
  37. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/build_creative_request.py +0 -0
  38. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/build_creative_response.py +0 -0
  39. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/channels.py +0 -0
  40. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/cpc_option.py +0 -0
  41. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/cpcv_option.py +0 -0
  42. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/cpm_auction_option.py +0 -0
  43. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/cpm_fixed_option.py +0 -0
  44. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/cpp_option.py +0 -0
  45. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/cpv_option.py +0 -0
  46. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/create_media_buy_request.py +0 -0
  47. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/create_media_buy_response.py +0 -0
  48. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/creative_asset.py +0 -0
  49. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/creative_assignment.py +0 -0
  50. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/creative_manifest.py +0 -0
  51. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/creative_policy.py +0 -0
  52. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/creative_status.py +0 -0
  53. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/css_asset.py +0 -0
  54. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/daast_asset.py +0 -0
  55. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/delivery_metrics.py +0 -0
  56. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/delivery_type.py +0 -0
  57. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/deployment.py +0 -0
  58. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/destination.py +0 -0
  59. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/error.py +0 -0
  60. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/flat_rate_option.py +0 -0
  61. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/format.py +0 -0
  62. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/format_id.py +0 -0
  63. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/frequency_cap.py +0 -0
  64. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/frequency_cap_scope.py +0 -0
  65. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/get_media_buy_delivery_request.py +0 -0
  66. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/get_media_buy_delivery_response.py +0 -0
  67. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/get_products_request.py +0 -0
  68. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/get_products_response.py +0 -0
  69. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/get_signals_request.py +0 -0
  70. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/get_signals_response.py +0 -0
  71. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/html_asset.py +0 -0
  72. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/identifier_types.py +0 -0
  73. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/image_asset.py +0 -0
  74. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/index.py +0 -0
  75. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/javascript_asset.py +0 -0
  76. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/list_authorized_properties_request.py +0 -0
  77. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/list_authorized_properties_response.py +0 -0
  78. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/list_creative_formats_request.py +0 -0
  79. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/list_creative_formats_response.py +0 -0
  80. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/list_creatives_request.py +0 -0
  81. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/list_creatives_response.py +0 -0
  82. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/markdown_asset.py +0 -0
  83. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/measurement.py +0 -0
  84. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/media_buy.py +0 -0
  85. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/media_buy_status.py +0 -0
  86. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/pacing.py +0 -0
  87. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/package.py +0 -0
  88. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/package_request.py +0 -0
  89. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/package_status.py +0 -0
  90. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/performance_feedback.py +0 -0
  91. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/placement.py +0 -0
  92. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/preview_creative_request.py +0 -0
  93. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/preview_creative_response.py +0 -0
  94. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/preview_render.py +0 -0
  95. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/pricing_model.py +0 -0
  96. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/pricing_option.py +0 -0
  97. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/product.py +0 -0
  98. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/promoted_offerings.py +0 -0
  99. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/promoted_products.py +0 -0
  100. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/property.py +0 -0
  101. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/protocol_envelope.py +0 -0
  102. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/provide_performance_feedback_request.py +0 -0
  103. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/provide_performance_feedback_response.py +0 -0
  104. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/publisher_identifier_types.py +0 -0
  105. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/push_notification_config.py +0 -0
  106. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/reporting_capabilities.py +0 -0
  107. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/response.py +0 -0
  108. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/standard_format_ids.py +0 -0
  109. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/start_timing.py +0 -0
  110. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/sub_asset.py +0 -0
  111. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/sync_creatives_request.py +0 -0
  112. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/sync_creatives_response.py +0 -0
  113. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/targeting.py +0 -0
  114. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/task_status.py +0 -0
  115. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/task_type.py +0 -0
  116. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/tasks_get_request.py +0 -0
  117. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/tasks_get_response.py +0 -0
  118. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/tasks_list_request.py +0 -0
  119. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/tasks_list_response.py +0 -0
  120. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/text_asset.py +0 -0
  121. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/update_media_buy_request.py +0 -0
  122. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/update_media_buy_response.py +0 -0
  123. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/url_asset.py +0 -0
  124. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/vast_asset.py +0 -0
  125. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/vcpm_auction_option.py +0 -0
  126. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/vcpm_fixed_option.py +0 -0
  127. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/video_asset.py +0 -0
  128. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/webhook_asset.py +0 -0
  129. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/webhook_payload.py +0 -0
  130. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/utils/__init__.py +0 -0
  131. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/utils/operation_id.py +0 -0
  132. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/utils/preview_cache.py +0 -0
  133. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/utils/response_parser.py +0 -0
  134. {adcp-2.2.0 → adcp-2.3.0}/src/adcp/validation.py +0 -0
  135. {adcp-2.2.0 → adcp-2.3.0}/src/adcp.egg-info/SOURCES.txt +0 -0
  136. {adcp-2.2.0 → adcp-2.3.0}/src/adcp.egg-info/dependency_links.txt +0 -0
  137. {adcp-2.2.0 → adcp-2.3.0}/src/adcp.egg-info/entry_points.txt +0 -0
  138. {adcp-2.2.0 → adcp-2.3.0}/src/adcp.egg-info/requires.txt +1 -1
  139. {adcp-2.2.0 → adcp-2.3.0}/src/adcp.egg-info/top_level.txt +0 -0
  140. {adcp-2.2.0 → adcp-2.3.0}/tests/test_client.py +0 -0
  141. {adcp-2.2.0 → adcp-2.3.0}/tests/test_code_generation.py +0 -0
  142. {adcp-2.2.0 → adcp-2.3.0}/tests/test_discriminated_unions.py +0 -0
  143. {adcp-2.2.0 → adcp-2.3.0}/tests/test_format_id_validation.py +0 -0
  144. {adcp-2.2.0 → adcp-2.3.0}/tests/test_helpers.py +0 -0
  145. {adcp-2.2.0 → adcp-2.3.0}/tests/test_preview_html.py +0 -0
  146. {adcp-2.2.0 → adcp-2.3.0}/tests/test_protocols.py +0 -0
  147. {adcp-2.2.0 → adcp-2.3.0}/tests/test_response_parser.py +0 -0
  148. {adcp-2.2.0 → adcp-2.3.0}/tests/test_simple_api.py +0 -0
  149. {adcp-2.2.0 → adcp-2.3.0}/tests/test_type_aliases.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adcp
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: Official Python client for the Ad Context Protocol (AdCP)
5
5
  Author-email: AdCP Community <maintainers@adcontextprotocol.org>
6
6
  License: Apache-2.0
@@ -26,6 +26,7 @@ Requires-Dist: pydantic>=2.0.0
26
26
  Requires-Dist: typing-extensions>=4.5.0
27
27
  Requires-Dist: a2a-sdk>=0.3.0
28
28
  Requires-Dist: mcp>=0.9.0
29
+ Requires-Dist: email-validator>=2.0.0
29
30
  Provides-Extra: dev
30
31
  Requires-Dist: pytest>=7.0.0; extra == "dev"
31
32
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
@@ -34,7 +35,6 @@ Requires-Dist: mypy>=1.0.0; extra == "dev"
34
35
  Requires-Dist: black>=23.0.0; extra == "dev"
35
36
  Requires-Dist: ruff>=0.1.0; extra == "dev"
36
37
  Requires-Dist: datamodel-code-generator[http]>=0.35.0; extra == "dev"
37
- Requires-Dist: email-validator>=2.0.0; extra == "dev"
38
38
  Dynamic: license-file
39
39
 
40
40
  # adcp - Python Client for Ad Context Protocol
@@ -544,6 +544,42 @@ is_authorized = await verify_agent_for_property(
544
544
 
545
545
  See `examples/adagents_validation.py` for complete examples.
546
546
 
547
+ ### Authorization Discovery
548
+
549
+ Discover which publishers have authorized your agent using two approaches:
550
+
551
+ **1. "Push" Approach** - Ask the agent (recommended, fastest):
552
+ ```python
553
+ from adcp import ADCPClient
554
+
555
+ async with ADCPClient(agent_config) as client:
556
+ # Single API call to agent
557
+ response = await client.simple.list_authorized_properties()
558
+ print(f"Authorized for: {response.publisher_domains}")
559
+ ```
560
+
561
+ **2. "Pull" Approach** - Check publisher adagents.json files (when you need property details):
562
+ ```python
563
+ from adcp import fetch_agent_authorizations
564
+
565
+ # Check specific publishers (fetches in parallel)
566
+ contexts = await fetch_agent_authorizations(
567
+ "https://our-sales-agent.com",
568
+ ["nytimes.com", "wsj.com", "cnn.com"]
569
+ )
570
+
571
+ for domain, ctx in contexts.items():
572
+ print(f"{domain}:")
573
+ print(f" Property IDs: {ctx.property_ids}")
574
+ print(f" Tags: {ctx.property_tags}")
575
+ ```
576
+
577
+ **When to use which:**
578
+ - **Push**: Quick discovery, portfolio overview, high-level authorization check
579
+ - **Pull**: Property-level details, specific publisher list, works offline
580
+
581
+ See `examples/fetch_agent_authorizations.py` for complete examples.
582
+
547
583
  ## CLI Tool
548
584
 
549
585
  The `adcp` command-line tool provides easy interaction with AdCP agents without writing code.
@@ -505,6 +505,42 @@ is_authorized = await verify_agent_for_property(
505
505
 
506
506
  See `examples/adagents_validation.py` for complete examples.
507
507
 
508
+ ### Authorization Discovery
509
+
510
+ Discover which publishers have authorized your agent using two approaches:
511
+
512
+ **1. "Push" Approach** - Ask the agent (recommended, fastest):
513
+ ```python
514
+ from adcp import ADCPClient
515
+
516
+ async with ADCPClient(agent_config) as client:
517
+ # Single API call to agent
518
+ response = await client.simple.list_authorized_properties()
519
+ print(f"Authorized for: {response.publisher_domains}")
520
+ ```
521
+
522
+ **2. "Pull" Approach** - Check publisher adagents.json files (when you need property details):
523
+ ```python
524
+ from adcp import fetch_agent_authorizations
525
+
526
+ # Check specific publishers (fetches in parallel)
527
+ contexts = await fetch_agent_authorizations(
528
+ "https://our-sales-agent.com",
529
+ ["nytimes.com", "wsj.com", "cnn.com"]
530
+ )
531
+
532
+ for domain, ctx in contexts.items():
533
+ print(f"{domain}:")
534
+ print(f" Property IDs: {ctx.property_ids}")
535
+ print(f" Tags: {ctx.property_tags}")
536
+ ```
537
+
538
+ **When to use which:**
539
+ - **Push**: Quick discovery, portfolio overview, high-level authorization check
540
+ - **Pull**: Property-level details, specific publisher list, works offline
541
+
542
+ See `examples/fetch_agent_authorizations.py` for complete examples.
543
+
508
544
  ## CLI Tool
509
545
 
510
546
  The `adcp` command-line tool provides easy interaction with AdCP agents without writing code.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "adcp"
7
- version = "2.2.0"
7
+ version = "2.3.0"
8
8
  description = "Official Python client for the Ad Context Protocol (AdCP)"
9
9
  authors = [
10
10
  {name = "AdCP Community", email = "maintainers@adcontextprotocol.org"}
@@ -31,6 +31,7 @@ dependencies = [
31
31
  "typing-extensions>=4.5.0",
32
32
  "a2a-sdk>=0.3.0",
33
33
  "mcp>=0.9.0",
34
+ "email-validator>=2.0.0",
34
35
  ]
35
36
 
36
37
  [project.scripts]
@@ -45,7 +46,6 @@ dev = [
45
46
  "black>=23.0.0",
46
47
  "ruff>=0.1.0",
47
48
  "datamodel-code-generator[http]>=0.35.0",
48
- "email-validator>=2.0.0",
49
49
  ]
50
50
 
51
51
  [project.urls]
@@ -8,8 +8,10 @@ Supports both A2A and MCP protocols with full type safety.
8
8
  """
9
9
 
10
10
  from adcp.adagents import (
11
+ AuthorizationContext,
11
12
  domain_matches,
12
13
  fetch_adagents,
14
+ fetch_agent_authorizations,
13
15
  get_all_properties,
14
16
  get_all_tags,
15
17
  get_properties_by_agent,
@@ -134,7 +136,7 @@ from adcp.validation import (
134
136
  validate_publisher_properties_item,
135
137
  )
136
138
 
137
- __version__ = "2.2.0"
139
+ __version__ = "2.3.0"
138
140
 
139
141
  __all__ = [
140
142
  # Client classes
@@ -178,7 +180,9 @@ __all__ = [
178
180
  "Product",
179
181
  "Property",
180
182
  # Adagents validation
183
+ "AuthorizationContext",
181
184
  "fetch_adagents",
185
+ "fetch_agent_authorizations",
182
186
  "verify_agent_authorization",
183
187
  "verify_agent_for_property",
184
188
  "domain_matches",
@@ -518,3 +518,125 @@ def get_properties_by_agent(adagents_data: dict[str, Any], agent_url: str) -> li
518
518
  return [p for p in properties if isinstance(p, dict)]
519
519
 
520
520
  return []
521
+
522
+
523
+ class AuthorizationContext:
524
+ """Authorization context for a publisher domain.
525
+
526
+ Attributes:
527
+ property_ids: List of property IDs the agent is authorized for
528
+ property_tags: List of property tags the agent is authorized for
529
+ raw_properties: Raw property data from adagents.json
530
+ """
531
+
532
+ def __init__(self, properties: list[dict[str, Any]]):
533
+ """Initialize from list of properties.
534
+
535
+ Args:
536
+ properties: List of property dictionaries from adagents.json
537
+ """
538
+ self.property_ids: list[str] = []
539
+ self.property_tags: list[str] = []
540
+ self.raw_properties = properties
541
+
542
+ # Extract property IDs and tags
543
+ for prop in properties:
544
+ if not isinstance(prop, dict):
545
+ continue
546
+
547
+ # Extract property ID
548
+ prop_id = prop.get("id")
549
+ if prop_id and isinstance(prop_id, str):
550
+ self.property_ids.append(prop_id)
551
+
552
+ # Extract tags
553
+ tags = prop.get("tags", [])
554
+ if isinstance(tags, list):
555
+ for tag in tags:
556
+ if isinstance(tag, str) and tag not in self.property_tags:
557
+ self.property_tags.append(tag)
558
+
559
+ def __repr__(self) -> str:
560
+ return (
561
+ f"AuthorizationContext("
562
+ f"property_ids={self.property_ids}, "
563
+ f"property_tags={self.property_tags})"
564
+ )
565
+
566
+
567
+ async def fetch_agent_authorizations(
568
+ agent_url: str,
569
+ publisher_domains: list[str],
570
+ timeout: float = 10.0,
571
+ client: httpx.AsyncClient | None = None,
572
+ ) -> dict[str, AuthorizationContext]:
573
+ """Fetch authorization contexts by checking publisher adagents.json files.
574
+
575
+ This function discovers what publishers have authorized your agent by fetching
576
+ their adagents.json files from the .well-known directory and extracting the
577
+ properties your agent can access.
578
+
579
+ This is the "pull" approach - you query publishers to see if they've authorized you.
580
+ For the "push" approach where the agent tells you what it's authorized for,
581
+ use the agent's list_authorized_properties endpoint via ADCPClient.
582
+
583
+ Args:
584
+ agent_url: URL of your sales agent
585
+ publisher_domains: List of publisher domains to check (e.g., ["nytimes.com", "wsj.com"])
586
+ timeout: Request timeout in seconds for each fetch
587
+ client: Optional httpx.AsyncClient for connection pooling
588
+
589
+ Returns:
590
+ Dictionary mapping publisher domain to AuthorizationContext.
591
+ Only includes domains where the agent is authorized.
592
+
593
+ Example:
594
+ >>> # "Pull" approach - check what publishers have authorized you
595
+ >>> contexts = await fetch_agent_authorizations(
596
+ ... "https://our-sales-agent.com",
597
+ ... ["nytimes.com", "wsj.com", "cnn.com"]
598
+ ... )
599
+ >>> for domain, ctx in contexts.items():
600
+ ... print(f"{domain}:")
601
+ ... print(f" Property IDs: {ctx.property_ids}")
602
+ ... print(f" Tags: {ctx.property_tags}")
603
+
604
+ See Also:
605
+ ADCPClient.list_authorized_properties: "Push" approach using the agent's API
606
+
607
+ Notes:
608
+ - Silently skips domains where adagents.json is not found or invalid
609
+ - Only returns domains where the agent is explicitly authorized
610
+ - For production use with many domains, pass a shared httpx.AsyncClient
611
+ to enable connection pooling
612
+ """
613
+ import asyncio
614
+
615
+ # Create tasks to fetch all adagents.json files in parallel
616
+ async def fetch_authorization_for_domain(
617
+ domain: str,
618
+ ) -> tuple[str, AuthorizationContext | None]:
619
+ """Fetch authorization context for a single domain."""
620
+ try:
621
+ adagents_data = await fetch_adagents(domain, timeout=timeout, client=client)
622
+
623
+ # Check if agent is authorized
624
+ if not verify_agent_authorization(adagents_data, agent_url):
625
+ return (domain, None)
626
+
627
+ # Get properties for this agent
628
+ properties = get_properties_by_agent(adagents_data, agent_url)
629
+
630
+ # Create authorization context
631
+ return (domain, AuthorizationContext(properties))
632
+
633
+ except (AdagentsNotFoundError, AdagentsValidationError, AdagentsTimeoutError):
634
+ # Silently skip domains with missing or invalid adagents.json
635
+ return (domain, None)
636
+
637
+ # Fetch all domains in parallel
638
+ tasks = [fetch_authorization_for_domain(domain) for domain in publisher_domains]
639
+ results = await asyncio.gather(*tasks)
640
+
641
+ # Build result dictionary, filtering out None values
642
+ return {domain: ctx for domain, ctx in results if ctx is not None}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adcp
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: Official Python client for the Ad Context Protocol (AdCP)
5
5
  Author-email: AdCP Community <maintainers@adcontextprotocol.org>
6
6
  License: Apache-2.0
@@ -26,6 +26,7 @@ Requires-Dist: pydantic>=2.0.0
26
26
  Requires-Dist: typing-extensions>=4.5.0
27
27
  Requires-Dist: a2a-sdk>=0.3.0
28
28
  Requires-Dist: mcp>=0.9.0
29
+ Requires-Dist: email-validator>=2.0.0
29
30
  Provides-Extra: dev
30
31
  Requires-Dist: pytest>=7.0.0; extra == "dev"
31
32
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
@@ -34,7 +35,6 @@ Requires-Dist: mypy>=1.0.0; extra == "dev"
34
35
  Requires-Dist: black>=23.0.0; extra == "dev"
35
36
  Requires-Dist: ruff>=0.1.0; extra == "dev"
36
37
  Requires-Dist: datamodel-code-generator[http]>=0.35.0; extra == "dev"
37
- Requires-Dist: email-validator>=2.0.0; extra == "dev"
38
38
  Dynamic: license-file
39
39
 
40
40
  # adcp - Python Client for Ad Context Protocol
@@ -544,6 +544,42 @@ is_authorized = await verify_agent_for_property(
544
544
 
545
545
  See `examples/adagents_validation.py` for complete examples.
546
546
 
547
+ ### Authorization Discovery
548
+
549
+ Discover which publishers have authorized your agent using two approaches:
550
+
551
+ **1. "Push" Approach** - Ask the agent (recommended, fastest):
552
+ ```python
553
+ from adcp import ADCPClient
554
+
555
+ async with ADCPClient(agent_config) as client:
556
+ # Single API call to agent
557
+ response = await client.simple.list_authorized_properties()
558
+ print(f"Authorized for: {response.publisher_domains}")
559
+ ```
560
+
561
+ **2. "Pull" Approach** - Check publisher adagents.json files (when you need property details):
562
+ ```python
563
+ from adcp import fetch_agent_authorizations
564
+
565
+ # Check specific publishers (fetches in parallel)
566
+ contexts = await fetch_agent_authorizations(
567
+ "https://our-sales-agent.com",
568
+ ["nytimes.com", "wsj.com", "cnn.com"]
569
+ )
570
+
571
+ for domain, ctx in contexts.items():
572
+ print(f"{domain}:")
573
+ print(f" Property IDs: {ctx.property_ids}")
574
+ print(f" Tags: {ctx.property_tags}")
575
+ ```
576
+
577
+ **When to use which:**
578
+ - **Push**: Quick discovery, portfolio overview, high-level authorization check
579
+ - **Pull**: Property-level details, specific publisher list, works offline
580
+
581
+ See `examples/fetch_agent_authorizations.py` for complete examples.
582
+
547
583
  ## CLI Tool
548
584
 
549
585
  The `adcp` command-line tool provides easy interaction with AdCP agents without writing code.