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.
- {adcp-2.2.0/src/adcp.egg-info → adcp-2.3.0}/PKG-INFO +38 -2
- {adcp-2.2.0 → adcp-2.3.0}/README.md +36 -0
- {adcp-2.2.0 → adcp-2.3.0}/pyproject.toml +2 -2
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/__init__.py +5 -1
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/adagents.py +122 -0
- {adcp-2.2.0 → adcp-2.3.0/src/adcp.egg-info}/PKG-INFO +38 -2
- {adcp-2.2.0 → adcp-2.3.0}/tests/test_adagents.py +362 -0
- {adcp-2.2.0 → adcp-2.3.0}/tests/test_cli.py +22 -0
- {adcp-2.2.0 → adcp-2.3.0}/LICENSE +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/setup.cfg +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/__main__.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/client.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/config.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/exceptions.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/protocols/__init__.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/protocols/a2a.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/protocols/base.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/protocols/mcp.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/py.typed +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/simple.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/testing/__init__.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/testing/test_helpers.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/__init__.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/aliases.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/base.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/core.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/__init__.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/activate_signal_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/activate_signal_response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/activation_key.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/adagents.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/asset_type.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/audio_asset.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/brand_manifest.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/brand_manifest_ref.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/build_creative_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/build_creative_response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/channels.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/cpc_option.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/cpcv_option.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/cpm_auction_option.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/cpm_fixed_option.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/cpp_option.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/cpv_option.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/create_media_buy_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/create_media_buy_response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/creative_asset.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/creative_assignment.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/creative_manifest.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/creative_policy.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/creative_status.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/css_asset.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/daast_asset.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/delivery_metrics.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/delivery_type.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/deployment.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/destination.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/error.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/flat_rate_option.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/format.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/format_id.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/frequency_cap.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/frequency_cap_scope.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/get_media_buy_delivery_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/get_media_buy_delivery_response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/get_products_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/get_products_response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/get_signals_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/get_signals_response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/html_asset.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/identifier_types.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/image_asset.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/index.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/javascript_asset.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/list_authorized_properties_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/list_authorized_properties_response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/list_creative_formats_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/list_creative_formats_response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/list_creatives_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/list_creatives_response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/markdown_asset.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/measurement.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/media_buy.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/media_buy_status.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/pacing.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/package.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/package_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/package_status.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/performance_feedback.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/placement.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/preview_creative_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/preview_creative_response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/preview_render.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/pricing_model.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/pricing_option.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/product.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/promoted_offerings.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/promoted_products.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/property.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/protocol_envelope.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/provide_performance_feedback_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/provide_performance_feedback_response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/publisher_identifier_types.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/push_notification_config.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/reporting_capabilities.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/standard_format_ids.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/start_timing.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/sub_asset.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/sync_creatives_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/sync_creatives_response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/targeting.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/task_status.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/task_type.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/tasks_get_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/tasks_get_response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/tasks_list_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/tasks_list_response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/text_asset.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/update_media_buy_request.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/update_media_buy_response.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/url_asset.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/vast_asset.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/vcpm_auction_option.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/vcpm_fixed_option.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/video_asset.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/webhook_asset.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/types/generated_poc/webhook_payload.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/utils/__init__.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/utils/operation_id.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/utils/preview_cache.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/utils/response_parser.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp/validation.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp.egg-info/SOURCES.txt +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp.egg-info/dependency_links.txt +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp.egg-info/entry_points.txt +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp.egg-info/requires.txt +1 -1
- {adcp-2.2.0 → adcp-2.3.0}/src/adcp.egg-info/top_level.txt +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/tests/test_client.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/tests/test_code_generation.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/tests/test_discriminated_unions.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/tests/test_format_id_validation.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/tests/test_helpers.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/tests/test_preview_html.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/tests/test_protocols.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/tests/test_response_parser.py +0 -0
- {adcp-2.2.0 → adcp-2.3.0}/tests/test_simple_api.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|