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.
Files changed (176) hide show
  1. adcp/__init__.py +364 -0
  2. adcp/__main__.py +440 -0
  3. adcp/adagents.py +642 -0
  4. adcp/client.py +1057 -0
  5. adcp/config.py +82 -0
  6. adcp/exceptions.py +185 -0
  7. adcp/protocols/__init__.py +9 -0
  8. adcp/protocols/a2a.py +484 -0
  9. adcp/protocols/base.py +190 -0
  10. adcp/protocols/mcp.py +440 -0
  11. adcp/py.typed +0 -0
  12. adcp/simple.py +451 -0
  13. adcp/testing/__init__.py +53 -0
  14. adcp/testing/test_helpers.py +311 -0
  15. adcp/types/__init__.py +561 -0
  16. adcp/types/_generated.py +237 -0
  17. adcp/types/aliases.py +748 -0
  18. adcp/types/base.py +26 -0
  19. adcp/types/core.py +174 -0
  20. adcp/types/generated_poc/__init__.py +3 -0
  21. adcp/types/generated_poc/adagents.py +411 -0
  22. adcp/types/generated_poc/core/__init__.py +3 -0
  23. adcp/types/generated_poc/core/activation_key.py +30 -0
  24. adcp/types/generated_poc/core/assets/__init__.py +3 -0
  25. adcp/types/generated_poc/core/assets/audio_asset.py +26 -0
  26. adcp/types/generated_poc/core/assets/css_asset.py +20 -0
  27. adcp/types/generated_poc/core/assets/daast_asset.py +61 -0
  28. adcp/types/generated_poc/core/assets/html_asset.py +18 -0
  29. adcp/types/generated_poc/core/assets/image_asset.py +19 -0
  30. adcp/types/generated_poc/core/assets/javascript_asset.py +23 -0
  31. adcp/types/generated_poc/core/assets/text_asset.py +20 -0
  32. adcp/types/generated_poc/core/assets/url_asset.py +28 -0
  33. adcp/types/generated_poc/core/assets/vast_asset.py +63 -0
  34. adcp/types/generated_poc/core/assets/video_asset.py +24 -0
  35. adcp/types/generated_poc/core/assets/webhook_asset.py +53 -0
  36. adcp/types/generated_poc/core/brand_manifest.py +201 -0
  37. adcp/types/generated_poc/core/context.py +15 -0
  38. adcp/types/generated_poc/core/creative_asset.py +102 -0
  39. adcp/types/generated_poc/core/creative_assignment.py +27 -0
  40. adcp/types/generated_poc/core/creative_filters.py +86 -0
  41. adcp/types/generated_poc/core/creative_manifest.py +68 -0
  42. adcp/types/generated_poc/core/creative_policy.py +28 -0
  43. adcp/types/generated_poc/core/delivery_metrics.py +111 -0
  44. adcp/types/generated_poc/core/deployment.py +78 -0
  45. adcp/types/generated_poc/core/destination.py +43 -0
  46. adcp/types/generated_poc/core/dimensions.py +18 -0
  47. adcp/types/generated_poc/core/error.py +29 -0
  48. adcp/types/generated_poc/core/ext.py +15 -0
  49. adcp/types/generated_poc/core/format.py +260 -0
  50. adcp/types/generated_poc/core/format_id.py +50 -0
  51. adcp/types/generated_poc/core/frequency_cap.py +19 -0
  52. adcp/types/generated_poc/core/measurement.py +40 -0
  53. adcp/types/generated_poc/core/media_buy.py +40 -0
  54. adcp/types/generated_poc/core/package.py +68 -0
  55. adcp/types/generated_poc/core/performance_feedback.py +78 -0
  56. adcp/types/generated_poc/core/placement.py +37 -0
  57. adcp/types/generated_poc/core/product.py +164 -0
  58. adcp/types/generated_poc/core/product_filters.py +97 -0
  59. adcp/types/generated_poc/core/promoted_offerings.py +102 -0
  60. adcp/types/generated_poc/core/promoted_products.py +38 -0
  61. adcp/types/generated_poc/core/property.py +64 -0
  62. adcp/types/generated_poc/core/property_id.py +21 -0
  63. adcp/types/generated_poc/core/property_tag.py +21 -0
  64. adcp/types/generated_poc/core/protocol_envelope.py +61 -0
  65. adcp/types/generated_poc/core/publisher_property_selector.py +75 -0
  66. adcp/types/generated_poc/core/push_notification_config.py +51 -0
  67. adcp/types/generated_poc/core/reporting_capabilities.py +51 -0
  68. adcp/types/generated_poc/core/response.py +24 -0
  69. adcp/types/generated_poc/core/signal_filters.py +29 -0
  70. adcp/types/generated_poc/core/sub_asset.py +55 -0
  71. adcp/types/generated_poc/core/targeting.py +53 -0
  72. adcp/types/generated_poc/core/webhook_payload.py +96 -0
  73. adcp/types/generated_poc/creative/__init__.py +3 -0
  74. adcp/types/generated_poc/creative/list_creative_formats_request.py +88 -0
  75. adcp/types/generated_poc/creative/list_creative_formats_response.py +55 -0
  76. adcp/types/generated_poc/creative/preview_creative_request.py +153 -0
  77. adcp/types/generated_poc/creative/preview_creative_response.py +169 -0
  78. adcp/types/generated_poc/creative/preview_render.py +152 -0
  79. adcp/types/generated_poc/enums/__init__.py +3 -0
  80. adcp/types/generated_poc/enums/adcp_domain.py +12 -0
  81. adcp/types/generated_poc/enums/asset_content_type.py +23 -0
  82. adcp/types/generated_poc/enums/auth_scheme.py +12 -0
  83. adcp/types/generated_poc/enums/available_metric.py +19 -0
  84. adcp/types/generated_poc/enums/channels.py +19 -0
  85. adcp/types/generated_poc/enums/co_branding_requirement.py +13 -0
  86. adcp/types/generated_poc/enums/creative_action.py +15 -0
  87. adcp/types/generated_poc/enums/creative_agent_capability.py +14 -0
  88. adcp/types/generated_poc/enums/creative_sort_field.py +16 -0
  89. adcp/types/generated_poc/enums/creative_status.py +14 -0
  90. adcp/types/generated_poc/enums/daast_tracking_event.py +21 -0
  91. adcp/types/generated_poc/enums/daast_version.py +12 -0
  92. adcp/types/generated_poc/enums/delivery_type.py +12 -0
  93. adcp/types/generated_poc/enums/dimension_unit.py +14 -0
  94. adcp/types/generated_poc/enums/feed_format.py +13 -0
  95. adcp/types/generated_poc/enums/feedback_source.py +14 -0
  96. adcp/types/generated_poc/enums/format_category.py +17 -0
  97. adcp/types/generated_poc/enums/format_id_parameter.py +12 -0
  98. adcp/types/generated_poc/enums/frequency_cap_scope.py +16 -0
  99. adcp/types/generated_poc/enums/history_entry_type.py +12 -0
  100. adcp/types/generated_poc/enums/http_method.py +12 -0
  101. adcp/types/generated_poc/enums/identifier_types.py +29 -0
  102. adcp/types/generated_poc/enums/javascript_module_type.py +13 -0
  103. adcp/types/generated_poc/enums/landing_page_requirement.py +13 -0
  104. adcp/types/generated_poc/enums/markdown_flavor.py +12 -0
  105. adcp/types/generated_poc/enums/media_buy_status.py +14 -0
  106. adcp/types/generated_poc/enums/metric_type.py +18 -0
  107. adcp/types/generated_poc/enums/notification_type.py +14 -0
  108. adcp/types/generated_poc/enums/pacing.py +13 -0
  109. adcp/types/generated_poc/enums/preview_output_format.py +12 -0
  110. adcp/types/generated_poc/enums/pricing_model.py +17 -0
  111. adcp/types/generated_poc/enums/property_type.py +17 -0
  112. adcp/types/generated_poc/enums/publisher_identifier_types.py +15 -0
  113. adcp/types/generated_poc/enums/reporting_frequency.py +13 -0
  114. adcp/types/generated_poc/enums/signal_catalog_type.py +13 -0
  115. adcp/types/generated_poc/enums/sort_direction.py +12 -0
  116. adcp/types/generated_poc/enums/standard_format_ids.py +45 -0
  117. adcp/types/generated_poc/enums/task_status.py +19 -0
  118. adcp/types/generated_poc/enums/task_type.py +15 -0
  119. adcp/types/generated_poc/enums/update_frequency.py +14 -0
  120. adcp/types/generated_poc/enums/url_asset_type.py +13 -0
  121. adcp/types/generated_poc/enums/validation_mode.py +12 -0
  122. adcp/types/generated_poc/enums/vast_tracking_event.py +26 -0
  123. adcp/types/generated_poc/enums/vast_version.py +15 -0
  124. adcp/types/generated_poc/enums/webhook_response_type.py +14 -0
  125. adcp/types/generated_poc/enums/webhook_security_method.py +13 -0
  126. adcp/types/generated_poc/media_buy/__init__.py +3 -0
  127. adcp/types/generated_poc/media_buy/build_creative_request.py +41 -0
  128. adcp/types/generated_poc/media_buy/build_creative_response.py +51 -0
  129. adcp/types/generated_poc/media_buy/create_media_buy_request.py +94 -0
  130. adcp/types/generated_poc/media_buy/create_media_buy_response.py +56 -0
  131. adcp/types/generated_poc/media_buy/get_media_buy_delivery_request.py +47 -0
  132. adcp/types/generated_poc/media_buy/get_media_buy_delivery_response.py +235 -0
  133. adcp/types/generated_poc/media_buy/get_products_request.py +48 -0
  134. adcp/types/generated_poc/media_buy/get_products_response.py +28 -0
  135. adcp/types/generated_poc/media_buy/list_authorized_properties_request.py +38 -0
  136. adcp/types/generated_poc/media_buy/list_authorized_properties_response.py +84 -0
  137. adcp/types/generated_poc/media_buy/list_creative_formats_request.py +74 -0
  138. adcp/types/generated_poc/media_buy/list_creative_formats_response.py +56 -0
  139. adcp/types/generated_poc/media_buy/list_creatives_request.py +76 -0
  140. adcp/types/generated_poc/media_buy/list_creatives_response.py +214 -0
  141. adcp/types/generated_poc/media_buy/package_request.py +63 -0
  142. adcp/types/generated_poc/media_buy/provide_performance_feedback_request.py +125 -0
  143. adcp/types/generated_poc/media_buy/provide_performance_feedback_response.py +53 -0
  144. adcp/types/generated_poc/media_buy/sync_creatives_request.py +63 -0
  145. adcp/types/generated_poc/media_buy/sync_creatives_response.py +105 -0
  146. adcp/types/generated_poc/media_buy/update_media_buy_request.py +195 -0
  147. adcp/types/generated_poc/media_buy/update_media_buy_response.py +55 -0
  148. adcp/types/generated_poc/pricing_options/__init__.py +3 -0
  149. adcp/types/generated_poc/pricing_options/cpc_option.py +43 -0
  150. adcp/types/generated_poc/pricing_options/cpcv_option.py +45 -0
  151. adcp/types/generated_poc/pricing_options/cpm_auction_option.py +58 -0
  152. adcp/types/generated_poc/pricing_options/cpm_fixed_option.py +43 -0
  153. adcp/types/generated_poc/pricing_options/cpp_option.py +64 -0
  154. adcp/types/generated_poc/pricing_options/cpv_option.py +77 -0
  155. adcp/types/generated_poc/pricing_options/flat_rate_option.py +93 -0
  156. adcp/types/generated_poc/pricing_options/vcpm_auction_option.py +61 -0
  157. adcp/types/generated_poc/pricing_options/vcpm_fixed_option.py +47 -0
  158. adcp/types/generated_poc/protocols/__init__.py +3 -0
  159. adcp/types/generated_poc/protocols/adcp_extension.py +37 -0
  160. adcp/types/generated_poc/signals/__init__.py +3 -0
  161. adcp/types/generated_poc/signals/activate_signal_request.py +32 -0
  162. adcp/types/generated_poc/signals/activate_signal_response.py +51 -0
  163. adcp/types/generated_poc/signals/get_signals_request.py +53 -0
  164. adcp/types/generated_poc/signals/get_signals_response.py +59 -0
  165. adcp/utils/__init__.py +7 -0
  166. adcp/utils/operation_id.py +15 -0
  167. adcp/utils/preview_cache.py +491 -0
  168. adcp/utils/response_parser.py +171 -0
  169. adcp/validation.py +172 -0
  170. adcp-2.12.0.data/data/ADCP_VERSION +1 -0
  171. adcp-2.12.0.dist-info/METADATA +992 -0
  172. adcp-2.12.0.dist-info/RECORD +176 -0
  173. adcp-2.12.0.dist-info/WHEEL +5 -0
  174. adcp-2.12.0.dist-info/entry_points.txt +2 -0
  175. adcp-2.12.0.dist-info/licenses/LICENSE +17 -0
  176. adcp-2.12.0.dist-info/top_level.txt +1 -0
adcp/__main__.py ADDED
@@ -0,0 +1,440 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ """Command-line interface for AdCP client - compatible with npx @adcp/client."""
5
+
6
+ import argparse
7
+ import asyncio
8
+ import json
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Any, cast
12
+
13
+ from adcp.client import ADCPClient
14
+ from adcp.config import (
15
+ CONFIG_FILE,
16
+ get_agent,
17
+ list_agents,
18
+ remove_agent,
19
+ save_agent,
20
+ )
21
+ from adcp.types.core import AgentConfig, Protocol
22
+
23
+
24
+ def print_json(data: Any) -> None:
25
+ """Print data as JSON."""
26
+ from pydantic import BaseModel
27
+
28
+ # Handle Pydantic models
29
+ if isinstance(data, BaseModel):
30
+ print(data.model_dump_json(indent=2, exclude_none=True))
31
+ else:
32
+ print(json.dumps(data, indent=2, default=str))
33
+
34
+
35
+ def print_result(result: Any, json_output: bool = False) -> None:
36
+ """Print result in formatted or JSON mode."""
37
+ if json_output:
38
+ # Match JavaScript client: output just the data for scripting
39
+ if result.success and result.data:
40
+ print_json(result.data)
41
+ else:
42
+ # On error, output error info
43
+ print_json({"error": result.error, "success": False})
44
+ else:
45
+ # Pretty output with message and data (like JavaScript client)
46
+ if result.success:
47
+ print("\nSUCCESS\n")
48
+ # Show protocol message if available
49
+ if hasattr(result, "message") and result.message:
50
+ print("Protocol Message:")
51
+ print(result.message)
52
+ print()
53
+ if result.data:
54
+ print("Response:")
55
+ print_json(result.data)
56
+ else:
57
+ print("\nFAILED\n")
58
+ print(f"Error: {result.error}")
59
+
60
+
61
+ async def execute_tool(
62
+ agent_config: dict[str, Any], tool_name: str, payload: dict[str, Any], json_output: bool = False
63
+ ) -> None:
64
+ """Execute a tool on an agent."""
65
+ # Ensure required fields
66
+ if "id" not in agent_config:
67
+ agent_config["id"] = agent_config.get("agent_uri", "unknown")
68
+
69
+ if "protocol" not in agent_config:
70
+ agent_config["protocol"] = "mcp"
71
+
72
+ # Convert string protocol to enum
73
+ if isinstance(agent_config["protocol"], str):
74
+ agent_config["protocol"] = Protocol(agent_config["protocol"].lower())
75
+
76
+ config = AgentConfig(**agent_config)
77
+
78
+ async with ADCPClient(config) as client:
79
+ # Dispatch to specific method based on tool name
80
+ result = await _dispatch_tool(client, tool_name, payload)
81
+ print_result(result, json_output)
82
+
83
+
84
+ # Tool dispatch mapping - single source of truth for ADCP methods
85
+ # Types are filled at runtime to avoid circular imports
86
+ # Special case: list_tools and get_info take no parameters (None means no request type)
87
+ TOOL_DISPATCH: dict[str, tuple[str, type | None]] = {
88
+ "list_tools": ("list_tools", None), # Protocol introspection - no request type
89
+ "get_info": ("get_info", None), # Agent info - no request type
90
+ "get_products": ("get_products", None),
91
+ "list_creative_formats": ("list_creative_formats", None),
92
+ "preview_creative": ("preview_creative", None),
93
+ "build_creative": ("build_creative", None),
94
+ "sync_creatives": ("sync_creatives", None),
95
+ "list_creatives": ("list_creatives", None),
96
+ "create_media_buy": ("create_media_buy", None),
97
+ "update_media_buy": ("update_media_buy", None),
98
+ "get_media_buy_delivery": ("get_media_buy_delivery", None),
99
+ "list_authorized_properties": ("list_authorized_properties", None),
100
+ "get_signals": ("get_signals", None),
101
+ "activate_signal": ("activate_signal", None),
102
+ "provide_performance_feedback": ("provide_performance_feedback", None),
103
+ }
104
+
105
+
106
+ async def _dispatch_tool(client: ADCPClient, tool_name: str, payload: dict[str, Any]) -> Any:
107
+ """Dispatch tool call to appropriate client method.
108
+
109
+ Args:
110
+ client: ADCP client instance
111
+ tool_name: Name of the tool to invoke
112
+ payload: Request payload as dict
113
+
114
+ Returns:
115
+ TaskResult with typed response or error
116
+
117
+ Raises:
118
+ ValidationError: If payload doesn't match request schema (caught and returned as TaskResult)
119
+ """
120
+ from pydantic import ValidationError
121
+
122
+ from adcp.types import _generated as gen
123
+ from adcp.types.core import TaskResult, TaskStatus
124
+
125
+ # Lazy initialization of request types (avoid circular imports)
126
+ if TOOL_DISPATCH["get_products"][1] is None:
127
+ TOOL_DISPATCH["get_products"] = ("get_products", gen.GetProductsRequest)
128
+ TOOL_DISPATCH["list_creative_formats"] = (
129
+ "list_creative_formats",
130
+ gen.ListCreativeFormatsRequest,
131
+ )
132
+ TOOL_DISPATCH["preview_creative"] = ("preview_creative", gen.PreviewCreativeRequest)
133
+ TOOL_DISPATCH["build_creative"] = ("build_creative", gen.BuildCreativeRequest)
134
+ TOOL_DISPATCH["sync_creatives"] = ("sync_creatives", gen.SyncCreativesRequest)
135
+ TOOL_DISPATCH["list_creatives"] = ("list_creatives", gen.ListCreativesRequest)
136
+ TOOL_DISPATCH["create_media_buy"] = ("create_media_buy", gen.CreateMediaBuyRequest)
137
+ TOOL_DISPATCH["update_media_buy"] = ("update_media_buy", gen.UpdateMediaBuyRequest)
138
+ TOOL_DISPATCH["get_media_buy_delivery"] = (
139
+ "get_media_buy_delivery",
140
+ gen.GetMediaBuyDeliveryRequest,
141
+ )
142
+ TOOL_DISPATCH["list_authorized_properties"] = (
143
+ "list_authorized_properties",
144
+ gen.ListAuthorizedPropertiesRequest,
145
+ )
146
+ TOOL_DISPATCH["get_signals"] = ("get_signals", gen.GetSignalsRequest)
147
+ TOOL_DISPATCH["activate_signal"] = ("activate_signal", gen.ActivateSignalRequest)
148
+ TOOL_DISPATCH["provide_performance_feedback"] = (
149
+ "provide_performance_feedback",
150
+ gen.ProvidePerformanceFeedbackRequest,
151
+ )
152
+
153
+ # Check if tool exists
154
+ if tool_name not in TOOL_DISPATCH:
155
+ available = ", ".join(sorted(TOOL_DISPATCH.keys()))
156
+ return TaskResult(
157
+ status=TaskStatus.FAILED,
158
+ success=False,
159
+ error=f"Unknown tool: {tool_name}. Available tools: {available}",
160
+ )
161
+
162
+ # Get method and request type
163
+ method_name, request_type = TOOL_DISPATCH[tool_name]
164
+ method = getattr(client, method_name)
165
+
166
+ # Special case: list_tools and get_info take no parameters and return
167
+ # data directly, not TaskResult
168
+ if tool_name == "list_tools":
169
+ try:
170
+ tools = await method()
171
+ return TaskResult(
172
+ status=TaskStatus.COMPLETED,
173
+ data={"tools": tools},
174
+ success=True,
175
+ )
176
+ except Exception as e:
177
+ return TaskResult(
178
+ status=TaskStatus.FAILED,
179
+ success=False,
180
+ error=f"Failed to list tools: {e}",
181
+ )
182
+
183
+ if tool_name == "get_info":
184
+ try:
185
+ info = await method()
186
+ return TaskResult(
187
+ status=TaskStatus.COMPLETED,
188
+ data=info,
189
+ success=True,
190
+ )
191
+ except Exception as e:
192
+ return TaskResult(
193
+ status=TaskStatus.FAILED,
194
+ success=False,
195
+ error=f"Failed to get agent info: {e}",
196
+ )
197
+
198
+ # Type guard - request_type should be initialized by this point for methods that need it
199
+ if request_type is None:
200
+ return TaskResult(
201
+ status=TaskStatus.FAILED,
202
+ success=False,
203
+ error=f"Internal error: {tool_name} request type not initialized",
204
+ )
205
+
206
+ # Validate and invoke
207
+ try:
208
+ request = request_type(**payload)
209
+ return await method(request)
210
+ except ValidationError as e:
211
+ # User-friendly error for invalid payloads
212
+ error_details = []
213
+ for error in e.errors():
214
+ field = ".".join(str(loc) for loc in error["loc"])
215
+ msg = error["msg"]
216
+ error_details.append(f" - {field}: {msg}")
217
+
218
+ return TaskResult(
219
+ status=TaskStatus.FAILED,
220
+ success=False,
221
+ error=f"Invalid request payload for {tool_name}:\n" + "\n".join(error_details),
222
+ )
223
+
224
+
225
+ def load_payload(payload_arg: str | None) -> dict[str, Any]:
226
+ """Load payload from argument (JSON, @file, or stdin)."""
227
+ if not payload_arg:
228
+ # Try to read from stdin if available and has data
229
+ if not sys.stdin.isatty():
230
+ try:
231
+ return cast(dict[str, Any], json.load(sys.stdin))
232
+ except (json.JSONDecodeError, ValueError):
233
+ pass
234
+ return {}
235
+
236
+ if payload_arg.startswith("@"):
237
+ # Load from file
238
+ file_path = Path(payload_arg[1:])
239
+ if not file_path.exists():
240
+ print(f"Error: File not found: {file_path}", file=sys.stderr)
241
+ sys.exit(1)
242
+ return cast(dict[str, Any], json.loads(file_path.read_text()))
243
+
244
+ # Parse as JSON
245
+ try:
246
+ return cast(dict[str, Any], json.loads(payload_arg))
247
+ except json.JSONDecodeError as e:
248
+ print(f"Error: Invalid JSON payload: {e}", file=sys.stderr)
249
+ sys.exit(1)
250
+
251
+
252
+ def handle_save_auth(alias: str, url: str | None, protocol: str | None) -> None:
253
+ """Handle --save-auth command."""
254
+ if not url:
255
+ # Interactive mode
256
+ url = input(f"Agent URL for '{alias}': ").strip()
257
+ if not url:
258
+ print("Error: URL is required", file=sys.stderr)
259
+ sys.exit(1)
260
+
261
+ if not protocol:
262
+ protocol = input("Protocol (mcp/a2a) [mcp]: ").strip() or "mcp"
263
+
264
+ auth_token = input("Auth token (optional): ").strip() or None
265
+
266
+ save_agent(alias, url, protocol, auth_token)
267
+ print(f"✓ Saved agent '{alias}'")
268
+
269
+
270
+ def handle_list_agents() -> None:
271
+ """Handle --list-agents command."""
272
+ agents = list_agents()
273
+
274
+ if not agents:
275
+ print("No saved agents")
276
+ return
277
+
278
+ print("\nSaved agents:")
279
+ for alias, config in agents.items():
280
+ auth = "yes" if config.get("auth_token") else "no"
281
+ print(f" {alias}")
282
+ print(f" URL: {config.get('agent_uri')}")
283
+ print(f" Protocol: {config.get('protocol', 'mcp').upper()}")
284
+ print(f" Auth: {auth}")
285
+
286
+
287
+ def handle_remove_agent(alias: str) -> None:
288
+ """Handle --remove-agent command."""
289
+ if remove_agent(alias):
290
+ print(f"✓ Removed agent '{alias}'")
291
+ else:
292
+ print(f"Error: Agent '{alias}' not found", file=sys.stderr)
293
+ sys.exit(1)
294
+
295
+
296
+ def handle_show_config() -> None:
297
+ """Handle --show-config command."""
298
+ print(f"Config file: {CONFIG_FILE}")
299
+
300
+
301
+ def resolve_agent_config(agent_identifier: str) -> dict[str, Any]:
302
+ """Resolve agent identifier to configuration."""
303
+ # Check if it's a saved alias
304
+ saved = get_agent(agent_identifier)
305
+ if saved:
306
+ return saved
307
+
308
+ # Check if it's a URL
309
+ if agent_identifier.startswith(("http://", "https://")):
310
+ return {
311
+ "id": agent_identifier.split("/")[-1],
312
+ "agent_uri": agent_identifier,
313
+ "protocol": "mcp",
314
+ }
315
+
316
+ # Check if it's a JSON config
317
+ if agent_identifier.startswith("{"):
318
+ try:
319
+ return cast(dict[str, Any], json.loads(agent_identifier))
320
+ except json.JSONDecodeError:
321
+ pass
322
+
323
+ print(f"Error: Unknown agent '{agent_identifier}'", file=sys.stderr)
324
+ print(" Not found as saved alias", file=sys.stderr)
325
+ print(" Not a valid URL", file=sys.stderr)
326
+ print(" Not valid JSON config", file=sys.stderr)
327
+ sys.exit(1)
328
+
329
+
330
+ def main() -> None:
331
+ """Main CLI entry point - compatible with JavaScript version."""
332
+ parser = argparse.ArgumentParser(
333
+ description="AdCP Client - Interact with AdCP agents",
334
+ usage="adcp [options] <agent> [tool] [payload]",
335
+ add_help=False,
336
+ )
337
+
338
+ # Configuration management
339
+ parser.add_argument("--save-auth", metavar="ALIAS", help="Save agent configuration")
340
+ parser.add_argument("--list-agents", action="store_true", help="List saved agents")
341
+ parser.add_argument("--remove-agent", metavar="ALIAS", help="Remove saved agent")
342
+ parser.add_argument("--show-config", action="store_true", help="Show config file location")
343
+ parser.add_argument("--version", action="store_true", help="Show SDK and AdCP version")
344
+
345
+ # Execution options
346
+ parser.add_argument("--protocol", choices=["mcp", "a2a"], help="Force protocol type")
347
+ parser.add_argument("--auth", help="Authentication token")
348
+ parser.add_argument("--json", action="store_true", help="Output as JSON")
349
+ parser.add_argument("--debug", action="store_true", help="Enable debug mode")
350
+ parser.add_argument("--help", "-h", action="store_true", help="Show help")
351
+
352
+ # Positional arguments
353
+ parser.add_argument("agent", nargs="?", help="Agent alias, URL, or config")
354
+ parser.add_argument("tool", nargs="?", help="Tool name to execute")
355
+ parser.add_argument("payload", nargs="?", help="Payload (JSON, @file, or stdin)")
356
+
357
+ # Parse known args to handle --save-auth with positional args
358
+ args, remaining = parser.parse_known_args()
359
+
360
+ # Handle help
361
+ if args.help or (
362
+ not args.agent
363
+ and not any(
364
+ [
365
+ args.save_auth,
366
+ args.list_agents,
367
+ args.remove_agent,
368
+ args.show_config,
369
+ args.version,
370
+ ]
371
+ )
372
+ ):
373
+ parser.print_help()
374
+ print("\nExamples:")
375
+ print(" adcp --version")
376
+ print(" adcp --save-auth myagent https://agent.example.com mcp")
377
+ print(" adcp --list-agents")
378
+ print(" adcp myagent get_info")
379
+ print(" adcp myagent list_tools")
380
+ print(' adcp myagent get_products \'{"brief":"TV ads"}\'')
381
+ print(" adcp https://agent.example.com list_tools")
382
+ sys.exit(0)
383
+
384
+ # Handle configuration commands
385
+ if args.version:
386
+ from adcp import __version__, get_adcp_version
387
+
388
+ print(f"AdCP Python SDK: v{__version__}")
389
+ print(f"Target AdCP Spec: {get_adcp_version()}")
390
+ sys.exit(0)
391
+
392
+ if args.save_auth:
393
+ url = args.agent if args.agent else None
394
+ protocol = args.tool if args.tool else None
395
+ handle_save_auth(args.save_auth, url, protocol)
396
+ sys.exit(0)
397
+
398
+ if args.list_agents:
399
+ handle_list_agents()
400
+ sys.exit(0)
401
+
402
+ if args.remove_agent:
403
+ handle_remove_agent(args.remove_agent)
404
+ sys.exit(0)
405
+
406
+ if args.show_config:
407
+ handle_show_config()
408
+ sys.exit(0)
409
+
410
+ # Execute tool
411
+ if not args.agent:
412
+ print("Error: Agent identifier required", file=sys.stderr)
413
+ sys.exit(1)
414
+
415
+ if not args.tool:
416
+ print("Error: Tool name required", file=sys.stderr)
417
+ sys.exit(1)
418
+
419
+ # Resolve agent config
420
+ agent_config = resolve_agent_config(args.agent)
421
+
422
+ # Override with command-line options
423
+ if args.protocol:
424
+ agent_config["protocol"] = args.protocol
425
+
426
+ if args.auth:
427
+ agent_config["auth_token"] = args.auth
428
+
429
+ if args.debug:
430
+ agent_config["debug"] = True
431
+
432
+ # Load payload
433
+ payload = load_payload(args.payload)
434
+
435
+ # Execute
436
+ asyncio.run(execute_tool(agent_config, args.tool, payload, args.json))
437
+
438
+
439
+ if __name__ == "__main__":
440
+ main()