airbyte-agent-amazon-ads 0.1.18__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.
- airbyte_agent_amazon_ads/__init__.py +75 -0
- airbyte_agent_amazon_ads/_vendored/__init__.py +1 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/__init__.py +82 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/auth_strategies.py +1171 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/auth_template.py +135 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/cloud_utils/__init__.py +5 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/cloud_utils/client.py +213 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/connector_model_loader.py +1116 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/constants.py +78 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/exceptions.py +23 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/executor/__init__.py +31 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/executor/hosted_executor.py +196 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/executor/local_executor.py +1773 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/executor/models.py +190 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/extensions.py +693 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/http/__init__.py +37 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/http/adapters/__init__.py +9 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/http/adapters/httpx_adapter.py +251 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/http/config.py +98 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/http/exceptions.py +119 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/http/protocols.py +114 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/http/response.py +104 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/http_client.py +693 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/introspection.py +481 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/logging/__init__.py +11 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/logging/logger.py +273 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/logging/types.py +93 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/observability/__init__.py +11 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/observability/config.py +179 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/observability/models.py +19 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/observability/redactor.py +81 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/observability/session.py +103 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/performance/__init__.py +6 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/performance/instrumentation.py +57 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/performance/metrics.py +93 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/schema/__init__.py +75 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/schema/base.py +201 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/schema/components.py +244 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/schema/connector.py +120 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/schema/extensions.py +301 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/schema/operations.py +146 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/schema/security.py +236 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/secrets.py +182 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/telemetry/__init__.py +10 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/telemetry/config.py +32 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/telemetry/events.py +59 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/telemetry/tracker.py +155 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/types.py +255 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/utils.py +60 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/validation.py +828 -0
- airbyte_agent_amazon_ads/connector.py +711 -0
- airbyte_agent_amazon_ads/connector_model.py +2213 -0
- airbyte_agent_amazon_ads/models.py +225 -0
- airbyte_agent_amazon_ads/types.py +238 -0
- airbyte_agent_amazon_ads-0.1.18.dist-info/METADATA +140 -0
- airbyte_agent_amazon_ads-0.1.18.dist-info/RECORD +57 -0
- airbyte_agent_amazon_ads-0.1.18.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Amazon-Ads connector.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import inspect
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from functools import wraps
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Callable, Mapping, TypeVar, overload
|
|
12
|
+
try:
|
|
13
|
+
from typing import Literal
|
|
14
|
+
except ImportError:
|
|
15
|
+
from typing_extensions import Literal
|
|
16
|
+
|
|
17
|
+
from .connector_model import AmazonAdsConnectorModel
|
|
18
|
+
from ._vendored.connector_sdk.introspection import describe_entities, generate_tool_description
|
|
19
|
+
from .types import (
|
|
20
|
+
PortfoliosGetParams,
|
|
21
|
+
PortfoliosListParams,
|
|
22
|
+
ProfilesGetParams,
|
|
23
|
+
ProfilesListParams,
|
|
24
|
+
SponsoredProductCampaignsGetParams,
|
|
25
|
+
SponsoredProductCampaignsListParams,
|
|
26
|
+
SponsoredProductCampaignsListParamsStatefilter,
|
|
27
|
+
AirbyteSearchParams,
|
|
28
|
+
ProfilesSearchFilter,
|
|
29
|
+
ProfilesSearchQuery,
|
|
30
|
+
)
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from .models import AmazonAdsAuthConfig
|
|
33
|
+
# Import response models and envelope models at runtime
|
|
34
|
+
from .models import (
|
|
35
|
+
AmazonAdsExecuteResult,
|
|
36
|
+
AmazonAdsExecuteResultWithMeta,
|
|
37
|
+
ProfilesListResult,
|
|
38
|
+
PortfoliosListResult,
|
|
39
|
+
SponsoredProductCampaignsListResult,
|
|
40
|
+
Portfolio,
|
|
41
|
+
Profile,
|
|
42
|
+
SponsoredProductCampaign,
|
|
43
|
+
AirbyteSearchHit,
|
|
44
|
+
AirbyteSearchResult,
|
|
45
|
+
ProfilesSearchData,
|
|
46
|
+
ProfilesSearchResult,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# TypeVar for decorator type preservation
|
|
50
|
+
_F = TypeVar("_F", bound=Callable[..., Any])
|
|
51
|
+
|
|
52
|
+
DEFAULT_MAX_OUTPUT_CHARS = 50_000 # ~50KB default, configurable per-tool
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _raise_output_too_large(message: str) -> None:
|
|
56
|
+
try:
|
|
57
|
+
from pydantic_ai import ModelRetry # type: ignore[import-not-found]
|
|
58
|
+
except Exception as exc:
|
|
59
|
+
raise RuntimeError(message) from exc
|
|
60
|
+
raise ModelRetry(message)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _check_output_size(result: Any, max_chars: int | None, tool_name: str) -> Any:
|
|
64
|
+
if max_chars is None or max_chars <= 0:
|
|
65
|
+
return result
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
serialized = json.dumps(result, default=str)
|
|
69
|
+
except (TypeError, ValueError):
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
if len(serialized) > max_chars:
|
|
73
|
+
truncated_preview = serialized[:500] + "..." if len(serialized) > 500 else serialized
|
|
74
|
+
_raise_output_too_large(
|
|
75
|
+
f"Tool '{tool_name}' output too large ({len(serialized):,} chars, limit {max_chars:,}). "
|
|
76
|
+
"Please narrow your query by: using the 'fields' parameter to select only needed fields, "
|
|
77
|
+
"adding filters, or reducing the 'limit'. "
|
|
78
|
+
f"Preview: {truncated_preview}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return result
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class AmazonAdsConnector:
|
|
87
|
+
"""
|
|
88
|
+
Type-safe Amazon-Ads API connector.
|
|
89
|
+
|
|
90
|
+
Auto-generated from OpenAPI specification with full type safety.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
connector_name = "amazon-ads"
|
|
94
|
+
connector_version = "1.0.4"
|
|
95
|
+
vendored_sdk_version = "0.1.0" # Version of vendored connector-sdk
|
|
96
|
+
|
|
97
|
+
# Map of (entity, action) -> needs_envelope for envelope wrapping decision
|
|
98
|
+
_ENVELOPE_MAP = {
|
|
99
|
+
("profiles", "list"): True,
|
|
100
|
+
("profiles", "get"): None,
|
|
101
|
+
("portfolios", "list"): True,
|
|
102
|
+
("portfolios", "get"): None,
|
|
103
|
+
("sponsored_product_campaigns", "list"): True,
|
|
104
|
+
("sponsored_product_campaigns", "get"): None,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Map of (entity, action) -> {python_param_name: api_param_name}
|
|
108
|
+
# Used to convert snake_case TypedDict keys to API parameter names in execute()
|
|
109
|
+
_PARAM_MAP = {
|
|
110
|
+
('profiles', 'list'): {'profile_type_filter': 'profileTypeFilter'},
|
|
111
|
+
('profiles', 'get'): {'profile_id': 'profileId'},
|
|
112
|
+
('portfolios', 'list'): {'include_extended_data_fields': 'includeExtendedDataFields'},
|
|
113
|
+
('portfolios', 'get'): {'portfolio_id': 'portfolioId'},
|
|
114
|
+
('sponsored_product_campaigns', 'list'): {'state_filter': 'stateFilter', 'max_results': 'maxResults', 'next_token': 'nextToken'},
|
|
115
|
+
('sponsored_product_campaigns', 'get'): {'campaign_id': 'campaignId'},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
def __init__(
|
|
119
|
+
self,
|
|
120
|
+
auth_config: AmazonAdsAuthConfig | None = None,
|
|
121
|
+
external_user_id: str | None = None,
|
|
122
|
+
airbyte_client_id: str | None = None,
|
|
123
|
+
airbyte_client_secret: str | None = None,
|
|
124
|
+
on_token_refresh: Any | None = None,
|
|
125
|
+
region: str | None = None ):
|
|
126
|
+
"""
|
|
127
|
+
Initialize a new amazon-ads connector instance.
|
|
128
|
+
|
|
129
|
+
Supports both local and hosted execution modes:
|
|
130
|
+
- Local mode: Provide `auth_config` for direct API calls
|
|
131
|
+
- Hosted mode: Provide `external_user_id`, `airbyte_client_id`, and `airbyte_client_secret` for hosted execution
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
auth_config: Typed authentication configuration (required for local mode)
|
|
135
|
+
external_user_id: External user ID (required for hosted mode)
|
|
136
|
+
airbyte_client_id: Airbyte OAuth client ID (required for hosted mode)
|
|
137
|
+
airbyte_client_secret: Airbyte OAuth client secret (required for hosted mode)
|
|
138
|
+
on_token_refresh: Optional callback for OAuth2 token refresh persistence.
|
|
139
|
+
Called with new_tokens dict when tokens are refreshed. Can be sync or async.
|
|
140
|
+
Example: lambda tokens: save_to_database(tokens) region: The Amazon Ads API endpoint URL based on region:
|
|
141
|
+
- NA (North America): https://advertising-api.amazon.com
|
|
142
|
+
- EU (Europe): https://advertising-api-eu.amazon.com
|
|
143
|
+
- FE (Far East): https://advertising-api-fe.amazon.com
|
|
144
|
+
|
|
145
|
+
Examples:
|
|
146
|
+
# Local mode (direct API calls)
|
|
147
|
+
connector = AmazonAdsConnector(auth_config=AmazonAdsAuthConfig(client_id="...", client_secret="...", refresh_token="..."))
|
|
148
|
+
# Hosted mode (executed on Airbyte cloud)
|
|
149
|
+
connector = AmazonAdsConnector(
|
|
150
|
+
external_user_id="user-123",
|
|
151
|
+
airbyte_client_id="client_abc123",
|
|
152
|
+
airbyte_client_secret="secret_xyz789"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Local mode with OAuth2 token refresh callback
|
|
156
|
+
def save_tokens(new_tokens: dict) -> None:
|
|
157
|
+
# Persist updated tokens to your storage (file, database, etc.)
|
|
158
|
+
with open("tokens.json", "w") as f:
|
|
159
|
+
json.dump(new_tokens, f)
|
|
160
|
+
|
|
161
|
+
connector = AmazonAdsConnector(
|
|
162
|
+
auth_config=AmazonAdsAuthConfig(access_token="...", refresh_token="..."),
|
|
163
|
+
on_token_refresh=save_tokens
|
|
164
|
+
)
|
|
165
|
+
"""
|
|
166
|
+
# Hosted mode: external_user_id, airbyte_client_id, and airbyte_client_secret provided
|
|
167
|
+
if external_user_id and airbyte_client_id and airbyte_client_secret:
|
|
168
|
+
from ._vendored.connector_sdk.executor import HostedExecutor
|
|
169
|
+
self._executor = HostedExecutor(
|
|
170
|
+
external_user_id=external_user_id,
|
|
171
|
+
airbyte_client_id=airbyte_client_id,
|
|
172
|
+
airbyte_client_secret=airbyte_client_secret,
|
|
173
|
+
connector_definition_id=str(AmazonAdsConnectorModel.id),
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
# Local mode: auth_config required
|
|
177
|
+
if not auth_config:
|
|
178
|
+
raise ValueError(
|
|
179
|
+
"Either provide (external_user_id, airbyte_client_id, airbyte_client_secret) for hosted mode "
|
|
180
|
+
"or auth_config for local mode"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
from ._vendored.connector_sdk.executor import LocalExecutor
|
|
184
|
+
|
|
185
|
+
# Build config_values dict from server variables
|
|
186
|
+
config_values: dict[str, str] = {}
|
|
187
|
+
if region:
|
|
188
|
+
config_values["region"] = region
|
|
189
|
+
|
|
190
|
+
self._executor = LocalExecutor(
|
|
191
|
+
model=AmazonAdsConnectorModel,
|
|
192
|
+
auth_config=auth_config.model_dump() if auth_config else None,
|
|
193
|
+
config_values=config_values,
|
|
194
|
+
on_token_refresh=on_token_refresh
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Update base_url with server variables if provided
|
|
198
|
+
base_url = self._executor.http_client.base_url
|
|
199
|
+
if region:
|
|
200
|
+
base_url = base_url.replace("{region}", region)
|
|
201
|
+
self._executor.http_client.base_url = base_url
|
|
202
|
+
|
|
203
|
+
# Initialize entity query objects
|
|
204
|
+
self.profiles = ProfilesQuery(self)
|
|
205
|
+
self.portfolios = PortfoliosQuery(self)
|
|
206
|
+
self.sponsored_product_campaigns = SponsoredProductCampaignsQuery(self)
|
|
207
|
+
|
|
208
|
+
# ===== TYPED EXECUTE METHOD (Recommended Interface) =====
|
|
209
|
+
|
|
210
|
+
@overload
|
|
211
|
+
async def execute(
|
|
212
|
+
self,
|
|
213
|
+
entity: Literal["profiles"],
|
|
214
|
+
action: Literal["list"],
|
|
215
|
+
params: "ProfilesListParams"
|
|
216
|
+
) -> "ProfilesListResult": ...
|
|
217
|
+
|
|
218
|
+
@overload
|
|
219
|
+
async def execute(
|
|
220
|
+
self,
|
|
221
|
+
entity: Literal["profiles"],
|
|
222
|
+
action: Literal["get"],
|
|
223
|
+
params: "ProfilesGetParams"
|
|
224
|
+
) -> "Profile": ...
|
|
225
|
+
|
|
226
|
+
@overload
|
|
227
|
+
async def execute(
|
|
228
|
+
self,
|
|
229
|
+
entity: Literal["portfolios"],
|
|
230
|
+
action: Literal["list"],
|
|
231
|
+
params: "PortfoliosListParams"
|
|
232
|
+
) -> "PortfoliosListResult": ...
|
|
233
|
+
|
|
234
|
+
@overload
|
|
235
|
+
async def execute(
|
|
236
|
+
self,
|
|
237
|
+
entity: Literal["portfolios"],
|
|
238
|
+
action: Literal["get"],
|
|
239
|
+
params: "PortfoliosGetParams"
|
|
240
|
+
) -> "Portfolio": ...
|
|
241
|
+
|
|
242
|
+
@overload
|
|
243
|
+
async def execute(
|
|
244
|
+
self,
|
|
245
|
+
entity: Literal["sponsored_product_campaigns"],
|
|
246
|
+
action: Literal["list"],
|
|
247
|
+
params: "SponsoredProductCampaignsListParams"
|
|
248
|
+
) -> "SponsoredProductCampaignsListResult": ...
|
|
249
|
+
|
|
250
|
+
@overload
|
|
251
|
+
async def execute(
|
|
252
|
+
self,
|
|
253
|
+
entity: Literal["sponsored_product_campaigns"],
|
|
254
|
+
action: Literal["get"],
|
|
255
|
+
params: "SponsoredProductCampaignsGetParams"
|
|
256
|
+
) -> "SponsoredProductCampaign": ...
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@overload
|
|
260
|
+
async def execute(
|
|
261
|
+
self,
|
|
262
|
+
entity: str,
|
|
263
|
+
action: Literal["list", "get", "search"],
|
|
264
|
+
params: Mapping[str, Any]
|
|
265
|
+
) -> AmazonAdsExecuteResult[Any] | AmazonAdsExecuteResultWithMeta[Any, Any] | Any: ...
|
|
266
|
+
|
|
267
|
+
async def execute(
|
|
268
|
+
self,
|
|
269
|
+
entity: str,
|
|
270
|
+
action: Literal["list", "get", "search"],
|
|
271
|
+
params: Mapping[str, Any] | None = None
|
|
272
|
+
) -> Any:
|
|
273
|
+
"""
|
|
274
|
+
Execute an entity operation with full type safety.
|
|
275
|
+
|
|
276
|
+
This is the recommended interface for blessed connectors as it:
|
|
277
|
+
- Uses the same signature as non-blessed connectors
|
|
278
|
+
- Provides full IDE autocomplete for entity/action/params
|
|
279
|
+
- Makes migration from generic to blessed connectors seamless
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
entity: Entity name (e.g., "customers")
|
|
283
|
+
action: Operation action (e.g., "create", "get", "list")
|
|
284
|
+
params: Operation parameters (typed based on entity+action)
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Typed response based on the operation
|
|
288
|
+
|
|
289
|
+
Example:
|
|
290
|
+
customer = await connector.execute(
|
|
291
|
+
entity="customers",
|
|
292
|
+
action="get",
|
|
293
|
+
params={"id": "cus_123"}
|
|
294
|
+
)
|
|
295
|
+
"""
|
|
296
|
+
from ._vendored.connector_sdk.executor import ExecutionConfig
|
|
297
|
+
|
|
298
|
+
# Remap parameter names from snake_case (TypedDict keys) to API parameter names
|
|
299
|
+
resolved_params = dict(params) if params is not None else None
|
|
300
|
+
if resolved_params:
|
|
301
|
+
param_map = self._PARAM_MAP.get((entity, action), {})
|
|
302
|
+
if param_map:
|
|
303
|
+
resolved_params = {param_map.get(k, k): v for k, v in resolved_params.items()}
|
|
304
|
+
|
|
305
|
+
# Use ExecutionConfig for both local and hosted executors
|
|
306
|
+
config = ExecutionConfig(
|
|
307
|
+
entity=entity,
|
|
308
|
+
action=action,
|
|
309
|
+
params=resolved_params
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
result = await self._executor.execute(config)
|
|
313
|
+
|
|
314
|
+
if not result.success:
|
|
315
|
+
raise RuntimeError(f"Execution failed: {result.error}")
|
|
316
|
+
|
|
317
|
+
# Check if this operation has extractors configured
|
|
318
|
+
has_extractors = self._ENVELOPE_MAP.get((entity, action), False)
|
|
319
|
+
|
|
320
|
+
if has_extractors:
|
|
321
|
+
# With extractors - return Pydantic envelope with data and meta
|
|
322
|
+
if result.meta is not None:
|
|
323
|
+
return AmazonAdsExecuteResultWithMeta[Any, Any](
|
|
324
|
+
data=result.data,
|
|
325
|
+
meta=result.meta
|
|
326
|
+
)
|
|
327
|
+
else:
|
|
328
|
+
return AmazonAdsExecuteResult[Any](data=result.data)
|
|
329
|
+
else:
|
|
330
|
+
# No extractors - return raw response data
|
|
331
|
+
return result.data
|
|
332
|
+
|
|
333
|
+
# ===== INTROSPECTION METHODS =====
|
|
334
|
+
|
|
335
|
+
@classmethod
|
|
336
|
+
def tool_utils(
|
|
337
|
+
cls,
|
|
338
|
+
func: _F | None = None,
|
|
339
|
+
*,
|
|
340
|
+
update_docstring: bool = True,
|
|
341
|
+
enable_hosted_mode_features: bool = True,
|
|
342
|
+
max_output_chars: int | None = DEFAULT_MAX_OUTPUT_CHARS,
|
|
343
|
+
) -> _F | Callable[[_F], _F]:
|
|
344
|
+
"""
|
|
345
|
+
Decorator that adds tool utilities like docstring augmentation and output limits.
|
|
346
|
+
|
|
347
|
+
Usage:
|
|
348
|
+
@mcp.tool()
|
|
349
|
+
@AmazonAdsConnector.tool_utils
|
|
350
|
+
async def execute(entity: str, action: str, params: dict):
|
|
351
|
+
...
|
|
352
|
+
|
|
353
|
+
@mcp.tool()
|
|
354
|
+
@AmazonAdsConnector.tool_utils(update_docstring=False, max_output_chars=None)
|
|
355
|
+
async def execute(entity: str, action: str, params: dict):
|
|
356
|
+
...
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
update_docstring: When True, append connector capabilities to __doc__.
|
|
360
|
+
enable_hosted_mode_features: When False, omit hosted-mode search sections from docstrings.
|
|
361
|
+
max_output_chars: Max serialized output size before raising. Use None to disable.
|
|
362
|
+
"""
|
|
363
|
+
|
|
364
|
+
def decorate(inner: _F) -> _F:
|
|
365
|
+
if update_docstring:
|
|
366
|
+
description = generate_tool_description(
|
|
367
|
+
AmazonAdsConnectorModel,
|
|
368
|
+
enable_hosted_mode_features=enable_hosted_mode_features,
|
|
369
|
+
)
|
|
370
|
+
original_doc = inner.__doc__ or ""
|
|
371
|
+
if original_doc.strip():
|
|
372
|
+
full_doc = f"{original_doc.strip()}\n{description}"
|
|
373
|
+
else:
|
|
374
|
+
full_doc = description
|
|
375
|
+
else:
|
|
376
|
+
full_doc = ""
|
|
377
|
+
|
|
378
|
+
if inspect.iscoroutinefunction(inner):
|
|
379
|
+
|
|
380
|
+
@wraps(inner)
|
|
381
|
+
async def aw(*args: Any, **kwargs: Any) -> Any:
|
|
382
|
+
result = await inner(*args, **kwargs)
|
|
383
|
+
return _check_output_size(result, max_output_chars, inner.__name__)
|
|
384
|
+
|
|
385
|
+
wrapped = aw
|
|
386
|
+
else:
|
|
387
|
+
|
|
388
|
+
@wraps(inner)
|
|
389
|
+
def sw(*args: Any, **kwargs: Any) -> Any:
|
|
390
|
+
result = inner(*args, **kwargs)
|
|
391
|
+
return _check_output_size(result, max_output_chars, inner.__name__)
|
|
392
|
+
|
|
393
|
+
wrapped = sw
|
|
394
|
+
|
|
395
|
+
if update_docstring:
|
|
396
|
+
wrapped.__doc__ = full_doc
|
|
397
|
+
return wrapped # type: ignore[return-value]
|
|
398
|
+
|
|
399
|
+
if func is not None:
|
|
400
|
+
return decorate(func)
|
|
401
|
+
return decorate
|
|
402
|
+
|
|
403
|
+
def list_entities(self) -> list[dict[str, Any]]:
|
|
404
|
+
"""
|
|
405
|
+
Get structured data about available entities, actions, and parameters.
|
|
406
|
+
|
|
407
|
+
Returns a list of entity descriptions with:
|
|
408
|
+
- entity_name: Name of the entity (e.g., "contacts", "deals")
|
|
409
|
+
- description: Entity description from the first endpoint
|
|
410
|
+
- available_actions: List of actions (e.g., ["list", "get", "create"])
|
|
411
|
+
- parameters: Dict mapping action -> list of parameter dicts
|
|
412
|
+
|
|
413
|
+
Example:
|
|
414
|
+
entities = connector.list_entities()
|
|
415
|
+
for entity in entities:
|
|
416
|
+
print(f"{entity['entity_name']}: {entity['available_actions']}")
|
|
417
|
+
"""
|
|
418
|
+
return describe_entities(AmazonAdsConnectorModel)
|
|
419
|
+
|
|
420
|
+
def entity_schema(self, entity: str) -> dict[str, Any] | None:
|
|
421
|
+
"""
|
|
422
|
+
Get the JSON schema for an entity.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
entity: Entity name (e.g., "contacts", "companies")
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
JSON schema dict describing the entity structure, or None if not found.
|
|
429
|
+
|
|
430
|
+
Example:
|
|
431
|
+
schema = connector.entity_schema("contacts")
|
|
432
|
+
if schema:
|
|
433
|
+
print(f"Contact properties: {list(schema.get('properties', {}).keys())}")
|
|
434
|
+
"""
|
|
435
|
+
entity_def = next(
|
|
436
|
+
(e for e in AmazonAdsConnectorModel.entities if e.name == entity),
|
|
437
|
+
None
|
|
438
|
+
)
|
|
439
|
+
if entity_def is None:
|
|
440
|
+
logging.getLogger(__name__).warning(
|
|
441
|
+
f"Entity '{entity}' not found. Available entities: "
|
|
442
|
+
f"{[e.name for e in AmazonAdsConnectorModel.entities]}"
|
|
443
|
+
)
|
|
444
|
+
return entity_def.entity_schema if entity_def else None
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
class ProfilesQuery:
|
|
449
|
+
"""
|
|
450
|
+
Query class for Profiles entity operations.
|
|
451
|
+
"""
|
|
452
|
+
|
|
453
|
+
def __init__(self, connector: AmazonAdsConnector):
|
|
454
|
+
"""Initialize query with connector reference."""
|
|
455
|
+
self._connector = connector
|
|
456
|
+
|
|
457
|
+
async def list(
|
|
458
|
+
self,
|
|
459
|
+
profile_type_filter: str | None = None,
|
|
460
|
+
**kwargs
|
|
461
|
+
) -> ProfilesListResult:
|
|
462
|
+
"""
|
|
463
|
+
Returns a list of advertising profiles associated with the authenticated user.
|
|
464
|
+
Profiles represent an advertiser's account in a specific marketplace. Advertisers
|
|
465
|
+
may have a single profile if they advertise in only one marketplace, or a separate
|
|
466
|
+
profile for each marketplace if they advertise regionally or globally.
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
profile_type_filter: Filter profiles by type. Comma-separated list of profile types.
|
|
471
|
+
Valid values: seller, vendor, agency
|
|
472
|
+
|
|
473
|
+
**kwargs: Additional parameters
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
ProfilesListResult
|
|
477
|
+
"""
|
|
478
|
+
params = {k: v for k, v in {
|
|
479
|
+
"profileTypeFilter": profile_type_filter,
|
|
480
|
+
**kwargs
|
|
481
|
+
}.items() if v is not None}
|
|
482
|
+
|
|
483
|
+
result = await self._connector.execute("profiles", "list", params)
|
|
484
|
+
# Cast generic envelope to concrete typed result
|
|
485
|
+
return ProfilesListResult(
|
|
486
|
+
data=result.data
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
async def get(
|
|
492
|
+
self,
|
|
493
|
+
profile_id: str,
|
|
494
|
+
**kwargs
|
|
495
|
+
) -> Profile:
|
|
496
|
+
"""
|
|
497
|
+
Retrieves a single advertising profile by its ID. The profile contains
|
|
498
|
+
information about the advertiser's account in a specific marketplace.
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
profile_id: The unique identifier of the profile
|
|
503
|
+
**kwargs: Additional parameters
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
Profile
|
|
507
|
+
"""
|
|
508
|
+
params = {k: v for k, v in {
|
|
509
|
+
"profileId": profile_id,
|
|
510
|
+
**kwargs
|
|
511
|
+
}.items() if v is not None}
|
|
512
|
+
|
|
513
|
+
result = await self._connector.execute("profiles", "get", params)
|
|
514
|
+
return result
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
async def search(
|
|
519
|
+
self,
|
|
520
|
+
query: ProfilesSearchQuery,
|
|
521
|
+
limit: int | None = None,
|
|
522
|
+
cursor: str | None = None,
|
|
523
|
+
fields: list[list[str]] | None = None,
|
|
524
|
+
) -> ProfilesSearchResult:
|
|
525
|
+
"""
|
|
526
|
+
Search profiles records from Airbyte cache.
|
|
527
|
+
|
|
528
|
+
This operation searches cached data from Airbyte syncs.
|
|
529
|
+
Only available in hosted execution mode.
|
|
530
|
+
|
|
531
|
+
Available filter fields (ProfilesSearchFilter):
|
|
532
|
+
- account_info:
|
|
533
|
+
- country_code:
|
|
534
|
+
- currency_code:
|
|
535
|
+
- daily_budget:
|
|
536
|
+
- profile_id:
|
|
537
|
+
- timezone:
|
|
538
|
+
|
|
539
|
+
Args:
|
|
540
|
+
query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
|
|
541
|
+
in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
|
|
542
|
+
limit: Maximum results to return (default 1000)
|
|
543
|
+
cursor: Pagination cursor from previous response's next_cursor
|
|
544
|
+
fields: Field paths to include in results. Each path is a list of keys for nested access.
|
|
545
|
+
Example: [["id"], ["user", "name"]] returns id and user.name fields.
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
ProfilesSearchResult with hits (list of AirbyteSearchHit[ProfilesSearchData]) and pagination info
|
|
549
|
+
|
|
550
|
+
Raises:
|
|
551
|
+
NotImplementedError: If called in local execution mode
|
|
552
|
+
"""
|
|
553
|
+
params: dict[str, Any] = {"query": query}
|
|
554
|
+
if limit is not None:
|
|
555
|
+
params["limit"] = limit
|
|
556
|
+
if cursor is not None:
|
|
557
|
+
params["cursor"] = cursor
|
|
558
|
+
if fields is not None:
|
|
559
|
+
params["fields"] = fields
|
|
560
|
+
|
|
561
|
+
result = await self._connector.execute("profiles", "search", params)
|
|
562
|
+
|
|
563
|
+
# Parse response into typed result
|
|
564
|
+
return ProfilesSearchResult(
|
|
565
|
+
hits=[
|
|
566
|
+
AirbyteSearchHit[ProfilesSearchData](
|
|
567
|
+
id=hit.get("id"),
|
|
568
|
+
score=hit.get("score"),
|
|
569
|
+
data=ProfilesSearchData(**hit.get("data", {}))
|
|
570
|
+
)
|
|
571
|
+
for hit in result.get("hits", [])
|
|
572
|
+
],
|
|
573
|
+
next_cursor=result.get("next_cursor"),
|
|
574
|
+
took_ms=result.get("took_ms")
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
class PortfoliosQuery:
|
|
578
|
+
"""
|
|
579
|
+
Query class for Portfolios entity operations.
|
|
580
|
+
"""
|
|
581
|
+
|
|
582
|
+
def __init__(self, connector: AmazonAdsConnector):
|
|
583
|
+
"""Initialize query with connector reference."""
|
|
584
|
+
self._connector = connector
|
|
585
|
+
|
|
586
|
+
async def list(
|
|
587
|
+
self,
|
|
588
|
+
include_extended_data_fields: str | None = None,
|
|
589
|
+
**kwargs
|
|
590
|
+
) -> PortfoliosListResult:
|
|
591
|
+
"""
|
|
592
|
+
Returns a list of portfolios for the specified profile. Portfolios are used to
|
|
593
|
+
group campaigns together for organizational and budget management purposes.
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
include_extended_data_fields: Whether to include extended data fields in the response
|
|
598
|
+
**kwargs: Additional parameters
|
|
599
|
+
|
|
600
|
+
Returns:
|
|
601
|
+
PortfoliosListResult
|
|
602
|
+
"""
|
|
603
|
+
params = {k: v for k, v in {
|
|
604
|
+
"includeExtendedDataFields": include_extended_data_fields,
|
|
605
|
+
**kwargs
|
|
606
|
+
}.items() if v is not None}
|
|
607
|
+
|
|
608
|
+
result = await self._connector.execute("portfolios", "list", params)
|
|
609
|
+
# Cast generic envelope to concrete typed result
|
|
610
|
+
return PortfoliosListResult(
|
|
611
|
+
data=result.data
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
async def get(
|
|
617
|
+
self,
|
|
618
|
+
portfolio_id: str,
|
|
619
|
+
**kwargs
|
|
620
|
+
) -> Portfolio:
|
|
621
|
+
"""
|
|
622
|
+
Retrieves a single portfolio by its ID using the v2 API.
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
Args:
|
|
626
|
+
portfolio_id: The unique identifier of the portfolio
|
|
627
|
+
**kwargs: Additional parameters
|
|
628
|
+
|
|
629
|
+
Returns:
|
|
630
|
+
Portfolio
|
|
631
|
+
"""
|
|
632
|
+
params = {k: v for k, v in {
|
|
633
|
+
"portfolioId": portfolio_id,
|
|
634
|
+
**kwargs
|
|
635
|
+
}.items() if v is not None}
|
|
636
|
+
|
|
637
|
+
result = await self._connector.execute("portfolios", "get", params)
|
|
638
|
+
return result
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
class SponsoredProductCampaignsQuery:
|
|
643
|
+
"""
|
|
644
|
+
Query class for SponsoredProductCampaigns entity operations.
|
|
645
|
+
"""
|
|
646
|
+
|
|
647
|
+
def __init__(self, connector: AmazonAdsConnector):
|
|
648
|
+
"""Initialize query with connector reference."""
|
|
649
|
+
self._connector = connector
|
|
650
|
+
|
|
651
|
+
async def list(
|
|
652
|
+
self,
|
|
653
|
+
state_filter: SponsoredProductCampaignsListParamsStatefilter | None = None,
|
|
654
|
+
max_results: int | None = None,
|
|
655
|
+
next_token: str | None = None,
|
|
656
|
+
**kwargs
|
|
657
|
+
) -> SponsoredProductCampaignsListResult:
|
|
658
|
+
"""
|
|
659
|
+
Returns a list of sponsored product campaigns for the specified profile.
|
|
660
|
+
Sponsored Products campaigns promote individual product listings on Amazon.
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
Args:
|
|
664
|
+
state_filter: Parameter stateFilter
|
|
665
|
+
max_results: Maximum number of results to return
|
|
666
|
+
next_token: Token for pagination
|
|
667
|
+
**kwargs: Additional parameters
|
|
668
|
+
|
|
669
|
+
Returns:
|
|
670
|
+
SponsoredProductCampaignsListResult
|
|
671
|
+
"""
|
|
672
|
+
params = {k: v for k, v in {
|
|
673
|
+
"stateFilter": state_filter,
|
|
674
|
+
"maxResults": max_results,
|
|
675
|
+
"nextToken": next_token,
|
|
676
|
+
**kwargs
|
|
677
|
+
}.items() if v is not None}
|
|
678
|
+
|
|
679
|
+
result = await self._connector.execute("sponsored_product_campaigns", "list", params)
|
|
680
|
+
# Cast generic envelope to concrete typed result
|
|
681
|
+
return SponsoredProductCampaignsListResult(
|
|
682
|
+
data=result.data
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
async def get(
|
|
688
|
+
self,
|
|
689
|
+
campaign_id: str,
|
|
690
|
+
**kwargs
|
|
691
|
+
) -> SponsoredProductCampaign:
|
|
692
|
+
"""
|
|
693
|
+
Retrieves a single sponsored product campaign by its ID using the v2 API.
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
Args:
|
|
697
|
+
campaign_id: The unique identifier of the campaign
|
|
698
|
+
**kwargs: Additional parameters
|
|
699
|
+
|
|
700
|
+
Returns:
|
|
701
|
+
SponsoredProductCampaign
|
|
702
|
+
"""
|
|
703
|
+
params = {k: v for k, v in {
|
|
704
|
+
"campaignId": campaign_id,
|
|
705
|
+
**kwargs
|
|
706
|
+
}.items() if v is not None}
|
|
707
|
+
|
|
708
|
+
result = await self._connector.execute("sponsored_product_campaigns", "get", params)
|
|
709
|
+
return result
|
|
710
|
+
|
|
711
|
+
|