amazon-ads-mcp 0.2.7__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.
- amazon_ads_mcp/__init__.py +11 -0
- amazon_ads_mcp/auth/__init__.py +33 -0
- amazon_ads_mcp/auth/base.py +211 -0
- amazon_ads_mcp/auth/hooks.py +172 -0
- amazon_ads_mcp/auth/manager.py +791 -0
- amazon_ads_mcp/auth/oauth_state_store.py +277 -0
- amazon_ads_mcp/auth/providers/__init__.py +14 -0
- amazon_ads_mcp/auth/providers/direct.py +393 -0
- amazon_ads_mcp/auth/providers/example_auth0.py.example +216 -0
- amazon_ads_mcp/auth/providers/openbridge.py +512 -0
- amazon_ads_mcp/auth/registry.py +146 -0
- amazon_ads_mcp/auth/secure_token_store.py +297 -0
- amazon_ads_mcp/auth/token_store.py +723 -0
- amazon_ads_mcp/config/__init__.py +5 -0
- amazon_ads_mcp/config/sampling.py +111 -0
- amazon_ads_mcp/config/settings.py +366 -0
- amazon_ads_mcp/exceptions.py +314 -0
- amazon_ads_mcp/middleware/__init__.py +11 -0
- amazon_ads_mcp/middleware/authentication.py +1474 -0
- amazon_ads_mcp/middleware/caching.py +177 -0
- amazon_ads_mcp/middleware/oauth.py +175 -0
- amazon_ads_mcp/middleware/sampling.py +112 -0
- amazon_ads_mcp/models/__init__.py +320 -0
- amazon_ads_mcp/models/amc_models.py +837 -0
- amazon_ads_mcp/models/api_responses.py +847 -0
- amazon_ads_mcp/models/base_models.py +215 -0
- amazon_ads_mcp/models/builtin_responses.py +496 -0
- amazon_ads_mcp/models/dsp_models.py +556 -0
- amazon_ads_mcp/models/stores_brands.py +610 -0
- amazon_ads_mcp/server/__init__.py +6 -0
- amazon_ads_mcp/server/__main__.py +6 -0
- amazon_ads_mcp/server/builtin_prompts.py +269 -0
- amazon_ads_mcp/server/builtin_tools.py +962 -0
- amazon_ads_mcp/server/file_routes.py +547 -0
- amazon_ads_mcp/server/html_templates.py +149 -0
- amazon_ads_mcp/server/mcp_server.py +327 -0
- amazon_ads_mcp/server/openapi_utils.py +158 -0
- amazon_ads_mcp/server/sampling_handler.py +251 -0
- amazon_ads_mcp/server/server_builder.py +751 -0
- amazon_ads_mcp/server/sidecar_loader.py +178 -0
- amazon_ads_mcp/server/transform_executor.py +827 -0
- amazon_ads_mcp/tools/__init__.py +22 -0
- amazon_ads_mcp/tools/cache_management.py +105 -0
- amazon_ads_mcp/tools/download_tools.py +267 -0
- amazon_ads_mcp/tools/identity.py +236 -0
- amazon_ads_mcp/tools/oauth.py +598 -0
- amazon_ads_mcp/tools/profile.py +150 -0
- amazon_ads_mcp/tools/profile_listing.py +285 -0
- amazon_ads_mcp/tools/region.py +320 -0
- amazon_ads_mcp/tools/region_identity.py +175 -0
- amazon_ads_mcp/utils/__init__.py +6 -0
- amazon_ads_mcp/utils/async_compat.py +215 -0
- amazon_ads_mcp/utils/errors.py +452 -0
- amazon_ads_mcp/utils/export_content_type_resolver.py +249 -0
- amazon_ads_mcp/utils/export_download_handler.py +579 -0
- amazon_ads_mcp/utils/header_resolver.py +81 -0
- amazon_ads_mcp/utils/http/__init__.py +56 -0
- amazon_ads_mcp/utils/http/circuit_breaker.py +127 -0
- amazon_ads_mcp/utils/http/client_manager.py +329 -0
- amazon_ads_mcp/utils/http/request.py +207 -0
- amazon_ads_mcp/utils/http/resilience.py +512 -0
- amazon_ads_mcp/utils/http/resilient_client.py +195 -0
- amazon_ads_mcp/utils/http/retry.py +76 -0
- amazon_ads_mcp/utils/http_client.py +873 -0
- amazon_ads_mcp/utils/media/__init__.py +21 -0
- amazon_ads_mcp/utils/media/negotiator.py +243 -0
- amazon_ads_mcp/utils/media/types.py +199 -0
- amazon_ads_mcp/utils/openapi/__init__.py +16 -0
- amazon_ads_mcp/utils/openapi/json.py +55 -0
- amazon_ads_mcp/utils/openapi/loader.py +263 -0
- amazon_ads_mcp/utils/openapi/refs.py +46 -0
- amazon_ads_mcp/utils/region_config.py +200 -0
- amazon_ads_mcp/utils/response_wrapper.py +171 -0
- amazon_ads_mcp/utils/sampling_helpers.py +156 -0
- amazon_ads_mcp/utils/sampling_wrapper.py +173 -0
- amazon_ads_mcp/utils/security.py +630 -0
- amazon_ads_mcp/utils/tool_naming.py +137 -0
- amazon_ads_mcp-0.2.7.dist-info/METADATA +664 -0
- amazon_ads_mcp-0.2.7.dist-info/RECORD +82 -0
- amazon_ads_mcp-0.2.7.dist-info/WHEEL +4 -0
- amazon_ads_mcp-0.2.7.dist-info/entry_points.txt +3 -0
- amazon_ads_mcp-0.2.7.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Example Auth0 authentication provider.
|
|
2
|
+
|
|
3
|
+
This is an example showing how to implement a custom authentication provider
|
|
4
|
+
for Auth0 or similar OAuth2/OIDC providers.
|
|
5
|
+
|
|
6
|
+
To use this:
|
|
7
|
+
1. Rename to auth0.py (remove .example extension)
|
|
8
|
+
2. Configure your Auth0 application
|
|
9
|
+
3. Update the configuration as needed
|
|
10
|
+
4. Import it in __init__.py to auto-register
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from datetime import datetime, timedelta, timezone
|
|
15
|
+
from typing import Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
import httpx
|
|
18
|
+
import jwt
|
|
19
|
+
|
|
20
|
+
from ...models import AuthCredentials, Identity, Token
|
|
21
|
+
from ...utils.http import get_http_client
|
|
22
|
+
from ..base import BaseAmazonAdsProvider, BaseIdentityProvider, ProviderConfig
|
|
23
|
+
from ..registry import register_provider
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@register_provider("auth0")
|
|
29
|
+
class Auth0Provider(BaseAmazonAdsProvider, BaseIdentityProvider):
|
|
30
|
+
"""Auth0 authentication provider example.
|
|
31
|
+
|
|
32
|
+
This example shows how to integrate Auth0 for authentication,
|
|
33
|
+
which could then be mapped to Amazon Ads API credentials.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, config: ProviderConfig):
|
|
37
|
+
"""Initialize Auth0 provider.
|
|
38
|
+
|
|
39
|
+
:param config: Provider configuration
|
|
40
|
+
:type config: ProviderConfig
|
|
41
|
+
"""
|
|
42
|
+
self.domain = config.get("domain") # e.g., "your-tenant.auth0.com"
|
|
43
|
+
self.client_id = config.get("client_id")
|
|
44
|
+
self.client_secret = config.get("client_secret")
|
|
45
|
+
self.audience = config.get("audience", "https://advertising-api.amazon.com")
|
|
46
|
+
|
|
47
|
+
if not all([self.domain, self.client_id, self.client_secret]):
|
|
48
|
+
raise ValueError(
|
|
49
|
+
"Auth0 provider requires 'domain', 'client_id', and 'client_secret'"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
self._region = config.get("region", "na")
|
|
53
|
+
self._access_token: Optional[Token] = None
|
|
54
|
+
self._id_token: Optional[str] = None
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def provider_type(self) -> str:
|
|
58
|
+
"""Return the provider type identifier."""
|
|
59
|
+
return "auth0"
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def region(self) -> str:
|
|
63
|
+
"""Get the current region."""
|
|
64
|
+
return self._region
|
|
65
|
+
|
|
66
|
+
async def initialize(self) -> None:
|
|
67
|
+
"""Initialize the provider."""
|
|
68
|
+
logger.info(f"Initializing Auth0 provider with domain {self.domain}")
|
|
69
|
+
|
|
70
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
71
|
+
"""Get shared HTTP client."""
|
|
72
|
+
return await get_http_client()
|
|
73
|
+
|
|
74
|
+
async def get_token(self) -> Token:
|
|
75
|
+
"""Get current access token from Auth0."""
|
|
76
|
+
if self._access_token and await self.validate_token(self._access_token):
|
|
77
|
+
return self._access_token
|
|
78
|
+
|
|
79
|
+
return await self._refresh_access_token()
|
|
80
|
+
|
|
81
|
+
async def _refresh_access_token(self) -> Token:
|
|
82
|
+
"""Get access token via Auth0 client credentials flow."""
|
|
83
|
+
logger.debug("Getting Auth0 access token")
|
|
84
|
+
|
|
85
|
+
client = await self._get_client()
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
response = await client.post(
|
|
89
|
+
f"https://{self.domain}/oauth/token",
|
|
90
|
+
json={
|
|
91
|
+
"client_id": self.client_id,
|
|
92
|
+
"client_secret": self.client_secret,
|
|
93
|
+
"audience": self.audience,
|
|
94
|
+
"grant_type": "client_credentials",
|
|
95
|
+
},
|
|
96
|
+
headers={"Content-Type": "application/json"},
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if response.status_code != 200:
|
|
100
|
+
logger.error(f"Auth0 token request failed: {response.text}")
|
|
101
|
+
response.raise_for_status()
|
|
102
|
+
|
|
103
|
+
data = response.json()
|
|
104
|
+
access_token = data.get("access_token")
|
|
105
|
+
expires_in = data.get("expires_in", 86400) # Default 24 hours
|
|
106
|
+
|
|
107
|
+
if not access_token:
|
|
108
|
+
raise ValueError("No access token in Auth0 response")
|
|
109
|
+
|
|
110
|
+
# Decode token to get claims
|
|
111
|
+
try:
|
|
112
|
+
# Note: In production, verify the signature properly
|
|
113
|
+
claims = jwt.decode(access_token, options={"verify_signature": False})
|
|
114
|
+
except:
|
|
115
|
+
claims = {}
|
|
116
|
+
|
|
117
|
+
expires_at = datetime.now(timezone.utc) + timedelta(seconds=expires_in)
|
|
118
|
+
|
|
119
|
+
self._access_token = Token(
|
|
120
|
+
value=access_token,
|
|
121
|
+
expires_at=expires_at,
|
|
122
|
+
token_type="Bearer",
|
|
123
|
+
metadata={
|
|
124
|
+
"issuer": f"https://{self.domain}/",
|
|
125
|
+
"subject": claims.get("sub"),
|
|
126
|
+
"audience": claims.get("aud"),
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
logger.debug(f"Auth0 access token obtained, expires at {expires_at}")
|
|
131
|
+
return self._access_token
|
|
132
|
+
|
|
133
|
+
except httpx.HTTPError as e:
|
|
134
|
+
logger.error(f"Failed to get Auth0 token: {e}")
|
|
135
|
+
raise
|
|
136
|
+
|
|
137
|
+
async def validate_token(self, token: Token) -> bool:
|
|
138
|
+
"""Validate if token is still valid."""
|
|
139
|
+
buffer = timedelta(minutes=5)
|
|
140
|
+
return datetime.now(timezone.utc) < (token.expires_at - buffer)
|
|
141
|
+
|
|
142
|
+
async def list_identities(self, **kwargs) -> List[Identity]:
|
|
143
|
+
"""List identities from Auth0.
|
|
144
|
+
|
|
145
|
+
This could query Auth0 Management API for users/organizations
|
|
146
|
+
that have Amazon Ads access.
|
|
147
|
+
"""
|
|
148
|
+
# Example: Return synthetic identity for now
|
|
149
|
+
# In real implementation, query Auth0 Management API
|
|
150
|
+
identity = Identity(
|
|
151
|
+
id="auth0-user",
|
|
152
|
+
type="auth0",
|
|
153
|
+
attributes={
|
|
154
|
+
"name": "Auth0 User",
|
|
155
|
+
"domain": self.domain,
|
|
156
|
+
"region": self._region,
|
|
157
|
+
"auth_method": "auth0",
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
return [identity]
|
|
161
|
+
|
|
162
|
+
async def get_identity(self, identity_id: str) -> Optional[Identity]:
|
|
163
|
+
"""Get specific identity by ID."""
|
|
164
|
+
identities = await self.list_identities()
|
|
165
|
+
for identity in identities:
|
|
166
|
+
if identity.id == identity_id:
|
|
167
|
+
return identity
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
async def get_identity_credentials(self, identity_id: str) -> AuthCredentials:
|
|
171
|
+
"""Get Amazon Ads credentials for an Auth0 identity.
|
|
172
|
+
|
|
173
|
+
This would typically:
|
|
174
|
+
1. Use the Auth0 token to call your backend API
|
|
175
|
+
2. Your backend exchanges this for Amazon Ads credentials
|
|
176
|
+
3. Return the Amazon Ads credentials
|
|
177
|
+
|
|
178
|
+
For this example, we'll show the structure:
|
|
179
|
+
"""
|
|
180
|
+
logger.info(f"Getting credentials for Auth0 identity {identity_id}")
|
|
181
|
+
|
|
182
|
+
# Get Auth0 token
|
|
183
|
+
auth0_token = await self.get_token()
|
|
184
|
+
|
|
185
|
+
# In a real implementation, you would:
|
|
186
|
+
# 1. Call your backend API with the Auth0 token
|
|
187
|
+
# 2. Backend validates the token and maps to Amazon Ads creds
|
|
188
|
+
# 3. Backend returns Amazon Ads access token
|
|
189
|
+
|
|
190
|
+
# For this example, we'll simulate the response
|
|
191
|
+
# In reality, this would come from your backend
|
|
192
|
+
amazon_ads_token = "YOUR_AMAZON_ADS_TOKEN_FROM_BACKEND"
|
|
193
|
+
amazon_ads_client_id = "YOUR_AMAZON_AD_API_CLIENT_ID" # or legacy AMAZON_ADS_CLIENT_ID
|
|
194
|
+
|
|
195
|
+
return AuthCredentials(
|
|
196
|
+
identity_id=identity_id,
|
|
197
|
+
access_token=amazon_ads_token,
|
|
198
|
+
expires_at=auth0_token.expires_at,
|
|
199
|
+
base_url=self.get_region_endpoint(),
|
|
200
|
+
headers={
|
|
201
|
+
"Authorization": f"Bearer {amazon_ads_token}",
|
|
202
|
+
"Amazon-Advertising-API-ClientId": amazon_ads_client_id,
|
|
203
|
+
},
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
async def get_headers(self) -> Dict[str, str]:
|
|
207
|
+
"""Get authentication headers."""
|
|
208
|
+
# Would need to implement credential exchange first
|
|
209
|
+
raise NotImplementedError(
|
|
210
|
+
"Auth0 provider requires backend integration for credential exchange"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
async def close(self) -> None:
|
|
214
|
+
"""Clean up provider resources."""
|
|
215
|
+
self._access_token = None
|
|
216
|
+
self._id_token = None
|