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.
Files changed (82) hide show
  1. amazon_ads_mcp/__init__.py +11 -0
  2. amazon_ads_mcp/auth/__init__.py +33 -0
  3. amazon_ads_mcp/auth/base.py +211 -0
  4. amazon_ads_mcp/auth/hooks.py +172 -0
  5. amazon_ads_mcp/auth/manager.py +791 -0
  6. amazon_ads_mcp/auth/oauth_state_store.py +277 -0
  7. amazon_ads_mcp/auth/providers/__init__.py +14 -0
  8. amazon_ads_mcp/auth/providers/direct.py +393 -0
  9. amazon_ads_mcp/auth/providers/example_auth0.py.example +216 -0
  10. amazon_ads_mcp/auth/providers/openbridge.py +512 -0
  11. amazon_ads_mcp/auth/registry.py +146 -0
  12. amazon_ads_mcp/auth/secure_token_store.py +297 -0
  13. amazon_ads_mcp/auth/token_store.py +723 -0
  14. amazon_ads_mcp/config/__init__.py +5 -0
  15. amazon_ads_mcp/config/sampling.py +111 -0
  16. amazon_ads_mcp/config/settings.py +366 -0
  17. amazon_ads_mcp/exceptions.py +314 -0
  18. amazon_ads_mcp/middleware/__init__.py +11 -0
  19. amazon_ads_mcp/middleware/authentication.py +1474 -0
  20. amazon_ads_mcp/middleware/caching.py +177 -0
  21. amazon_ads_mcp/middleware/oauth.py +175 -0
  22. amazon_ads_mcp/middleware/sampling.py +112 -0
  23. amazon_ads_mcp/models/__init__.py +320 -0
  24. amazon_ads_mcp/models/amc_models.py +837 -0
  25. amazon_ads_mcp/models/api_responses.py +847 -0
  26. amazon_ads_mcp/models/base_models.py +215 -0
  27. amazon_ads_mcp/models/builtin_responses.py +496 -0
  28. amazon_ads_mcp/models/dsp_models.py +556 -0
  29. amazon_ads_mcp/models/stores_brands.py +610 -0
  30. amazon_ads_mcp/server/__init__.py +6 -0
  31. amazon_ads_mcp/server/__main__.py +6 -0
  32. amazon_ads_mcp/server/builtin_prompts.py +269 -0
  33. amazon_ads_mcp/server/builtin_tools.py +962 -0
  34. amazon_ads_mcp/server/file_routes.py +547 -0
  35. amazon_ads_mcp/server/html_templates.py +149 -0
  36. amazon_ads_mcp/server/mcp_server.py +327 -0
  37. amazon_ads_mcp/server/openapi_utils.py +158 -0
  38. amazon_ads_mcp/server/sampling_handler.py +251 -0
  39. amazon_ads_mcp/server/server_builder.py +751 -0
  40. amazon_ads_mcp/server/sidecar_loader.py +178 -0
  41. amazon_ads_mcp/server/transform_executor.py +827 -0
  42. amazon_ads_mcp/tools/__init__.py +22 -0
  43. amazon_ads_mcp/tools/cache_management.py +105 -0
  44. amazon_ads_mcp/tools/download_tools.py +267 -0
  45. amazon_ads_mcp/tools/identity.py +236 -0
  46. amazon_ads_mcp/tools/oauth.py +598 -0
  47. amazon_ads_mcp/tools/profile.py +150 -0
  48. amazon_ads_mcp/tools/profile_listing.py +285 -0
  49. amazon_ads_mcp/tools/region.py +320 -0
  50. amazon_ads_mcp/tools/region_identity.py +175 -0
  51. amazon_ads_mcp/utils/__init__.py +6 -0
  52. amazon_ads_mcp/utils/async_compat.py +215 -0
  53. amazon_ads_mcp/utils/errors.py +452 -0
  54. amazon_ads_mcp/utils/export_content_type_resolver.py +249 -0
  55. amazon_ads_mcp/utils/export_download_handler.py +579 -0
  56. amazon_ads_mcp/utils/header_resolver.py +81 -0
  57. amazon_ads_mcp/utils/http/__init__.py +56 -0
  58. amazon_ads_mcp/utils/http/circuit_breaker.py +127 -0
  59. amazon_ads_mcp/utils/http/client_manager.py +329 -0
  60. amazon_ads_mcp/utils/http/request.py +207 -0
  61. amazon_ads_mcp/utils/http/resilience.py +512 -0
  62. amazon_ads_mcp/utils/http/resilient_client.py +195 -0
  63. amazon_ads_mcp/utils/http/retry.py +76 -0
  64. amazon_ads_mcp/utils/http_client.py +873 -0
  65. amazon_ads_mcp/utils/media/__init__.py +21 -0
  66. amazon_ads_mcp/utils/media/negotiator.py +243 -0
  67. amazon_ads_mcp/utils/media/types.py +199 -0
  68. amazon_ads_mcp/utils/openapi/__init__.py +16 -0
  69. amazon_ads_mcp/utils/openapi/json.py +55 -0
  70. amazon_ads_mcp/utils/openapi/loader.py +263 -0
  71. amazon_ads_mcp/utils/openapi/refs.py +46 -0
  72. amazon_ads_mcp/utils/region_config.py +200 -0
  73. amazon_ads_mcp/utils/response_wrapper.py +171 -0
  74. amazon_ads_mcp/utils/sampling_helpers.py +156 -0
  75. amazon_ads_mcp/utils/sampling_wrapper.py +173 -0
  76. amazon_ads_mcp/utils/security.py +630 -0
  77. amazon_ads_mcp/utils/tool_naming.py +137 -0
  78. amazon_ads_mcp-0.2.7.dist-info/METADATA +664 -0
  79. amazon_ads_mcp-0.2.7.dist-info/RECORD +82 -0
  80. amazon_ads_mcp-0.2.7.dist-info/WHEEL +4 -0
  81. amazon_ads_mcp-0.2.7.dist-info/entry_points.txt +3 -0
  82. 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