adcp 2.12.0__py3-none-any.whl
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/__init__.py +364 -0
- adcp/__main__.py +440 -0
- adcp/adagents.py +642 -0
- adcp/client.py +1057 -0
- adcp/config.py +82 -0
- adcp/exceptions.py +185 -0
- adcp/protocols/__init__.py +9 -0
- adcp/protocols/a2a.py +484 -0
- adcp/protocols/base.py +190 -0
- adcp/protocols/mcp.py +440 -0
- adcp/py.typed +0 -0
- adcp/simple.py +451 -0
- adcp/testing/__init__.py +53 -0
- adcp/testing/test_helpers.py +311 -0
- adcp/types/__init__.py +561 -0
- adcp/types/_generated.py +237 -0
- adcp/types/aliases.py +748 -0
- adcp/types/base.py +26 -0
- adcp/types/core.py +174 -0
- adcp/types/generated_poc/__init__.py +3 -0
- adcp/types/generated_poc/adagents.py +411 -0
- adcp/types/generated_poc/core/__init__.py +3 -0
- adcp/types/generated_poc/core/activation_key.py +30 -0
- adcp/types/generated_poc/core/assets/__init__.py +3 -0
- adcp/types/generated_poc/core/assets/audio_asset.py +26 -0
- adcp/types/generated_poc/core/assets/css_asset.py +20 -0
- adcp/types/generated_poc/core/assets/daast_asset.py +61 -0
- adcp/types/generated_poc/core/assets/html_asset.py +18 -0
- adcp/types/generated_poc/core/assets/image_asset.py +19 -0
- adcp/types/generated_poc/core/assets/javascript_asset.py +23 -0
- adcp/types/generated_poc/core/assets/text_asset.py +20 -0
- adcp/types/generated_poc/core/assets/url_asset.py +28 -0
- adcp/types/generated_poc/core/assets/vast_asset.py +63 -0
- adcp/types/generated_poc/core/assets/video_asset.py +24 -0
- adcp/types/generated_poc/core/assets/webhook_asset.py +53 -0
- adcp/types/generated_poc/core/brand_manifest.py +201 -0
- adcp/types/generated_poc/core/context.py +15 -0
- adcp/types/generated_poc/core/creative_asset.py +102 -0
- adcp/types/generated_poc/core/creative_assignment.py +27 -0
- adcp/types/generated_poc/core/creative_filters.py +86 -0
- adcp/types/generated_poc/core/creative_manifest.py +68 -0
- adcp/types/generated_poc/core/creative_policy.py +28 -0
- adcp/types/generated_poc/core/delivery_metrics.py +111 -0
- adcp/types/generated_poc/core/deployment.py +78 -0
- adcp/types/generated_poc/core/destination.py +43 -0
- adcp/types/generated_poc/core/dimensions.py +18 -0
- adcp/types/generated_poc/core/error.py +29 -0
- adcp/types/generated_poc/core/ext.py +15 -0
- adcp/types/generated_poc/core/format.py +260 -0
- adcp/types/generated_poc/core/format_id.py +50 -0
- adcp/types/generated_poc/core/frequency_cap.py +19 -0
- adcp/types/generated_poc/core/measurement.py +40 -0
- adcp/types/generated_poc/core/media_buy.py +40 -0
- adcp/types/generated_poc/core/package.py +68 -0
- adcp/types/generated_poc/core/performance_feedback.py +78 -0
- adcp/types/generated_poc/core/placement.py +37 -0
- adcp/types/generated_poc/core/product.py +164 -0
- adcp/types/generated_poc/core/product_filters.py +97 -0
- adcp/types/generated_poc/core/promoted_offerings.py +102 -0
- adcp/types/generated_poc/core/promoted_products.py +38 -0
- adcp/types/generated_poc/core/property.py +64 -0
- adcp/types/generated_poc/core/property_id.py +21 -0
- adcp/types/generated_poc/core/property_tag.py +21 -0
- adcp/types/generated_poc/core/protocol_envelope.py +61 -0
- adcp/types/generated_poc/core/publisher_property_selector.py +75 -0
- adcp/types/generated_poc/core/push_notification_config.py +51 -0
- adcp/types/generated_poc/core/reporting_capabilities.py +51 -0
- adcp/types/generated_poc/core/response.py +24 -0
- adcp/types/generated_poc/core/signal_filters.py +29 -0
- adcp/types/generated_poc/core/sub_asset.py +55 -0
- adcp/types/generated_poc/core/targeting.py +53 -0
- adcp/types/generated_poc/core/webhook_payload.py +96 -0
- adcp/types/generated_poc/creative/__init__.py +3 -0
- adcp/types/generated_poc/creative/list_creative_formats_request.py +88 -0
- adcp/types/generated_poc/creative/list_creative_formats_response.py +55 -0
- adcp/types/generated_poc/creative/preview_creative_request.py +153 -0
- adcp/types/generated_poc/creative/preview_creative_response.py +169 -0
- adcp/types/generated_poc/creative/preview_render.py +152 -0
- adcp/types/generated_poc/enums/__init__.py +3 -0
- adcp/types/generated_poc/enums/adcp_domain.py +12 -0
- adcp/types/generated_poc/enums/asset_content_type.py +23 -0
- adcp/types/generated_poc/enums/auth_scheme.py +12 -0
- adcp/types/generated_poc/enums/available_metric.py +19 -0
- adcp/types/generated_poc/enums/channels.py +19 -0
- adcp/types/generated_poc/enums/co_branding_requirement.py +13 -0
- adcp/types/generated_poc/enums/creative_action.py +15 -0
- adcp/types/generated_poc/enums/creative_agent_capability.py +14 -0
- adcp/types/generated_poc/enums/creative_sort_field.py +16 -0
- adcp/types/generated_poc/enums/creative_status.py +14 -0
- adcp/types/generated_poc/enums/daast_tracking_event.py +21 -0
- adcp/types/generated_poc/enums/daast_version.py +12 -0
- adcp/types/generated_poc/enums/delivery_type.py +12 -0
- adcp/types/generated_poc/enums/dimension_unit.py +14 -0
- adcp/types/generated_poc/enums/feed_format.py +13 -0
- adcp/types/generated_poc/enums/feedback_source.py +14 -0
- adcp/types/generated_poc/enums/format_category.py +17 -0
- adcp/types/generated_poc/enums/format_id_parameter.py +12 -0
- adcp/types/generated_poc/enums/frequency_cap_scope.py +16 -0
- adcp/types/generated_poc/enums/history_entry_type.py +12 -0
- adcp/types/generated_poc/enums/http_method.py +12 -0
- adcp/types/generated_poc/enums/identifier_types.py +29 -0
- adcp/types/generated_poc/enums/javascript_module_type.py +13 -0
- adcp/types/generated_poc/enums/landing_page_requirement.py +13 -0
- adcp/types/generated_poc/enums/markdown_flavor.py +12 -0
- adcp/types/generated_poc/enums/media_buy_status.py +14 -0
- adcp/types/generated_poc/enums/metric_type.py +18 -0
- adcp/types/generated_poc/enums/notification_type.py +14 -0
- adcp/types/generated_poc/enums/pacing.py +13 -0
- adcp/types/generated_poc/enums/preview_output_format.py +12 -0
- adcp/types/generated_poc/enums/pricing_model.py +17 -0
- adcp/types/generated_poc/enums/property_type.py +17 -0
- adcp/types/generated_poc/enums/publisher_identifier_types.py +15 -0
- adcp/types/generated_poc/enums/reporting_frequency.py +13 -0
- adcp/types/generated_poc/enums/signal_catalog_type.py +13 -0
- adcp/types/generated_poc/enums/sort_direction.py +12 -0
- adcp/types/generated_poc/enums/standard_format_ids.py +45 -0
- adcp/types/generated_poc/enums/task_status.py +19 -0
- adcp/types/generated_poc/enums/task_type.py +15 -0
- adcp/types/generated_poc/enums/update_frequency.py +14 -0
- adcp/types/generated_poc/enums/url_asset_type.py +13 -0
- adcp/types/generated_poc/enums/validation_mode.py +12 -0
- adcp/types/generated_poc/enums/vast_tracking_event.py +26 -0
- adcp/types/generated_poc/enums/vast_version.py +15 -0
- adcp/types/generated_poc/enums/webhook_response_type.py +14 -0
- adcp/types/generated_poc/enums/webhook_security_method.py +13 -0
- adcp/types/generated_poc/media_buy/__init__.py +3 -0
- adcp/types/generated_poc/media_buy/build_creative_request.py +41 -0
- adcp/types/generated_poc/media_buy/build_creative_response.py +51 -0
- adcp/types/generated_poc/media_buy/create_media_buy_request.py +94 -0
- adcp/types/generated_poc/media_buy/create_media_buy_response.py +56 -0
- adcp/types/generated_poc/media_buy/get_media_buy_delivery_request.py +47 -0
- adcp/types/generated_poc/media_buy/get_media_buy_delivery_response.py +235 -0
- adcp/types/generated_poc/media_buy/get_products_request.py +48 -0
- adcp/types/generated_poc/media_buy/get_products_response.py +28 -0
- adcp/types/generated_poc/media_buy/list_authorized_properties_request.py +38 -0
- adcp/types/generated_poc/media_buy/list_authorized_properties_response.py +84 -0
- adcp/types/generated_poc/media_buy/list_creative_formats_request.py +74 -0
- adcp/types/generated_poc/media_buy/list_creative_formats_response.py +56 -0
- adcp/types/generated_poc/media_buy/list_creatives_request.py +76 -0
- adcp/types/generated_poc/media_buy/list_creatives_response.py +214 -0
- adcp/types/generated_poc/media_buy/package_request.py +63 -0
- adcp/types/generated_poc/media_buy/provide_performance_feedback_request.py +125 -0
- adcp/types/generated_poc/media_buy/provide_performance_feedback_response.py +53 -0
- adcp/types/generated_poc/media_buy/sync_creatives_request.py +63 -0
- adcp/types/generated_poc/media_buy/sync_creatives_response.py +105 -0
- adcp/types/generated_poc/media_buy/update_media_buy_request.py +195 -0
- adcp/types/generated_poc/media_buy/update_media_buy_response.py +55 -0
- adcp/types/generated_poc/pricing_options/__init__.py +3 -0
- adcp/types/generated_poc/pricing_options/cpc_option.py +43 -0
- adcp/types/generated_poc/pricing_options/cpcv_option.py +45 -0
- adcp/types/generated_poc/pricing_options/cpm_auction_option.py +58 -0
- adcp/types/generated_poc/pricing_options/cpm_fixed_option.py +43 -0
- adcp/types/generated_poc/pricing_options/cpp_option.py +64 -0
- adcp/types/generated_poc/pricing_options/cpv_option.py +77 -0
- adcp/types/generated_poc/pricing_options/flat_rate_option.py +93 -0
- adcp/types/generated_poc/pricing_options/vcpm_auction_option.py +61 -0
- adcp/types/generated_poc/pricing_options/vcpm_fixed_option.py +47 -0
- adcp/types/generated_poc/protocols/__init__.py +3 -0
- adcp/types/generated_poc/protocols/adcp_extension.py +37 -0
- adcp/types/generated_poc/signals/__init__.py +3 -0
- adcp/types/generated_poc/signals/activate_signal_request.py +32 -0
- adcp/types/generated_poc/signals/activate_signal_response.py +51 -0
- adcp/types/generated_poc/signals/get_signals_request.py +53 -0
- adcp/types/generated_poc/signals/get_signals_response.py +59 -0
- adcp/utils/__init__.py +7 -0
- adcp/utils/operation_id.py +15 -0
- adcp/utils/preview_cache.py +491 -0
- adcp/utils/response_parser.py +171 -0
- adcp/validation.py +172 -0
- adcp-2.12.0.data/data/ADCP_VERSION +1 -0
- adcp-2.12.0.dist-info/METADATA +992 -0
- adcp-2.12.0.dist-info/RECORD +176 -0
- adcp-2.12.0.dist-info/WHEEL +5 -0
- adcp-2.12.0.dist-info/entry_points.txt +2 -0
- adcp-2.12.0.dist-info/licenses/LICENSE +17 -0
- adcp-2.12.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,992 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: adcp
|
|
3
|
+
Version: 2.12.0
|
|
4
|
+
Summary: Official Python client for the Ad Context Protocol (AdCP)
|
|
5
|
+
Author-email: AdCP Community <maintainers@adcontextprotocol.org>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/adcontextprotocol/adcp-client-python
|
|
8
|
+
Project-URL: Documentation, https://docs.adcontextprotocol.org
|
|
9
|
+
Project-URL: Repository, https://github.com/adcontextprotocol/adcp-client-python
|
|
10
|
+
Project-URL: Issues, https://github.com/adcontextprotocol/adcp-client-python/issues
|
|
11
|
+
Keywords: adcp,mcp,a2a,protocol,advertising
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: httpx>=0.24.0
|
|
25
|
+
Requires-Dist: pydantic>=2.0.0
|
|
26
|
+
Requires-Dist: typing-extensions>=4.5.0
|
|
27
|
+
Requires-Dist: a2a-sdk>=0.3.0
|
|
28
|
+
Requires-Dist: mcp>=0.9.0
|
|
29
|
+
Requires-Dist: email-validator>=2.0.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
37
|
+
Requires-Dist: datamodel-code-generator[http]>=0.35.0; extra == "dev"
|
|
38
|
+
Provides-Extra: docs
|
|
39
|
+
Requires-Dist: pdoc3>=0.10.0; extra == "docs"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
# adcp - Python Client for Ad Context Protocol
|
|
43
|
+
|
|
44
|
+
[](https://badge.fury.io/py/adcp)
|
|
45
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
46
|
+
[](https://www.python.org/downloads/)
|
|
47
|
+
|
|
48
|
+
Official Python client for the **Ad Context Protocol (AdCP)**. Build distributed advertising operations that work synchronously OR asynchronously with the same code.
|
|
49
|
+
|
|
50
|
+
## The Core Concept
|
|
51
|
+
|
|
52
|
+
AdCP operations are **distributed and asynchronous by default**. An agent might:
|
|
53
|
+
- Complete your request **immediately** (synchronous)
|
|
54
|
+
- Need time to process and **send results via webhook** (asynchronous)
|
|
55
|
+
- Ask for **clarifications** before proceeding
|
|
56
|
+
- Send periodic **status updates** as work progresses
|
|
57
|
+
|
|
58
|
+
**Your code stays the same.** You write handlers once, and they work for both sync completions and webhook deliveries.
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install adcp
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> **Note**: This client requires Python 3.10 or later and supports both synchronous and asynchronous workflows.
|
|
67
|
+
|
|
68
|
+
## Quick Start: Test Helpers
|
|
69
|
+
|
|
70
|
+
The fastest way to get started is using pre-configured test agents with the **`.simple` API**:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from adcp.testing import test_agent
|
|
74
|
+
|
|
75
|
+
# Zero configuration - just import and call with kwargs!
|
|
76
|
+
products = await test_agent.simple.get_products(
|
|
77
|
+
brief='Coffee subscription service for busy professionals'
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
print(f"Found {len(products.products)} products")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Simple vs. Standard API
|
|
84
|
+
|
|
85
|
+
**Every ADCPClient** includes both API styles via the `.simple` accessor:
|
|
86
|
+
|
|
87
|
+
**Simple API** (`client.simple.*`) - Recommended for examples/prototyping:
|
|
88
|
+
```python
|
|
89
|
+
from adcp.testing import test_agent
|
|
90
|
+
|
|
91
|
+
# Kwargs and direct return - raises on error
|
|
92
|
+
products = await test_agent.simple.get_products(brief='Coffee brands')
|
|
93
|
+
print(products.products[0].name)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Standard API** (`client.*`) - Recommended for production:
|
|
97
|
+
```python
|
|
98
|
+
from adcp.testing import test_agent
|
|
99
|
+
from adcp import GetProductsRequest
|
|
100
|
+
|
|
101
|
+
# Explicit request objects and TaskResult wrapper
|
|
102
|
+
request = GetProductsRequest(brief='Coffee brands')
|
|
103
|
+
result = await test_agent.get_products(request)
|
|
104
|
+
|
|
105
|
+
if result.success and result.data:
|
|
106
|
+
print(result.data.products[0].name)
|
|
107
|
+
else:
|
|
108
|
+
print(f"Error: {result.error}")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**When to use which:**
|
|
112
|
+
- **Simple API** (`.simple`): Quick testing, documentation, examples, notebooks
|
|
113
|
+
- **Standard API**: Production code, complex error handling, webhook workflows
|
|
114
|
+
|
|
115
|
+
### Available Test Helpers
|
|
116
|
+
|
|
117
|
+
Pre-configured agents (all include `.simple` accessor):
|
|
118
|
+
- **`test_agent`**: MCP test agent with authentication
|
|
119
|
+
- **`test_agent_a2a`**: A2A test agent with authentication
|
|
120
|
+
- **`test_agent_no_auth`**: MCP test agent without authentication
|
|
121
|
+
- **`test_agent_a2a_no_auth`**: A2A test agent without authentication
|
|
122
|
+
- **`creative_agent`**: Reference creative agent for preview functionality
|
|
123
|
+
- **`test_agent_client`**: Multi-agent client with both protocols
|
|
124
|
+
|
|
125
|
+
> **Note**: Test agents are rate-limited and for testing/examples only. DO NOT use in production.
|
|
126
|
+
|
|
127
|
+
See [examples/simple_api_demo.py](examples/simple_api_demo.py) for a complete comparison.
|
|
128
|
+
|
|
129
|
+
> **Tip**: Import types from the main `adcp` package (e.g., `from adcp import GetProductsRequest`) rather than `adcp.types.generated` for better API stability.
|
|
130
|
+
|
|
131
|
+
## Quick Start: Distributed Operations
|
|
132
|
+
|
|
133
|
+
For production use, configure your own agents:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from adcp import ADCPMultiAgentClient, AgentConfig, GetProductsRequest
|
|
137
|
+
|
|
138
|
+
# Configure agents and handlers (context manager ensures proper cleanup)
|
|
139
|
+
async with ADCPMultiAgentClient(
|
|
140
|
+
agents=[
|
|
141
|
+
AgentConfig(
|
|
142
|
+
id="agent_x",
|
|
143
|
+
agent_uri="https://agent-x.com",
|
|
144
|
+
protocol="a2a"
|
|
145
|
+
),
|
|
146
|
+
AgentConfig(
|
|
147
|
+
id="agent_y",
|
|
148
|
+
agent_uri="https://agent-y.com/mcp/",
|
|
149
|
+
protocol="mcp"
|
|
150
|
+
)
|
|
151
|
+
],
|
|
152
|
+
# Webhook URL template (macros: {agent_id}, {task_type}, {operation_id})
|
|
153
|
+
webhook_url_template="https://myapp.com/webhook/{task_type}/{agent_id}/{operation_id}",
|
|
154
|
+
|
|
155
|
+
# Activity callback - fires for ALL events
|
|
156
|
+
on_activity=lambda activity: print(f"[{activity.type}] {activity.task_type}"),
|
|
157
|
+
|
|
158
|
+
# Status change handlers
|
|
159
|
+
handlers={
|
|
160
|
+
"on_get_products_status_change": lambda response, metadata: (
|
|
161
|
+
db.save_products(metadata.operation_id, response.products)
|
|
162
|
+
if metadata.status == "completed" else None
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
) as client:
|
|
166
|
+
# Execute operation - library handles operation IDs, webhook URLs, context management
|
|
167
|
+
agent = client.agent("agent_x")
|
|
168
|
+
request = GetProductsRequest(brief="Coffee brands")
|
|
169
|
+
result = await agent.get_products(request)
|
|
170
|
+
|
|
171
|
+
# Check result
|
|
172
|
+
if result.status == "completed":
|
|
173
|
+
# Agent completed synchronously!
|
|
174
|
+
print(f"✅ Sync completion: {len(result.data.products)} products")
|
|
175
|
+
|
|
176
|
+
if result.status == "submitted":
|
|
177
|
+
# Agent will send webhook when complete
|
|
178
|
+
print(f"⏳ Async - webhook registered at: {result.submitted.webhook_url}")
|
|
179
|
+
# Connections automatically cleaned up here
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Documentation
|
|
183
|
+
|
|
184
|
+
- **[API Reference](https://adcontextprotocol.github.io/adcp-client-python/)** - Complete API documentation with type signatures and examples
|
|
185
|
+
- **[Protocol Spec](https://github.com/adcontextprotocol/adcp)** - Ad Context Protocol specification
|
|
186
|
+
- **[Examples](examples/)** - Code examples and usage patterns
|
|
187
|
+
|
|
188
|
+
The API reference documentation is automatically generated from the code and includes:
|
|
189
|
+
- Full type signatures for all methods
|
|
190
|
+
- Field descriptions from JSON Schema
|
|
191
|
+
- Method documentation with examples
|
|
192
|
+
- Searchable interface
|
|
193
|
+
|
|
194
|
+
## Features
|
|
195
|
+
|
|
196
|
+
### Test Helpers
|
|
197
|
+
|
|
198
|
+
Pre-configured test agents for instant prototyping and testing:
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from adcp.testing import (
|
|
202
|
+
test_agent, test_agent_a2a,
|
|
203
|
+
test_agent_no_auth, test_agent_a2a_no_auth,
|
|
204
|
+
creative_agent, test_agent_client, create_test_agent
|
|
205
|
+
)
|
|
206
|
+
from adcp import GetProductsRequest, PreviewCreativeRequest
|
|
207
|
+
|
|
208
|
+
# 1. Single agent with authentication (MCP)
|
|
209
|
+
result = await test_agent.get_products(
|
|
210
|
+
GetProductsRequest(brief="Coffee brands")
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# 2. Single agent with authentication (A2A)
|
|
214
|
+
result = await test_agent_a2a.get_products(
|
|
215
|
+
GetProductsRequest(brief="Coffee brands")
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# 3. Single agent WITHOUT authentication (MCP)
|
|
219
|
+
# Useful for testing unauthenticated behavior
|
|
220
|
+
result = await test_agent_no_auth.get_products(
|
|
221
|
+
GetProductsRequest(brief="Coffee brands")
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# 4. Single agent WITHOUT authentication (A2A)
|
|
225
|
+
result = await test_agent_a2a_no_auth.get_products(
|
|
226
|
+
GetProductsRequest(brief="Coffee brands")
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# 5. Creative agent (preview functionality, no auth required)
|
|
230
|
+
result = await creative_agent.preview_creative(
|
|
231
|
+
PreviewCreativeRequest(
|
|
232
|
+
manifest={"format_id": "banner_300x250", "assets": {...}}
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# 6. Multi-agent (parallel execution with both protocols)
|
|
237
|
+
results = await test_agent_client.get_products(
|
|
238
|
+
GetProductsRequest(brief="Coffee brands")
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# 7. Custom configuration
|
|
242
|
+
from adcp.client import ADCPClient
|
|
243
|
+
config = create_test_agent(id="my-test", timeout=60.0)
|
|
244
|
+
client = ADCPClient(config)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Use cases:**
|
|
248
|
+
- Quick prototyping and experimentation
|
|
249
|
+
- Example code and documentation
|
|
250
|
+
- Integration testing without mock servers
|
|
251
|
+
- Testing authentication behavior (comparing auth vs no-auth results)
|
|
252
|
+
- Learning AdCP concepts
|
|
253
|
+
|
|
254
|
+
**Important:** Test agents are public, rate-limited, and for testing only. Never use in production.
|
|
255
|
+
|
|
256
|
+
### Full Protocol Support
|
|
257
|
+
- **A2A Protocol**: Native support for Agent-to-Agent protocol
|
|
258
|
+
- **MCP Protocol**: Native support for Model Context Protocol
|
|
259
|
+
- **Auto-detection**: Automatically detect which protocol an agent uses
|
|
260
|
+
|
|
261
|
+
### Type Safety
|
|
262
|
+
|
|
263
|
+
Full type hints with Pydantic validation and auto-generated types from the AdCP spec. All commonly-used types are exported from the main `adcp` package for convenience:
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
from adcp import (
|
|
267
|
+
GetProductsRequest,
|
|
268
|
+
BrandManifest,
|
|
269
|
+
Package,
|
|
270
|
+
CpmFixedRatePricingOption,
|
|
271
|
+
MediaBuyStatus,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# All methods require typed request objects
|
|
275
|
+
request = GetProductsRequest(brief="Coffee brands", max_results=10)
|
|
276
|
+
result = await agent.get_products(request)
|
|
277
|
+
# result: TaskResult[GetProductsResponse]
|
|
278
|
+
|
|
279
|
+
if result.success:
|
|
280
|
+
for product in result.data.products:
|
|
281
|
+
print(product.name, product.pricing_options) # Full IDE autocomplete!
|
|
282
|
+
|
|
283
|
+
# Type-safe pricing with discriminators
|
|
284
|
+
pricing = CpmFixedRatePricingOption(
|
|
285
|
+
pricing_option_id="cpm_usd",
|
|
286
|
+
pricing_model="cpm",
|
|
287
|
+
is_fixed=True, # Literal[True] - type checked!
|
|
288
|
+
currency="USD",
|
|
289
|
+
rate=5.0
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# Type-safe status enums
|
|
293
|
+
if media_buy.status == MediaBuyStatus.active:
|
|
294
|
+
print("Media buy is active")
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Exported from main package:**
|
|
298
|
+
- **Core domain types**: `BrandManifest`, `Creative`, `CreativeManifest`, `MediaBuy`, `Package`
|
|
299
|
+
- **Status enums**: `CreativeStatus`, `MediaBuyStatus`, `PackageStatus`, `PricingModel`
|
|
300
|
+
- **All 9 pricing options**: `CpcPricingOption`, `CpmFixedRatePricingOption`, `VcpmAuctionPricingOption`, etc.
|
|
301
|
+
- **Request/Response types**: All 16 operations with full request/response types
|
|
302
|
+
|
|
303
|
+
#### Semantic Type Aliases
|
|
304
|
+
|
|
305
|
+
For discriminated union types (success/error responses), use semantic aliases for clearer code:
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
from adcp import (
|
|
309
|
+
CreateMediaBuySuccessResponse, # Clear: this is the success case
|
|
310
|
+
CreateMediaBuyErrorResponse, # Clear: this is the error case
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
def handle_response(
|
|
314
|
+
response: CreateMediaBuySuccessResponse | CreateMediaBuyErrorResponse
|
|
315
|
+
) -> None:
|
|
316
|
+
if isinstance(response, CreateMediaBuySuccessResponse):
|
|
317
|
+
print(f"✅ Media buy created: {response.media_buy_id}")
|
|
318
|
+
else:
|
|
319
|
+
print(f"❌ Errors: {response.errors}")
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Available semantic aliases:**
|
|
323
|
+
- Response types: `*SuccessResponse` / `*ErrorResponse` (e.g., `CreateMediaBuySuccessResponse`)
|
|
324
|
+
- Request variants: `*FormatRequest` / `*ManifestRequest` (e.g., `PreviewCreativeFormatRequest`)
|
|
325
|
+
- Preview renders: `PreviewRenderImage` / `PreviewRenderHtml` / `PreviewRenderIframe`
|
|
326
|
+
- Activation keys: `PropertyIdActivationKey` / `PropertyTagActivationKey`
|
|
327
|
+
|
|
328
|
+
See `examples/type_aliases_demo.py` for more examples.
|
|
329
|
+
|
|
330
|
+
**Import guidelines:**
|
|
331
|
+
- ✅ **DO**: Import from main package: `from adcp import GetProductsRequest`
|
|
332
|
+
- ✅ **DO**: Use semantic aliases: `from adcp import CreateMediaBuySuccessResponse`
|
|
333
|
+
- ⚠️ **AVOID**: Import from internal modules: `from adcp.types._generated import CreateMediaBuyResponse1`
|
|
334
|
+
|
|
335
|
+
The main package exports provide a stable API while internal generated types may change.
|
|
336
|
+
|
|
337
|
+
### Multi-Agent Operations
|
|
338
|
+
Execute across multiple agents simultaneously:
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
from adcp import GetProductsRequest
|
|
342
|
+
|
|
343
|
+
# Parallel execution across all agents
|
|
344
|
+
request = GetProductsRequest(brief="Coffee brands")
|
|
345
|
+
results = await client.get_products(request)
|
|
346
|
+
|
|
347
|
+
for result in results:
|
|
348
|
+
if result.status == "completed":
|
|
349
|
+
print(f"Sync: {len(result.data.products)} products")
|
|
350
|
+
elif result.status == "submitted":
|
|
351
|
+
print(f"Async: webhook to {result.submitted.webhook_url}")
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Webhook Handling
|
|
355
|
+
Single endpoint handles all webhooks:
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
from fastapi import FastAPI, Request
|
|
359
|
+
|
|
360
|
+
app = FastAPI()
|
|
361
|
+
|
|
362
|
+
@app.post("/webhook/{task_type}/{agent_id}/{operation_id}")
|
|
363
|
+
async def webhook(task_type: str, agent_id: str, operation_id: str, request: Request):
|
|
364
|
+
payload = await request.json()
|
|
365
|
+
payload["task_type"] = task_type
|
|
366
|
+
payload["operation_id"] = operation_id
|
|
367
|
+
|
|
368
|
+
# Route to agent client - handlers fire automatically
|
|
369
|
+
agent = client.agent(agent_id)
|
|
370
|
+
await agent.handle_webhook(
|
|
371
|
+
payload,
|
|
372
|
+
request.headers.get("x-adcp-signature")
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
return {"received": True}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Security
|
|
379
|
+
Webhook signature verification built-in:
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
client = ADCPMultiAgentClient(
|
|
383
|
+
agents=agents,
|
|
384
|
+
webhook_secret=os.getenv("WEBHOOK_SECRET")
|
|
385
|
+
)
|
|
386
|
+
# Signatures verified automatically on handle_webhook()
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Debug Mode
|
|
390
|
+
|
|
391
|
+
Enable debug mode to see full request/response details:
|
|
392
|
+
|
|
393
|
+
```python
|
|
394
|
+
agent_config = AgentConfig(
|
|
395
|
+
id="agent_x",
|
|
396
|
+
agent_uri="https://agent-x.com",
|
|
397
|
+
protocol="mcp",
|
|
398
|
+
debug=True # Enable debug mode
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
result = await client.agent("agent_x").get_products(brief="Coffee brands")
|
|
402
|
+
|
|
403
|
+
# Access debug information
|
|
404
|
+
if result.debug_info:
|
|
405
|
+
print(f"Duration: {result.debug_info.duration_ms}ms")
|
|
406
|
+
print(f"Request: {result.debug_info.request}")
|
|
407
|
+
print(f"Response: {result.debug_info.response}")
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Or use the CLI:
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
uvx adcp --debug myagent get_products '{"brief":"TV ads"}'
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Resource Management
|
|
417
|
+
|
|
418
|
+
**Why use async context managers?**
|
|
419
|
+
- Ensures HTTP connections are properly closed, preventing resource leaks
|
|
420
|
+
- Handles cleanup even when exceptions occur
|
|
421
|
+
- Required for production applications with connection pooling
|
|
422
|
+
- Prevents issues with async task group cleanup in MCP protocol
|
|
423
|
+
|
|
424
|
+
The recommended pattern uses async context managers:
|
|
425
|
+
|
|
426
|
+
```python
|
|
427
|
+
from adcp import ADCPClient, AgentConfig, GetProductsRequest
|
|
428
|
+
|
|
429
|
+
# Recommended: Automatic cleanup with context manager
|
|
430
|
+
config = AgentConfig(id="agent_x", agent_uri="https://...", protocol="a2a")
|
|
431
|
+
async with ADCPClient(config) as client:
|
|
432
|
+
request = GetProductsRequest(brief="Coffee brands")
|
|
433
|
+
result = await client.get_products(request)
|
|
434
|
+
# Connection automatically closed on exit
|
|
435
|
+
|
|
436
|
+
# Multi-agent client also supports context managers
|
|
437
|
+
async with ADCPMultiAgentClient(agents) as client:
|
|
438
|
+
# Execute across all agents in parallel
|
|
439
|
+
results = await client.get_products(request)
|
|
440
|
+
# All agent connections closed automatically (even if some failed)
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Manual cleanup is available for special cases (e.g., managing client lifecycle manually):
|
|
444
|
+
|
|
445
|
+
```python
|
|
446
|
+
# Use manual cleanup when you need fine-grained control over lifecycle
|
|
447
|
+
client = ADCPClient(config)
|
|
448
|
+
try:
|
|
449
|
+
result = await client.get_products(request)
|
|
450
|
+
finally:
|
|
451
|
+
await client.close() # Explicit cleanup
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
**When to use manual cleanup:**
|
|
455
|
+
- Managing client lifecycle across multiple functions
|
|
456
|
+
- Testing scenarios requiring explicit control
|
|
457
|
+
- Integration with frameworks that manage resources differently
|
|
458
|
+
|
|
459
|
+
In most cases, prefer the context manager pattern.
|
|
460
|
+
|
|
461
|
+
### Error Handling
|
|
462
|
+
|
|
463
|
+
The library provides a comprehensive exception hierarchy with helpful error messages:
|
|
464
|
+
|
|
465
|
+
```python
|
|
466
|
+
from adcp.exceptions import (
|
|
467
|
+
ADCPError, # Base exception
|
|
468
|
+
ADCPConnectionError, # Connection failed
|
|
469
|
+
ADCPAuthenticationError, # Auth failed (401, 403)
|
|
470
|
+
ADCPTimeoutError, # Request timed out
|
|
471
|
+
ADCPProtocolError, # Invalid response format
|
|
472
|
+
ADCPToolNotFoundError, # Tool not found
|
|
473
|
+
ADCPWebhookSignatureError # Invalid webhook signature
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
try:
|
|
477
|
+
result = await client.agent("agent_x").get_products(brief="Coffee")
|
|
478
|
+
except ADCPAuthenticationError as e:
|
|
479
|
+
# Exception includes agent context and helpful suggestions
|
|
480
|
+
print(f"Auth failed for {e.agent_id}: {e.message}")
|
|
481
|
+
print(f"Suggestion: {e.suggestion}")
|
|
482
|
+
except ADCPTimeoutError as e:
|
|
483
|
+
print(f"Request timed out after {e.timeout}s")
|
|
484
|
+
except ADCPConnectionError as e:
|
|
485
|
+
print(f"Connection failed: {e.message}")
|
|
486
|
+
print(f"Agent URI: {e.agent_uri}")
|
|
487
|
+
except ADCPError as e:
|
|
488
|
+
# Catch-all for other AdCP errors
|
|
489
|
+
print(f"AdCP error: {e.message}")
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
All exceptions include:
|
|
493
|
+
- **Contextual information**: agent ID, URI, and operation details
|
|
494
|
+
- **Actionable suggestions**: specific steps to fix common issues
|
|
495
|
+
- **Error classification**: proper HTTP status code handling
|
|
496
|
+
|
|
497
|
+
## Available Tools
|
|
498
|
+
|
|
499
|
+
All AdCP tools with full type safety:
|
|
500
|
+
|
|
501
|
+
**Media Buy Lifecycle:**
|
|
502
|
+
- `get_products()` - Discover advertising products
|
|
503
|
+
- `list_creative_formats()` - Get supported creative formats
|
|
504
|
+
- `create_media_buy()` - Create new media buy
|
|
505
|
+
- `update_media_buy()` - Update existing media buy
|
|
506
|
+
- `sync_creatives()` - Upload/sync creative assets
|
|
507
|
+
- `list_creatives()` - List creative assets
|
|
508
|
+
- `get_media_buy_delivery()` - Get delivery performance
|
|
509
|
+
|
|
510
|
+
**Creative Management:**
|
|
511
|
+
- `preview_creative()` - Preview creative before building
|
|
512
|
+
- `build_creative()` - Generate production-ready creative assets
|
|
513
|
+
|
|
514
|
+
**Audience & Targeting:**
|
|
515
|
+
- `list_authorized_properties()` - Get authorized properties
|
|
516
|
+
- `get_signals()` - Get audience signals
|
|
517
|
+
- `activate_signal()` - Activate audience signals
|
|
518
|
+
- `provide_performance_feedback()` - Send performance feedback
|
|
519
|
+
|
|
520
|
+
## Workflow Examples
|
|
521
|
+
|
|
522
|
+
### Complete Media Buy Workflow
|
|
523
|
+
|
|
524
|
+
A typical media buy workflow involves discovering products, creating the buy, and managing creatives:
|
|
525
|
+
|
|
526
|
+
```python
|
|
527
|
+
from adcp import ADCPClient, AgentConfig, GetProductsRequest, CreateMediaBuyRequest
|
|
528
|
+
from adcp import BrandManifest, PublisherPropertiesAll
|
|
529
|
+
|
|
530
|
+
# 1. Connect to agent
|
|
531
|
+
config = AgentConfig(id="sales_agent", agent_uri="https://...", protocol="mcp")
|
|
532
|
+
async with ADCPClient(config) as client:
|
|
533
|
+
|
|
534
|
+
# 2. Discover available products
|
|
535
|
+
products_result = await client.get_products(
|
|
536
|
+
GetProductsRequest(brief="Premium video inventory for coffee brand")
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
if products_result.success:
|
|
540
|
+
product = products_result.data.products[0]
|
|
541
|
+
print(f"Found product: {product.name}")
|
|
542
|
+
|
|
543
|
+
# 3. Create media buy reservation
|
|
544
|
+
media_buy_result = await client.create_media_buy(
|
|
545
|
+
CreateMediaBuyRequest(
|
|
546
|
+
brand_manifest=BrandManifest(
|
|
547
|
+
name="Coffee Co",
|
|
548
|
+
brand_url="https://coffeeco.com",
|
|
549
|
+
logo_url="https://coffeeco.com/logo.png",
|
|
550
|
+
# ... additional brand details
|
|
551
|
+
),
|
|
552
|
+
packages=[{
|
|
553
|
+
"package_id": product.packages[0].package_id,
|
|
554
|
+
"quantity": 1000000 # impressions
|
|
555
|
+
}],
|
|
556
|
+
publisher_properties=PublisherPropertiesAll(
|
|
557
|
+
selection_type="all" # Target all authorized properties
|
|
558
|
+
)
|
|
559
|
+
)
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
if media_buy_result.success:
|
|
563
|
+
media_buy_id = media_buy_result.data.media_buy_id
|
|
564
|
+
print(f"✅ Media buy created: {media_buy_id}")
|
|
565
|
+
|
|
566
|
+
# 4. Update media buy if needed
|
|
567
|
+
from adcp import UpdateMediaBuyPackagesRequest
|
|
568
|
+
|
|
569
|
+
update_result = await client.update_media_buy(
|
|
570
|
+
UpdateMediaBuyPackagesRequest(
|
|
571
|
+
media_buy_id=media_buy_id,
|
|
572
|
+
packages=[{
|
|
573
|
+
"package_id": product.packages[0].package_id,
|
|
574
|
+
"quantity": 1500000 # Increase budget
|
|
575
|
+
}]
|
|
576
|
+
)
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
if update_result.success:
|
|
580
|
+
print("✅ Media buy updated")
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### Complete Creative Workflow
|
|
584
|
+
|
|
585
|
+
Build and deliver production-ready creatives:
|
|
586
|
+
|
|
587
|
+
```python
|
|
588
|
+
from adcp import ADCPClient, AgentConfig
|
|
589
|
+
from adcp import PreviewCreativeFormatRequest, BuildCreativeRequest
|
|
590
|
+
from adcp import CreativeManifest, PlatformDeployment
|
|
591
|
+
|
|
592
|
+
# 1. Connect to creative agent
|
|
593
|
+
config = AgentConfig(id="creative_agent", agent_uri="https://...", protocol="mcp")
|
|
594
|
+
async with ADCPClient(config) as client:
|
|
595
|
+
|
|
596
|
+
# 2. List available formats
|
|
597
|
+
formats_result = await client.list_creative_formats()
|
|
598
|
+
|
|
599
|
+
if formats_result.success:
|
|
600
|
+
format_id = formats_result.data.formats[0].format_id
|
|
601
|
+
print(f"Using format: {format_id.id}")
|
|
602
|
+
|
|
603
|
+
# 3. Preview creative (test before building)
|
|
604
|
+
preview_result = await client.preview_creative(
|
|
605
|
+
PreviewCreativeFormatRequest(
|
|
606
|
+
target_format_id=format_id.id,
|
|
607
|
+
inputs={
|
|
608
|
+
"headline": "Fresh Coffee Daily",
|
|
609
|
+
"cta": "Order Now"
|
|
610
|
+
},
|
|
611
|
+
output_format="url" # Get preview URL
|
|
612
|
+
)
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
if preview_result.success:
|
|
616
|
+
preview_url = preview_result.data.renders[0].url
|
|
617
|
+
print(f"Preview at: {preview_url}")
|
|
618
|
+
|
|
619
|
+
# 4. Build production creative
|
|
620
|
+
build_result = await client.build_creative(
|
|
621
|
+
BuildCreativeRequest(
|
|
622
|
+
manifest=CreativeManifest(
|
|
623
|
+
format_id=format_id,
|
|
624
|
+
brand_url="https://coffeeco.com",
|
|
625
|
+
# ... creative content
|
|
626
|
+
),
|
|
627
|
+
target_format_id=format_id.id,
|
|
628
|
+
deployment=PlatformDeployment(
|
|
629
|
+
type="platform",
|
|
630
|
+
platform_id="google_admanager"
|
|
631
|
+
)
|
|
632
|
+
)
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
if build_result.success:
|
|
636
|
+
vast_url = build_result.data.assets[0].url
|
|
637
|
+
print(f"✅ Creative ready: {vast_url}")
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### Integrated Workflow: Media Buy + Creatives
|
|
641
|
+
|
|
642
|
+
Combine both workflows for a complete campaign setup:
|
|
643
|
+
|
|
644
|
+
```python
|
|
645
|
+
from adcp import ADCPMultiAgentClient, AgentConfig
|
|
646
|
+
from adcp import GetProductsRequest, CreateMediaBuyRequest, BuildCreativeRequest
|
|
647
|
+
|
|
648
|
+
# Connect to both sales and creative agents
|
|
649
|
+
async with ADCPMultiAgentClient(
|
|
650
|
+
agents=[
|
|
651
|
+
AgentConfig(id="sales", agent_uri="https://sales-agent.com", protocol="mcp"),
|
|
652
|
+
AgentConfig(id="creative", agent_uri="https://creative-agent.com", protocol="mcp"),
|
|
653
|
+
]
|
|
654
|
+
) as client:
|
|
655
|
+
|
|
656
|
+
# 1. Get products from sales agent
|
|
657
|
+
sales_agent = client.agent("sales")
|
|
658
|
+
products = await sales_agent.simple.get_products(
|
|
659
|
+
brief="Premium video inventory"
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
# 2. Get creative formats from creative agent
|
|
663
|
+
creative_agent = client.agent("creative")
|
|
664
|
+
formats = await creative_agent.simple.list_creative_formats()
|
|
665
|
+
|
|
666
|
+
# 3. Build creative asset
|
|
667
|
+
creative_result = await creative_agent.build_creative(
|
|
668
|
+
BuildCreativeRequest(
|
|
669
|
+
manifest=creative_manifest,
|
|
670
|
+
target_format_id=formats.formats[0].format_id.id
|
|
671
|
+
)
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
# 4. Create media buy with creative
|
|
675
|
+
media_buy_result = await sales_agent.create_media_buy(
|
|
676
|
+
CreateMediaBuyRequest(
|
|
677
|
+
brand_manifest=brand_manifest,
|
|
678
|
+
packages=[{"package_id": products.products[0].packages[0].package_id}],
|
|
679
|
+
publisher_properties=publisher_properties,
|
|
680
|
+
creative_urls=[creative_result.data.assets[0].url]
|
|
681
|
+
)
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
print(f"✅ Campaign live: {media_buy_result.data.media_buy_id}")
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
## Property Discovery (AdCP v2.2.0)
|
|
688
|
+
|
|
689
|
+
Build agent registries by discovering properties agents can sell:
|
|
690
|
+
|
|
691
|
+
```python
|
|
692
|
+
from adcp.discovery import PropertyCrawler, get_property_index
|
|
693
|
+
|
|
694
|
+
# Crawl agents to discover properties
|
|
695
|
+
crawler = PropertyCrawler()
|
|
696
|
+
await crawler.crawl_agents([
|
|
697
|
+
{"agent_url": "https://agent-x.com", "protocol": "a2a"},
|
|
698
|
+
{"agent_url": "https://agent-y.com/mcp/", "protocol": "mcp"}
|
|
699
|
+
])
|
|
700
|
+
|
|
701
|
+
index = get_property_index()
|
|
702
|
+
|
|
703
|
+
# Query 1: Who can sell this property?
|
|
704
|
+
matches = index.find_agents_for_property("domain", "cnn.com")
|
|
705
|
+
|
|
706
|
+
# Query 2: What can this agent sell?
|
|
707
|
+
auth = index.get_agent_authorizations("https://agent-x.com")
|
|
708
|
+
|
|
709
|
+
# Query 3: Find by tags
|
|
710
|
+
premium = index.find_agents_by_property_tags(["premium", "ctv"])
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
## Publisher Authorization Validation
|
|
714
|
+
|
|
715
|
+
Verify sales agents are authorized to sell publisher properties via adagents.json:
|
|
716
|
+
|
|
717
|
+
```python
|
|
718
|
+
from adcp import (
|
|
719
|
+
fetch_adagents,
|
|
720
|
+
verify_agent_authorization,
|
|
721
|
+
verify_agent_for_property,
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
# Fetch and parse adagents.json from publisher
|
|
725
|
+
adagents_data = await fetch_adagents("publisher.com")
|
|
726
|
+
|
|
727
|
+
# Verify agent authorization for a property
|
|
728
|
+
is_authorized = verify_agent_authorization(
|
|
729
|
+
adagents_data=adagents_data,
|
|
730
|
+
agent_url="https://sales-agent.example.com",
|
|
731
|
+
property_type="website",
|
|
732
|
+
property_identifiers=[{"type": "domain", "value": "publisher.com"}]
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
# Or use convenience wrapper (fetch + verify in one call)
|
|
736
|
+
is_authorized = await verify_agent_for_property(
|
|
737
|
+
publisher_domain="publisher.com",
|
|
738
|
+
agent_url="https://sales-agent.example.com",
|
|
739
|
+
property_identifiers=[{"type": "domain", "value": "publisher.com"}],
|
|
740
|
+
property_type="website"
|
|
741
|
+
)
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
**Domain Matching Rules:**
|
|
745
|
+
- Exact match: `example.com` matches `example.com`
|
|
746
|
+
- Common subdomains: `www.example.com` matches `example.com`
|
|
747
|
+
- Wildcards: `api.example.com` matches `*.example.com`
|
|
748
|
+
- Protocol-agnostic: `http://agent.com` matches `https://agent.com`
|
|
749
|
+
|
|
750
|
+
**Use Cases:**
|
|
751
|
+
- Sales agents verify authorization before accepting media buys
|
|
752
|
+
- Publishers test their adagents.json files
|
|
753
|
+
- Developer tools build authorization validators
|
|
754
|
+
|
|
755
|
+
See `examples/adagents_validation.py` for complete examples.
|
|
756
|
+
|
|
757
|
+
### Authorization Discovery
|
|
758
|
+
|
|
759
|
+
Discover which publishers have authorized your agent using two approaches:
|
|
760
|
+
|
|
761
|
+
**1. "Push" Approach** - Ask the agent (recommended, fastest):
|
|
762
|
+
```python
|
|
763
|
+
from adcp import ADCPClient
|
|
764
|
+
|
|
765
|
+
async with ADCPClient(agent_config) as client:
|
|
766
|
+
# Single API call to agent
|
|
767
|
+
response = await client.simple.list_authorized_properties()
|
|
768
|
+
print(f"Authorized for: {response.publisher_domains}")
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
**2. "Pull" Approach** - Check publisher adagents.json files (when you need property details):
|
|
772
|
+
```python
|
|
773
|
+
from adcp import fetch_agent_authorizations
|
|
774
|
+
|
|
775
|
+
# Check specific publishers (fetches in parallel)
|
|
776
|
+
contexts = await fetch_agent_authorizations(
|
|
777
|
+
"https://our-sales-agent.com",
|
|
778
|
+
["nytimes.com", "wsj.com", "cnn.com"]
|
|
779
|
+
)
|
|
780
|
+
|
|
781
|
+
for domain, ctx in contexts.items():
|
|
782
|
+
print(f"{domain}:")
|
|
783
|
+
print(f" Property IDs: {ctx.property_ids}")
|
|
784
|
+
print(f" Tags: {ctx.property_tags}")
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
**When to use which:**
|
|
788
|
+
- **Push**: Quick discovery, portfolio overview, high-level authorization check
|
|
789
|
+
- **Pull**: Property-level details, specific publisher list, works offline
|
|
790
|
+
|
|
791
|
+
See `examples/fetch_agent_authorizations.py` for complete examples.
|
|
792
|
+
|
|
793
|
+
## CLI Tool
|
|
794
|
+
|
|
795
|
+
The `adcp` command-line tool provides easy interaction with AdCP agents without writing code.
|
|
796
|
+
|
|
797
|
+
### Installation
|
|
798
|
+
|
|
799
|
+
```bash
|
|
800
|
+
# Install globally
|
|
801
|
+
pip install adcp
|
|
802
|
+
|
|
803
|
+
# Or use uvx to run without installing
|
|
804
|
+
uvx adcp --help
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
### Quick Start
|
|
808
|
+
|
|
809
|
+
```bash
|
|
810
|
+
# Save agent configuration
|
|
811
|
+
uvx adcp --save-auth myagent https://agent.example.com mcp
|
|
812
|
+
|
|
813
|
+
# List tools available on agent
|
|
814
|
+
uvx adcp myagent list_tools
|
|
815
|
+
|
|
816
|
+
# Execute a tool
|
|
817
|
+
uvx adcp myagent get_products '{"brief":"TV ads"}'
|
|
818
|
+
|
|
819
|
+
# Use from stdin
|
|
820
|
+
echo '{"brief":"TV ads"}' | uvx adcp myagent get_products
|
|
821
|
+
|
|
822
|
+
# Use from file
|
|
823
|
+
uvx adcp myagent get_products @request.json
|
|
824
|
+
|
|
825
|
+
# Get JSON output
|
|
826
|
+
uvx adcp --json myagent get_products '{"brief":"TV ads"}'
|
|
827
|
+
|
|
828
|
+
# Enable debug mode
|
|
829
|
+
uvx adcp --debug myagent get_products '{"brief":"TV ads"}'
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
### Using Test Agents from CLI
|
|
833
|
+
|
|
834
|
+
The CLI provides easy access to public test agents without configuration:
|
|
835
|
+
|
|
836
|
+
```bash
|
|
837
|
+
# Use test agent with authentication (MCP)
|
|
838
|
+
uvx adcp https://test-agent.adcontextprotocol.org/mcp/ \
|
|
839
|
+
--auth 1v8tAhASaUYYp4odoQ1PnMpdqNaMiTrCRqYo9OJp6IQ \
|
|
840
|
+
get_products '{"brief":"Coffee brands"}'
|
|
841
|
+
|
|
842
|
+
# Use test agent WITHOUT authentication (MCP)
|
|
843
|
+
uvx adcp https://test-agent.adcontextprotocol.org/mcp/ \
|
|
844
|
+
get_products '{"brief":"Coffee brands"}'
|
|
845
|
+
|
|
846
|
+
# Use test agent with authentication (A2A)
|
|
847
|
+
uvx adcp --protocol a2a \
|
|
848
|
+
--auth 1v8tAhASaUYYp4odoQ1PnMpdqNaMiTrCRqYo9OJp6IQ \
|
|
849
|
+
https://test-agent.adcontextprotocol.org \
|
|
850
|
+
get_products '{"brief":"Coffee brands"}'
|
|
851
|
+
|
|
852
|
+
# Save test agent for easier access
|
|
853
|
+
uvx adcp --save-auth test-agent https://test-agent.adcontextprotocol.org/mcp/ mcp
|
|
854
|
+
# Enter token when prompted: 1v8tAhASaUYYp4odoQ1PnMpdqNaMiTrCRqYo9OJp6IQ
|
|
855
|
+
|
|
856
|
+
# Now use saved config
|
|
857
|
+
uvx adcp test-agent get_products '{"brief":"Coffee brands"}'
|
|
858
|
+
|
|
859
|
+
# Use creative agent (no auth required)
|
|
860
|
+
uvx adcp https://creative.adcontextprotocol.org/mcp \
|
|
861
|
+
preview_creative @creative_manifest.json
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
**Test Agent Details:**
|
|
865
|
+
- **URL (MCP)**: `https://test-agent.adcontextprotocol.org/mcp/`
|
|
866
|
+
- **URL (A2A)**: `https://test-agent.adcontextprotocol.org`
|
|
867
|
+
- **Auth Token**: `1v8tAhASaUYYp4odoQ1PnMpdqNaMiTrCRqYo9OJp6IQ` (optional, public token)
|
|
868
|
+
- **Rate Limited**: For testing only, not for production
|
|
869
|
+
- **No Auth Mode**: Omit `--auth` flag to test unauthenticated behavior
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
### Configuration Management
|
|
873
|
+
|
|
874
|
+
```bash
|
|
875
|
+
# Save agent with authentication
|
|
876
|
+
uvx adcp --save-auth myagent https://agent.example.com mcp
|
|
877
|
+
# Prompts for optional auth token
|
|
878
|
+
|
|
879
|
+
# List saved agents
|
|
880
|
+
uvx adcp --list-agents
|
|
881
|
+
|
|
882
|
+
# Remove saved agent
|
|
883
|
+
uvx adcp --remove-agent myagent
|
|
884
|
+
|
|
885
|
+
# Show config file location
|
|
886
|
+
uvx adcp --show-config
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
### Direct URL Access
|
|
890
|
+
|
|
891
|
+
```bash
|
|
892
|
+
# Use URL directly without saving
|
|
893
|
+
uvx adcp https://agent.example.com/mcp list_tools
|
|
894
|
+
|
|
895
|
+
# Override protocol
|
|
896
|
+
uvx adcp --protocol a2a https://agent.example.com list_tools
|
|
897
|
+
|
|
898
|
+
# Pass auth token
|
|
899
|
+
uvx adcp --auth YOUR_TOKEN https://agent.example.com list_tools
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
### Examples
|
|
903
|
+
|
|
904
|
+
```bash
|
|
905
|
+
# Get products from saved agent
|
|
906
|
+
uvx adcp myagent get_products '{"brief":"Coffee brands for digital video"}'
|
|
907
|
+
|
|
908
|
+
# Create media buy
|
|
909
|
+
uvx adcp myagent create_media_buy '{
|
|
910
|
+
"name": "Q4 Campaign",
|
|
911
|
+
"budget": 50000,
|
|
912
|
+
"start_date": "2024-01-01",
|
|
913
|
+
"end_date": "2024-03-31"
|
|
914
|
+
}'
|
|
915
|
+
|
|
916
|
+
# List creative formats with JSON output
|
|
917
|
+
uvx adcp --json myagent list_creative_formats | jq '.data'
|
|
918
|
+
|
|
919
|
+
# Debug connection issues
|
|
920
|
+
uvx adcp --debug myagent list_tools
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
### Configuration File
|
|
924
|
+
|
|
925
|
+
Agent configurations are stored in `~/.adcp/config.json`:
|
|
926
|
+
|
|
927
|
+
```json
|
|
928
|
+
{
|
|
929
|
+
"agents": {
|
|
930
|
+
"myagent": {
|
|
931
|
+
"agent_uri": "https://agent.example.com",
|
|
932
|
+
"protocol": "mcp",
|
|
933
|
+
"auth_token": "optional-token"
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
## Environment Configuration
|
|
940
|
+
|
|
941
|
+
```bash
|
|
942
|
+
# .env
|
|
943
|
+
WEBHOOK_URL_TEMPLATE="https://myapp.com/webhook/{task_type}/{agent_id}/{operation_id}"
|
|
944
|
+
WEBHOOK_SECRET="your-webhook-secret"
|
|
945
|
+
|
|
946
|
+
ADCP_AGENTS='[
|
|
947
|
+
{
|
|
948
|
+
"id": "agent_x",
|
|
949
|
+
"agent_uri": "https://agent-x.com",
|
|
950
|
+
"protocol": "a2a",
|
|
951
|
+
"auth_token_env": "AGENT_X_TOKEN"
|
|
952
|
+
}
|
|
953
|
+
]'
|
|
954
|
+
AGENT_X_TOKEN="actual-token-here"
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
```python
|
|
958
|
+
# Auto-discover from environment
|
|
959
|
+
client = ADCPMultiAgentClient.from_env()
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
## Development
|
|
963
|
+
|
|
964
|
+
```bash
|
|
965
|
+
# Install with dev dependencies
|
|
966
|
+
pip install -e ".[dev]"
|
|
967
|
+
|
|
968
|
+
# Run tests
|
|
969
|
+
pytest
|
|
970
|
+
|
|
971
|
+
# Type checking
|
|
972
|
+
mypy src/
|
|
973
|
+
|
|
974
|
+
# Format code
|
|
975
|
+
black src/ tests/
|
|
976
|
+
ruff check src/ tests/
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
## Contributing
|
|
980
|
+
|
|
981
|
+
Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
982
|
+
|
|
983
|
+
## License
|
|
984
|
+
|
|
985
|
+
Apache 2.0 License - see [LICENSE](LICENSE) file for details.
|
|
986
|
+
|
|
987
|
+
## Support
|
|
988
|
+
|
|
989
|
+
- **API Reference**: [adcontextprotocol.github.io/adcp-client-python](https://adcontextprotocol.github.io/adcp-client-python/)
|
|
990
|
+
- **Protocol Documentation**: [docs.adcontextprotocol.org](https://docs.adcontextprotocol.org)
|
|
991
|
+
- **Issues**: [GitHub Issues](https://github.com/adcontextprotocol/adcp-client-python/issues)
|
|
992
|
+
- **Protocol Spec**: [AdCP Specification](https://github.com/adcontextprotocol/adcp)
|