adcp 0.1.2__py3-none-any.whl → 1.0.2__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/types/tasks.py ADDED
@@ -0,0 +1,281 @@
1
+ """
2
+ Auto-generated Pydantic models from AdCP JSON schemas.
3
+
4
+ DO NOT EDIT THIS FILE MANUALLY.
5
+ Generated from: https://adcontextprotocol.org/schemas/v1/
6
+ To regenerate:
7
+ python scripts/sync_schemas.py
8
+ python scripts/fix_schema_refs.py
9
+ python scripts/generate_models_simple.py
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Any, Literal
15
+
16
+ from pydantic import BaseModel, Field
17
+
18
+ # Import all types from generated module
19
+ from adcp.types.generated import (
20
+ BrandManifestRef,
21
+ Channels,
22
+ CreativeAsset,
23
+ CreativeManifest,
24
+ Error,
25
+ Format,
26
+ FormatId,
27
+ PackageRequest,
28
+ Product,
29
+ PushNotificationConfig,
30
+ StartTiming,
31
+ )
32
+
33
+
34
+ class ActivateSignalRequest(BaseModel):
35
+ """Request parameters for activating a signal on a specific platform/account"""
36
+
37
+ signal_agent_segment_id: str = Field(description="The universal identifier for the signal to activate")
38
+ platform: str = Field(description="The target platform for activation")
39
+ account: str | None = Field(None, description="Account identifier (required for account-specific activation)")
40
+
41
+
42
+ class BuildCreativeRequest(BaseModel):
43
+ """Request to transform or generate a creative manifest. Takes a source manifest (which may be minimal for pure generation) and produces a target manifest in the specified format. The source manifest should include all assets required by the target format (e.g., promoted_offerings for generative formats)."""
44
+
45
+ message: str | None = Field(None, description="Natural language instructions for the transformation or generation. For pure generation, this is the creative brief. For transformation, this provides guidance on how to adapt the creative.")
46
+ creative_manifest: CreativeManifest | None = Field(None, description="Creative manifest to transform or generate from. For pure generation, this should include the target format_id and any required input assets (e.g., promoted_offerings for generative formats). For transformation (e.g., resizing, reformatting), this is the complete creative to adapt.")
47
+ target_format_id: FormatId = Field(description="Format ID to generate. The format definition specifies required input assets and output structure.")
48
+
49
+
50
+ class CreateMediaBuyRequest(BaseModel):
51
+ """Request parameters for creating a media buy"""
52
+
53
+ buyer_ref: str = Field(description="Buyer's reference identifier for this media buy")
54
+ packages: list[PackageRequest] = Field(description="Array of package configurations")
55
+ brand_manifest: BrandManifestRef = Field(description="Brand information manifest serving as the namespace and identity for this media buy. Provides brand context, assets, and product catalog. Can be provided inline or as a URL reference to a hosted manifest. Can be cached and reused across multiple requests.")
56
+ po_number: str | None = Field(None, description="Purchase order number for tracking")
57
+ start_time: StartTiming
58
+ end_time: str = Field(description="Campaign end date/time in ISO 8601 format")
59
+ reporting_webhook: Any | None = None
60
+
61
+
62
+ class GetMediaBuyDeliveryRequest(BaseModel):
63
+ """Request parameters for retrieving comprehensive delivery metrics"""
64
+
65
+ media_buy_ids: list[str] | None = Field(None, description="Array of publisher media buy IDs to get delivery data for")
66
+ buyer_refs: list[str] | None = Field(None, description="Array of buyer reference IDs to get delivery data for")
67
+ status_filter: Any | None = Field(None, description="Filter by status. Can be a single status or array of statuses")
68
+ start_date: str | None = Field(None, description="Start date for reporting period (YYYY-MM-DD)")
69
+ end_date: str | None = Field(None, description="End date for reporting period (YYYY-MM-DD)")
70
+
71
+
72
+ class GetProductsRequest(BaseModel):
73
+ """Request parameters for discovering available advertising products"""
74
+
75
+ brief: str | None = Field(None, description="Natural language description of campaign requirements")
76
+ brand_manifest: BrandManifestRef | None = Field(None, description="Brand information manifest providing brand context, assets, and product catalog. Can be provided inline or as a URL reference to a hosted manifest.")
77
+ filters: dict[str, Any] | None = Field(None, description="Structured filters for product discovery")
78
+
79
+
80
+ class GetSignalsRequest(BaseModel):
81
+ """Request parameters for discovering signals based on description"""
82
+
83
+ signal_spec: str = Field(description="Natural language description of the desired signals")
84
+ deliver_to: dict[str, Any] = Field(description="Where the signals need to be delivered")
85
+ filters: dict[str, Any] | None = Field(None, description="Filters to refine results")
86
+ max_results: int | None = Field(None, description="Maximum number of results to return")
87
+
88
+
89
+ class ListAuthorizedPropertiesRequest(BaseModel):
90
+ """Request parameters for discovering which publishers this agent is authorized to represent"""
91
+
92
+ publisher_domains: list[str] | None = Field(None, description="Filter to specific publisher domains (optional). If omitted, returns all publishers this agent represents.")
93
+
94
+
95
+ class ListCreativeFormatsRequest(BaseModel):
96
+ """Request parameters for discovering creative formats provided by this creative agent"""
97
+
98
+ format_ids: list[FormatId] | None = Field(None, description="Return only these specific format IDs")
99
+ type: Literal["audio", "video", "display", "dooh"] | None = Field(None, description="Filter by format type (technical categories with distinct requirements)")
100
+ asset_types: list[Literal["image", "video", "audio", "text", "html", "javascript", "url"]] | None = Field(None, description="Filter to formats that include these asset types. For third-party tags, search for 'html' or 'javascript'. E.g., ['image', 'text'] returns formats with images and text, ['javascript'] returns formats accepting JavaScript tags.")
101
+ max_width: int | None = Field(None, description="Maximum width in pixels (inclusive). Returns formats with width <= this value. Omit for responsive/fluid formats.")
102
+ max_height: int | None = Field(None, description="Maximum height in pixels (inclusive). Returns formats with height <= this value. Omit for responsive/fluid formats.")
103
+ min_width: int | None = Field(None, description="Minimum width in pixels (inclusive). Returns formats with width >= this value.")
104
+ min_height: int | None = Field(None, description="Minimum height in pixels (inclusive). Returns formats with height >= this value.")
105
+ is_responsive: bool | None = Field(None, description="Filter for responsive formats that adapt to container size. When true, returns formats without fixed dimensions.")
106
+ name_search: str | None = Field(None, description="Search for formats by name (case-insensitive partial match)")
107
+
108
+
109
+ class ListCreativesRequest(BaseModel):
110
+ """Request parameters for querying creative assets from the centralized library with filtering, sorting, and pagination"""
111
+
112
+ filters: dict[str, Any] | None = Field(None, description="Filter criteria for querying creatives")
113
+ sort: dict[str, Any] | None = Field(None, description="Sorting parameters")
114
+ pagination: dict[str, Any] | None = Field(None, description="Pagination parameters")
115
+ include_assignments: bool | None = Field(None, description="Include package assignment information in response")
116
+ include_performance: bool | None = Field(None, description="Include aggregated performance metrics in response")
117
+ include_sub_assets: bool | None = Field(None, description="Include sub-assets (for carousel/native formats) in response")
118
+ fields: list[Literal["creative_id", "name", "format", "status", "created_date", "updated_date", "tags", "assignments", "performance", "sub_assets"]] | None = Field(None, description="Specific fields to include in response (omit for all fields)")
119
+
120
+
121
+ class PreviewCreativeRequest(BaseModel):
122
+ """Request to generate a preview of a creative manifest in a specific format. The creative_manifest should include all assets required by the format (e.g., promoted_offerings for generative formats)."""
123
+
124
+ format_id: FormatId = Field(description="Format identifier for rendering the preview")
125
+ creative_manifest: CreativeManifest = Field(description="Complete creative manifest with all required assets (including promoted_offerings if required by the format)")
126
+ inputs: list[dict[str, Any]] | None = Field(None, description="Array of input sets for generating multiple preview variants. Each input set defines macros and context values for one preview rendering. If not provided, creative agent will generate default previews.")
127
+ template_id: str | None = Field(None, description="Specific template ID for custom format rendering")
128
+
129
+
130
+ class ProvidePerformanceFeedbackRequest(BaseModel):
131
+ """Request payload for provide_performance_feedback task"""
132
+
133
+ media_buy_id: str = Field(description="Publisher's media buy identifier")
134
+ measurement_period: dict[str, Any] = Field(description="Time period for performance measurement")
135
+ performance_index: float = Field(description="Normalized performance score (0.0 = no value, 1.0 = expected, >1.0 = above expected)")
136
+ package_id: str | None = Field(None, description="Specific package within the media buy (if feedback is package-specific)")
137
+ creative_id: str | None = Field(None, description="Specific creative asset (if feedback is creative-specific)")
138
+ metric_type: Literal["overall_performance", "conversion_rate", "brand_lift", "click_through_rate", "completion_rate", "viewability", "brand_safety", "cost_efficiency"] | None = Field(None, description="The business metric being measured")
139
+ feedback_source: Literal["buyer_attribution", "third_party_measurement", "platform_analytics", "verification_partner"] | None = Field(None, description="Source of the performance data")
140
+
141
+
142
+ class SyncCreativesRequest(BaseModel):
143
+ """Request parameters for syncing creative assets with upsert semantics - supports bulk operations, patch updates, and assignment management"""
144
+
145
+ creatives: list[CreativeAsset] = Field(description="Array of creative assets to sync (create or update)")
146
+ patch: bool | None = Field(None, description="When true, only provided fields are updated (partial update). When false, entire creative is replaced (full upsert).")
147
+ assignments: dict[str, Any] | None = Field(None, description="Optional bulk assignment of creatives to packages")
148
+ delete_missing: bool | None = Field(None, description="When true, creatives not included in this sync will be archived. Use with caution for full library replacement.")
149
+ dry_run: bool | None = Field(None, description="When true, preview changes without applying them. Returns what would be created/updated/deleted.")
150
+ validation_mode: Literal["strict", "lenient"] | None = Field(None, description="Validation strictness. 'strict' fails entire sync on any validation error. 'lenient' processes valid creatives and reports errors.")
151
+ push_notification_config: PushNotificationConfig | None = Field(None, description="Optional webhook configuration for async sync notifications. Publisher will send webhook when sync completes if operation takes longer than immediate response time (typically for large bulk operations or manual approval/HITL).")
152
+
153
+
154
+ class UpdateMediaBuyRequest(BaseModel):
155
+ """Request parameters for updating campaign and package settings"""
156
+
157
+ media_buy_id: str | None = Field(None, description="Publisher's ID of the media buy to update")
158
+ buyer_ref: str | None = Field(None, description="Buyer's reference for the media buy to update")
159
+ active: bool | None = Field(None, description="Pause/resume the entire media buy")
160
+ start_time: StartTiming | None = None
161
+ end_time: str | None = Field(None, description="New end date/time in ISO 8601 format")
162
+ packages: list[dict[str, Any]] | None = Field(None, description="Package-specific updates")
163
+ push_notification_config: PushNotificationConfig | None = Field(None, description="Optional webhook configuration for async update notifications. Publisher will send webhook when update completes if operation takes longer than immediate response time.")
164
+
165
+
166
+ class ActivateSignalResponse(BaseModel):
167
+ """Response payload for activate_signal task"""
168
+
169
+ decisioning_platform_segment_id: str | None = Field(None, description="The platform-specific ID to use once activated")
170
+ estimated_activation_duration_minutes: float | None = Field(None, description="Estimated time to complete (optional)")
171
+ deployed_at: str | None = Field(None, description="Timestamp when activation completed (optional)")
172
+ errors: list[Error] | None = Field(None, description="Task-specific errors and warnings (e.g., activation failures, platform issues)")
173
+
174
+
175
+ class BuildCreativeResponse(BaseModel):
176
+ """Response containing the transformed or generated creative manifest, ready for use with preview_creative or sync_creatives"""
177
+
178
+ creative_manifest: CreativeManifest = Field(description="The generated or transformed creative manifest")
179
+ errors: list[Error] | None = Field(None, description="Task-specific errors and warnings")
180
+
181
+
182
+ class CreateMediaBuyResponse(BaseModel):
183
+ """Response payload for create_media_buy task"""
184
+
185
+ media_buy_id: str | None = Field(None, description="Publisher's unique identifier for the created media buy")
186
+ buyer_ref: str = Field(description="Buyer's reference identifier for this media buy")
187
+ creative_deadline: str | None = Field(None, description="ISO 8601 timestamp for creative upload deadline")
188
+ packages: list[dict[str, Any]] | None = Field(None, description="Array of created packages")
189
+ errors: list[Error] | None = Field(None, description="Task-specific errors and warnings (e.g., partial package creation failures)")
190
+
191
+
192
+ class GetMediaBuyDeliveryResponse(BaseModel):
193
+ """Response payload for get_media_buy_delivery task"""
194
+
195
+ notification_type: Literal["scheduled", "final", "delayed", "adjusted"] | None = Field(None, description="Type of webhook notification (only present in webhook deliveries): scheduled = regular periodic update, final = campaign completed, delayed = data not yet available, adjusted = resending period with updated data")
196
+ partial_data: bool | None = Field(None, description="Indicates if any media buys in this webhook have missing/delayed data (only present in webhook deliveries)")
197
+ unavailable_count: int | None = Field(None, description="Number of media buys with reporting_delayed or failed status (only present in webhook deliveries when partial_data is true)")
198
+ sequence_number: int | None = Field(None, description="Sequential notification number (only present in webhook deliveries, starts at 1)")
199
+ next_expected_at: str | None = Field(None, description="ISO 8601 timestamp for next expected notification (only present in webhook deliveries when notification_type is not 'final')")
200
+ reporting_period: dict[str, Any] = Field(description="Date range for the report. All periods use UTC timezone.")
201
+ currency: str = Field(description="ISO 4217 currency code")
202
+ aggregated_totals: dict[str, Any] | None = Field(None, description="Combined metrics across all returned media buys. Only included in API responses (get_media_buy_delivery), not in webhook notifications.")
203
+ media_buy_deliveries: list[dict[str, Any]] = Field(description="Array of delivery data for media buys. When used in webhook notifications, may contain multiple media buys aggregated by publisher. When used in get_media_buy_delivery API responses, typically contains requested media buys.")
204
+ errors: list[Error] | None = Field(None, description="Task-specific errors and warnings (e.g., missing delivery data, reporting platform issues)")
205
+
206
+
207
+ class GetProductsResponse(BaseModel):
208
+ """Response payload for get_products task"""
209
+
210
+ products: list[Product] = Field(description="Array of matching products")
211
+ errors: list[Error] | None = Field(None, description="Task-specific errors and warnings (e.g., product filtering issues)")
212
+
213
+
214
+ class GetSignalsResponse(BaseModel):
215
+ """Response payload for get_signals task"""
216
+
217
+ signals: list[dict[str, Any]] = Field(description="Array of matching signals")
218
+ errors: list[Error] | None = Field(None, description="Task-specific errors and warnings (e.g., signal discovery or pricing issues)")
219
+
220
+
221
+ class ListAuthorizedPropertiesResponse(BaseModel):
222
+ """Response payload for list_authorized_properties task. Lists publisher domains and authorization scope (property_ids or property_tags). Buyers fetch actual property definitions from each publisher's canonical adagents.json file."""
223
+
224
+ publisher_domains: list[str] = Field(description="Publisher domains this agent is authorized to represent. Buyers should fetch each publisher's adagents.json to see property definitions and verify this agent is in their authorized_agents list with authorization scope.")
225
+ primary_channels: list[Channels] | None = Field(None, description="Primary advertising channels represented in this property portfolio. Helps buying agents quickly filter relevance.")
226
+ primary_countries: list[str] | None = Field(None, description="Primary countries (ISO 3166-1 alpha-2 codes) where properties are concentrated. Helps buying agents quickly filter relevance.")
227
+ portfolio_description: str | None = Field(None, description="Markdown-formatted description of the property portfolio, including inventory types, audience characteristics, and special features.")
228
+ advertising_policies: str | None = Field(None, description="Publisher's advertising content policies, restrictions, and guidelines in natural language. May include prohibited categories, blocked advertisers, restricted tactics, brand safety requirements, or links to full policy documentation.")
229
+ last_updated: str | None = Field(None, description="ISO 8601 timestamp of when the agent's publisher authorization list was last updated. Buyers can use this to determine if their cached publisher adagents.json files might be stale.")
230
+ errors: list[Error] | None = Field(None, description="Task-specific errors and warnings (e.g., property availability issues)")
231
+
232
+
233
+ class ListCreativeFormatsResponse(BaseModel):
234
+ """Response payload for list_creative_formats task from creative agent - returns full format definitions"""
235
+
236
+ formats: list[Format] = Field(description="Full format definitions for all formats this agent supports. Each format's authoritative source is indicated by its agent_url field.")
237
+ creative_agents: list[dict[str, Any]] | None = Field(None, description="Optional: Creative agents that provide additional formats. Buyers can recursively query these agents to discover more formats. No authentication required for list_creative_formats.")
238
+ errors: list[Error] | None = Field(None, description="Task-specific errors and warnings")
239
+
240
+
241
+ class ListCreativesResponse(BaseModel):
242
+ """Response from creative library query with filtered results, metadata, and optional enriched data"""
243
+
244
+ query_summary: dict[str, Any] = Field(description="Summary of the query that was executed")
245
+ pagination: dict[str, Any] = Field(description="Pagination information for navigating results")
246
+ creatives: list[dict[str, Any]] = Field(description="Array of creative assets matching the query")
247
+ format_summary: dict[str, Any] | None = Field(None, description="Breakdown of creatives by format type")
248
+ status_summary: dict[str, Any] | None = Field(None, description="Breakdown of creatives by status")
249
+
250
+
251
+ class PreviewCreativeResponse(BaseModel):
252
+ """Response containing preview links for a creative. Each preview URL returns an HTML page that can be embedded in an iframe to display the rendered creative."""
253
+
254
+ previews: list[dict[str, Any]] = Field(description="Array of preview variants. Each preview corresponds to an input set from the request. If no inputs were provided, returns a single default preview.")
255
+ interactive_url: str | None = Field(None, description="Optional URL to an interactive testing page that shows all preview variants with controls to switch between them, modify macro values, and test different scenarios.")
256
+ expires_at: str = Field(description="ISO 8601 timestamp when preview links expire")
257
+
258
+
259
+ class ProvidePerformanceFeedbackResponse(BaseModel):
260
+ """Response payload for provide_performance_feedback task"""
261
+
262
+ success: bool = Field(description="Whether the performance feedback was successfully received")
263
+ errors: list[Error] | None = Field(None, description="Task-specific errors and warnings (e.g., invalid measurement period, missing campaign data)")
264
+
265
+
266
+ class SyncCreativesResponse(BaseModel):
267
+ """Response from creative sync operation with results for each creative"""
268
+
269
+ dry_run: bool | None = Field(None, description="Whether this was a dry run (no actual changes made)")
270
+ creatives: list[dict[str, Any]] = Field(description="Results for each creative processed")
271
+
272
+
273
+ class UpdateMediaBuyResponse(BaseModel):
274
+ """Response payload for update_media_buy task"""
275
+
276
+ media_buy_id: str = Field(description="Publisher's identifier for the media buy")
277
+ buyer_ref: str = Field(description="Buyer's reference identifier for the media buy")
278
+ implementation_date: Any | None = Field(None, description="ISO 8601 timestamp when changes take effect (null if pending approval)")
279
+ affected_packages: list[dict[str, Any]] | None = Field(None, description="Array of packages that were modified")
280
+ errors: list[Error] | None = Field(None, description="Task-specific errors and warnings (e.g., partial update failures)")
281
+
adcp/utils/__init__.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """Utility functions."""
2
4
 
3
5
  from adcp.utils.operation_id import create_operation_id
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """Operation ID generation utilities."""
2
4
 
3
5
  from uuid import uuid4
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adcp
3
- Version: 0.1.2
3
+ Version: 1.0.2
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
@@ -62,8 +62,7 @@ pip install adcp
62
62
  ## Quick Start: Distributed Operations
63
63
 
64
64
  ```python
65
- from adcp import ADCPMultiAgentClient
66
- from adcp.types import AgentConfig
65
+ from adcp import ADCPMultiAgentClient, AgentConfig, GetProductsRequest
67
66
 
68
67
  # Configure agents and handlers
69
68
  client = ADCPMultiAgentClient(
@@ -96,7 +95,8 @@ client = ADCPMultiAgentClient(
96
95
 
97
96
  # Execute operation - library handles operation IDs, webhook URLs, context management
98
97
  agent = client.agent("agent_x")
99
- result = await agent.get_products(brief="Coffee brands")
98
+ request = GetProductsRequest(brief="Coffee brands")
99
+ result = await agent.get_products(request)
100
100
 
101
101
  # Check result
102
102
  if result.status == "completed":
@@ -116,23 +116,30 @@ if result.status == "submitted":
116
116
  - **Auto-detection**: Automatically detect which protocol an agent uses
117
117
 
118
118
  ### Type Safety
119
- Full type hints with Pydantic validation:
119
+ Full type hints with Pydantic validation and auto-generated types from the AdCP spec:
120
120
 
121
121
  ```python
122
- result = await agent.get_products(brief="Coffee brands")
122
+ from adcp import GetProductsRequest
123
+
124
+ # All methods require typed request objects
125
+ request = GetProductsRequest(brief="Coffee brands", max_results=10)
126
+ result = await agent.get_products(request)
123
127
  # result: TaskResult[GetProductsResponse]
124
128
 
125
129
  if result.success:
126
130
  for product in result.data.products:
127
- print(product.name, product.price) # Full IDE autocomplete!
131
+ print(product.name, product.pricing_options) # Full IDE autocomplete!
128
132
  ```
129
133
 
130
134
  ### Multi-Agent Operations
131
135
  Execute across multiple agents simultaneously:
132
136
 
133
137
  ```python
138
+ from adcp import GetProductsRequest
139
+
134
140
  # Parallel execution across all agents
135
- results = await client.get_products(brief="Coffee brands")
141
+ request = GetProductsRequest(brief="Coffee brands")
142
+ results = await client.get_products(request)
136
143
 
137
144
  for result in results:
138
145
  if result.status == "completed":
@@ -176,6 +183,69 @@ client = ADCPMultiAgentClient(
176
183
  # Signatures verified automatically on handle_webhook()
177
184
  ```
178
185
 
186
+ ### Debug Mode
187
+
188
+ Enable debug mode to see full request/response details:
189
+
190
+ ```python
191
+ agent_config = AgentConfig(
192
+ id="agent_x",
193
+ agent_uri="https://agent-x.com",
194
+ protocol="mcp",
195
+ debug=True # Enable debug mode
196
+ )
197
+
198
+ result = await client.agent("agent_x").get_products(brief="Coffee brands")
199
+
200
+ # Access debug information
201
+ if result.debug_info:
202
+ print(f"Duration: {result.debug_info.duration_ms}ms")
203
+ print(f"Request: {result.debug_info.request}")
204
+ print(f"Response: {result.debug_info.response}")
205
+ ```
206
+
207
+ Or use the CLI:
208
+
209
+ ```bash
210
+ uvx adcp --debug myagent get_products '{"brief":"TV ads"}'
211
+ ```
212
+
213
+ ### Error Handling
214
+
215
+ The library provides a comprehensive exception hierarchy with helpful error messages:
216
+
217
+ ```python
218
+ from adcp.exceptions import (
219
+ ADCPError, # Base exception
220
+ ADCPConnectionError, # Connection failed
221
+ ADCPAuthenticationError, # Auth failed (401, 403)
222
+ ADCPTimeoutError, # Request timed out
223
+ ADCPProtocolError, # Invalid response format
224
+ ADCPToolNotFoundError, # Tool not found
225
+ ADCPWebhookSignatureError # Invalid webhook signature
226
+ )
227
+
228
+ try:
229
+ result = await client.agent("agent_x").get_products(brief="Coffee")
230
+ except ADCPAuthenticationError as e:
231
+ # Exception includes agent context and helpful suggestions
232
+ print(f"Auth failed for {e.agent_id}: {e.message}")
233
+ print(f"Suggestion: {e.suggestion}")
234
+ except ADCPTimeoutError as e:
235
+ print(f"Request timed out after {e.timeout}s")
236
+ except ADCPConnectionError as e:
237
+ print(f"Connection failed: {e.message}")
238
+ print(f"Agent URI: {e.agent_uri}")
239
+ except ADCPError as e:
240
+ # Catch-all for other AdCP errors
241
+ print(f"AdCP error: {e.message}")
242
+ ```
243
+
244
+ All exceptions include:
245
+ - **Contextual information**: agent ID, URI, and operation details
246
+ - **Actionable suggestions**: specific steps to fix common issues
247
+ - **Error classification**: proper HTTP status code handling
248
+
179
249
  ## Available Tools
180
250
 
181
251
  All AdCP tools with full type safety:
@@ -221,6 +291,112 @@ auth = index.get_agent_authorizations("https://agent-x.com")
221
291
  premium = index.find_agents_by_property_tags(["premium", "ctv"])
222
292
  ```
223
293
 
294
+ ## CLI Tool
295
+
296
+ The `adcp` command-line tool provides easy interaction with AdCP agents without writing code.
297
+
298
+ ### Installation
299
+
300
+ ```bash
301
+ # Install globally
302
+ pip install adcp
303
+
304
+ # Or use uvx to run without installing
305
+ uvx adcp --help
306
+ ```
307
+
308
+ ### Quick Start
309
+
310
+ ```bash
311
+ # Save agent configuration
312
+ uvx adcp --save-auth myagent https://agent.example.com mcp
313
+
314
+ # List tools available on agent
315
+ uvx adcp myagent list_tools
316
+
317
+ # Execute a tool
318
+ uvx adcp myagent get_products '{"brief":"TV ads"}'
319
+
320
+ # Use from stdin
321
+ echo '{"brief":"TV ads"}' | uvx adcp myagent get_products
322
+
323
+ # Use from file
324
+ uvx adcp myagent get_products @request.json
325
+
326
+ # Get JSON output
327
+ uvx adcp --json myagent get_products '{"brief":"TV ads"}'
328
+
329
+ # Enable debug mode
330
+ uvx adcp --debug myagent get_products '{"brief":"TV ads"}'
331
+ ```
332
+
333
+ ### Configuration Management
334
+
335
+ ```bash
336
+ # Save agent with authentication
337
+ uvx adcp --save-auth myagent https://agent.example.com mcp
338
+ # Prompts for optional auth token
339
+
340
+ # List saved agents
341
+ uvx adcp --list-agents
342
+
343
+ # Remove saved agent
344
+ uvx adcp --remove-agent myagent
345
+
346
+ # Show config file location
347
+ uvx adcp --show-config
348
+ ```
349
+
350
+ ### Direct URL Access
351
+
352
+ ```bash
353
+ # Use URL directly without saving
354
+ uvx adcp https://agent.example.com/mcp list_tools
355
+
356
+ # Override protocol
357
+ uvx adcp --protocol a2a https://agent.example.com list_tools
358
+
359
+ # Pass auth token
360
+ uvx adcp --auth YOUR_TOKEN https://agent.example.com list_tools
361
+ ```
362
+
363
+ ### Examples
364
+
365
+ ```bash
366
+ # Get products from saved agent
367
+ uvx adcp myagent get_products '{"brief":"Coffee brands for digital video"}'
368
+
369
+ # Create media buy
370
+ uvx adcp myagent create_media_buy '{
371
+ "name": "Q4 Campaign",
372
+ "budget": 50000,
373
+ "start_date": "2024-01-01",
374
+ "end_date": "2024-03-31"
375
+ }'
376
+
377
+ # List creative formats with JSON output
378
+ uvx adcp --json myagent list_creative_formats | jq '.data'
379
+
380
+ # Debug connection issues
381
+ uvx adcp --debug myagent list_tools
382
+ ```
383
+
384
+ ### Configuration File
385
+
386
+ Agent configurations are stored in `~/.adcp/config.json`:
387
+
388
+ ```json
389
+ {
390
+ "agents": {
391
+ "myagent": {
392
+ "agent_uri": "https://agent.example.com",
393
+ "protocol": "mcp",
394
+ "auth_token": "optional-token"
395
+ }
396
+ }
397
+ }
398
+ ```
399
+
224
400
  ## Environment Configuration
225
401
 
226
402
  ```bash
@@ -0,0 +1,21 @@
1
+ adcp/__init__.py,sha256=P-Lws8JQAoeN82lIPhabzOPf5CSgTRDWNSQswb9aBK4,2512
2
+ adcp/__main__.py,sha256=MR7hiJu0cM7wkBAMpJ9wq9IXWFjWeD5gEOvJOJawnW8,8900
3
+ adcp/client.py,sha256=E7SEqZarXuT76nURHWG_O9thNauNW1yEesFlF2lVXjg,20900
4
+ adcp/config.py,sha256=Vsy7ZPOI8G3fB_i5Nk-CHbC7wdasCUWuKlos0fwA0kY,2017
5
+ adcp/exceptions.py,sha256=dNRMKV23DlkGKyB9Xmt6MtlhvDu1crjzD_en4nAEwDY,4399
6
+ adcp/protocols/__init__.py,sha256=6UFwACQ0QadBUzy17wUROHqsJDp8ztPW2jzyl53Zh_g,262
7
+ adcp/protocols/a2a.py,sha256=c5PZ1SlZe2TPzx-eFu-RH9P-h98CW4F5PBdgsU5k8HE,10281
8
+ adcp/protocols/base.py,sha256=fG4tk4UMVSkBE3zv5b7bPEOSEXOCTI2oFyy9V6H406Y,1183
9
+ adcp/protocols/mcp.py,sha256=Oo0i7jyTnmQPoLeKuP6YBCmCzi1WazUDf3SzxMAU6lA,11163
10
+ adcp/types/__init__.py,sha256=3E_TJUXqQQFcjmSZZSPLwqBP3s_ijsH2LDeuOU-MP30,402
11
+ adcp/types/core.py,sha256=w6CLD2K0riAHUnrktYmQV7qnkO-6Ab4-CN67YSagAE4,4731
12
+ adcp/types/generated.py,sha256=5vbr113-rhNJSMePAEvQSPtqqEqiloVw1F8BeT4tk3o,49973
13
+ adcp/types/tasks.py,sha256=iwNoV-1_BxH3PZxmfg2rgZHDUN-o_Ob7zgPNbKtyNPM,21553
14
+ adcp/utils/__init__.py,sha256=uetvSJB19CjQbtwEYZiTnumJG11GsafQmXm5eR3hL7E,153
15
+ adcp/utils/operation_id.py,sha256=wQX9Bb5epXzRq23xoeYPTqzu5yLuhshg7lKJZihcM2k,294
16
+ adcp-1.0.2.dist-info/licenses/LICENSE,sha256=PF39NR3Ae8PLgBhg3Uxw6ju7iGVIf8hfv9LRWQdii_U,629
17
+ adcp-1.0.2.dist-info/METADATA,sha256=q2TkYphUBt9eTOWTZ2e774Xj1cuzxjeOgEvvTAPtOZ8,12724
18
+ adcp-1.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ adcp-1.0.2.dist-info/entry_points.txt,sha256=DQKpcGsJX8DtVI_SGApQ7tNvqUB4zkTLaTAEpFgmi3U,44
20
+ adcp-1.0.2.dist-info/top_level.txt,sha256=T1_NF0GefncFU9v_k56oDwKSJREyCqIM8lAwNZf0EOs,5
21
+ adcp-1.0.2.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ adcp = adcp.__main__:main
@@ -1,15 +0,0 @@
1
- adcp/__init__.py,sha256=TpXs5-627LCcgm351m2Q1oD5WsFawGoLfe3Fj6Wi4-o,424
2
- adcp/client.py,sha256=DaAA_5Jxw2N0xjRSdvplJdUU_QHHMJqNzow9QzeagJw,16095
3
- adcp/protocols/__init__.py,sha256=hAGf5yAfgqpqa_-pMWXL35Bl-Aeca8Zdt_E1uEeI0_M,226
4
- adcp/protocols/a2a.py,sha256=P9tnUYiVSRa8_OJPsZmr2_r1IasRIGS7z6LR8uTj0lg,5719
5
- adcp/protocols/base.py,sha256=RhaeFyOj4lxCxGJajUo1ydpNDW2OZPnnMJ6QmodOESw,915
6
- adcp/protocols/mcp.py,sha256=dLSmDs-qJrn_dcXUthABRblb2ripjNVYWi0wZ0VOOe8,3417
7
- adcp/types/__init__.py,sha256=LuiNdxbVfS91LIt2DTW7xX0BR-chmqwXz4ScYS6Hd44,334
8
- adcp/types/core.py,sha256=xybdwh1bsI_q1QiS8DB_6DiFdUNzWsycfVbi7sO2nfo,2119
9
- adcp/utils/__init__.py,sha256=ME5OVQipWgFdECX0Lur8-ThcVrqHfQOtx27TwjDe9LE,117
10
- adcp/utils/operation_id.py,sha256=_TcXbaSuaFhnN5F1zECspSJ5n08x_-LIzIWbdlkwzEQ,258
11
- adcp-0.1.2.dist-info/licenses/LICENSE,sha256=PF39NR3Ae8PLgBhg3Uxw6ju7iGVIf8hfv9LRWQdii_U,629
12
- adcp-0.1.2.dist-info/METADATA,sha256=pA0Ur9lBAYwdy_u-KsWhwPSYhWNHY_jzoWrPFsl8pGc,8429
13
- adcp-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- adcp-0.1.2.dist-info/top_level.txt,sha256=T1_NF0GefncFU9v_k56oDwKSJREyCqIM8lAwNZf0EOs,5
15
- adcp-0.1.2.dist-info/RECORD,,
File without changes