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,5 @@
1
+ """Configuration module for Amazon Ads API MCP server.
2
+
3
+ This module contains configuration management for the Amazon Ads MCP server,
4
+ including settings, environment variables, and configuration validation.
5
+ """
@@ -0,0 +1,111 @@
1
+ """Sampling configuration for server-side LLM fallback."""
2
+
3
+ import logging
4
+ import os
5
+ from typing import Optional
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class SamplingConfig(BaseModel):
13
+ """Configuration for optional server-side sampling."""
14
+
15
+ enabled: bool = Field(
16
+ default=False, description="Enable server-side sampling fallback"
17
+ )
18
+ provider: str = Field(default="openai", description="LLM provider (openai or none)")
19
+ model: str = Field(default="gpt-4o-mini", description="Model to use for sampling")
20
+ api_key: Optional[str] = Field(default=None, description="API key for the provider")
21
+ base_url: Optional[str] = Field(
22
+ default=None,
23
+ description="Optional base URL for OpenAI-compatible endpoints",
24
+ )
25
+ temperature: float = Field(default=0.2, description="Sampling temperature")
26
+ max_tokens: int = Field(default=400, description="Maximum tokens for sampling")
27
+ timeout_ms: int = Field(default=8000, description="Timeout in milliseconds")
28
+
29
+ @classmethod
30
+ def from_environment(cls) -> "SamplingConfig":
31
+ """Load configuration from environment variables."""
32
+ config = cls()
33
+
34
+ # Check if sampling is enabled
35
+ sampling_enabled = os.getenv("SAMPLING_ENABLED")
36
+ if sampling_enabled and sampling_enabled.lower() in (
37
+ "true",
38
+ "1",
39
+ "yes",
40
+ ):
41
+ config.enabled = True
42
+ logger.info("Server-side sampling is ENABLED")
43
+
44
+ # Load provider settings
45
+ if provider := os.getenv("SAMPLING_PROVIDER"):
46
+ config.provider = provider.lower()
47
+
48
+ if model := os.getenv("SAMPLING_MODEL"):
49
+ config.model = model
50
+
51
+ # Use standard OpenAI API key
52
+ config.api_key = os.getenv("OPENAI_API_KEY")
53
+
54
+ if base_url := os.getenv("SAMPLING_BASE_URL"):
55
+ config.base_url = base_url
56
+
57
+ # Load sampling parameters
58
+ if temp := os.getenv("SAMPLING_TEMPERATURE"):
59
+ try:
60
+ config.temperature = float(temp)
61
+ except ValueError:
62
+ logger.warning(
63
+ f"Invalid temperature value: {temp}, using default {config.temperature}"
64
+ )
65
+
66
+ if max_tok := os.getenv("SAMPLING_MAX_TOKENS"):
67
+ try:
68
+ config.max_tokens = int(max_tok)
69
+ except ValueError:
70
+ logger.warning(
71
+ f"Invalid max_tokens value: {max_tok}, using default {config.max_tokens}"
72
+ )
73
+
74
+ if timeout := os.getenv("SAMPLING_TIMEOUT_MS"):
75
+ try:
76
+ config.timeout_ms = int(timeout)
77
+ except ValueError:
78
+ logger.warning(
79
+ f"Invalid timeout_ms value: {timeout}, using default {config.timeout_ms}"
80
+ )
81
+
82
+ return config
83
+
84
+ def is_valid(self) -> bool:
85
+ """Check if the configuration is valid for creating a sampling handler."""
86
+ if not self.enabled:
87
+ return False
88
+
89
+ if self.provider == "none":
90
+ return False
91
+
92
+ if self.provider == "openai" and not self.api_key:
93
+ logger.warning(
94
+ "Sampling enabled but no API key provided. Sampling will be disabled."
95
+ )
96
+ return False
97
+
98
+ return True
99
+
100
+ def log_status(self):
101
+ """Log the current sampling configuration status."""
102
+ if not self.enabled:
103
+ logger.info("Server-side sampling: DISABLED (default)")
104
+ elif not self.is_valid():
105
+ logger.warning("Server-side sampling: DISABLED (invalid configuration)")
106
+ else:
107
+ logger.info(
108
+ "Server-side sampling: ENABLED (provider=%s, model=%s, fallback-only)",
109
+ self.provider,
110
+ self.model,
111
+ )
@@ -0,0 +1,366 @@
1
+ """Configuration settings for Amazon Ads API MCP server.
2
+
3
+ This module defines the configuration settings for the Amazon Ads MCP server,
4
+ including authentication methods, API endpoints, and server configuration.
5
+ Settings are loaded from environment variables and .env files.
6
+ """
7
+
8
+ from typing import Literal, Optional
9
+
10
+ from pydantic import AliasChoices, Field, field_validator
11
+ from pydantic_settings import BaseSettings, SettingsConfigDict
12
+
13
+ from ..utils.region_config import RegionConfig
14
+
15
+
16
+ class Settings(BaseSettings):
17
+ """Application settings loaded from environment variables.
18
+
19
+ Manages all configuration settings for the Amazon Ads MCP server.
20
+ Supports both direct Amazon Ads API authentication and OpenBridge
21
+ integration. Settings can be configured via environment variables
22
+ or .env files.
23
+
24
+ :param auth_method: Authentication method to use (direct/openbridge)
25
+ :type auth_method: Literal["direct", "openbridge"]
26
+ :param amazon_ads_client_id: Amazon Ads API Client ID for direct auth
27
+ :type amazon_ads_client_id: Optional[str]
28
+ :param amazon_ads_client_secret: Amazon Ads API Client Secret for direct auth
29
+ :type amazon_ads_client_secret: Optional[str]
30
+ :param amazon_ads_refresh_token: Amazon Ads API Refresh Token for direct auth
31
+ :type amazon_ads_refresh_token: Optional[str]
32
+ :param openbridge_refresh_token: OpenBridge API key (aka refresh token)
33
+ :type openbridge_refresh_token: Optional[str]
34
+ :param openbridge_remote_identity_id: OpenBridge remote identity ID
35
+ :type openbridge_remote_identity_id: Optional[str]
36
+ :param amazon_ads_region: Amazon Ads API region (na/eu/fe)
37
+ :type amazon_ads_region: Literal["na", "eu", "fe"]
38
+ :param amazon_ads_api_base_url: Base URL for Amazon Ads API
39
+ :type amazon_ads_api_base_url: str
40
+ :param amazon_ads_sandbox_mode: Enable sandbox mode for testing
41
+ :type amazon_ads_sandbox_mode: bool
42
+ :param mcp_server_name: Name of the MCP server
43
+ :type mcp_server_name: str
44
+ :param mcp_server_host: Host for the MCP server
45
+ :type mcp_server_host: str
46
+ :param mcp_server_port: Port for the MCP server
47
+ :type mcp_server_port: int
48
+ :param log_level: Logging level for the application
49
+ :type log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
50
+ """
51
+
52
+ model_config = SettingsConfigDict(
53
+ env_file=".env",
54
+ env_file_encoding="utf-8",
55
+ case_sensitive=False,
56
+ extra="ignore", # Ignore extra fields in .env file
57
+ )
58
+
59
+ # Authentication method
60
+ auth_method: Literal["direct", "openbridge"] = Field(
61
+ "openbridge", description="Authentication method to use"
62
+ )
63
+
64
+ # Direct Amazon Ads API Configuration (BYOA - Bring Your Own API)
65
+ # These match the standard Amazon Ads API environment variables
66
+ ad_api_client_id: Optional[str] = Field(
67
+ None,
68
+ alias="AMAZON_AD_API_CLIENT_ID",
69
+ description="Amazon Ads API Client ID (for direct auth)",
70
+ )
71
+ ad_api_client_secret: Optional[str] = Field(
72
+ None,
73
+ alias="AMAZON_AD_API_CLIENT_SECRET",
74
+ description="Amazon Ads API Client Secret (for direct auth)",
75
+ )
76
+ ad_api_refresh_token: Optional[str] = Field(
77
+ None,
78
+ alias="AMAZON_AD_API_REFRESH_TOKEN",
79
+ description="Amazon Ads API Refresh Token (for direct auth)",
80
+ )
81
+ ad_api_profile_id: Optional[str] = Field(
82
+ None,
83
+ alias="AMAZON_AD_API_PROFILE_ID",
84
+ description="Amazon Ads Profile ID (for direct auth)",
85
+ )
86
+
87
+ # Legacy field names for backward compatibility
88
+ amazon_ads_client_id: Optional[str] = Field(
89
+ None,
90
+ description="Amazon Ads API Client ID (deprecated, use AMAZON_AD_API_CLIENT_ID)",
91
+ )
92
+ amazon_ads_client_secret: Optional[str] = Field(
93
+ None,
94
+ description="Amazon Ads API Client Secret (deprecated, use AMAZON_AD_API_CLIENT_SECRET)",
95
+ )
96
+ amazon_ads_refresh_token: Optional[str] = Field(
97
+ None,
98
+ description="Amazon Ads API Refresh Token (deprecated, use AMAZON_AD_API_REFRESH_TOKEN)",
99
+ )
100
+
101
+ # Openbridge Configuration
102
+ openbridge_refresh_token: Optional[str] = Field(
103
+ None,
104
+ validation_alias=AliasChoices("OPENBRIDGE_REFRESH_TOKEN", "OPENBRIDGE_API_KEY"),
105
+ description="OpenBridge API key (aka refresh token)",
106
+ )
107
+ openbridge_remote_identity_id: Optional[str] = Field(
108
+ None, description="Openbridge Remote Identity ID for Amazon Ads"
109
+ )
110
+ amazon_ads_region: Literal["na", "eu", "fe"] = Field(
111
+ "na", description="Amazon Ads API Region"
112
+ )
113
+
114
+ # API Configuration
115
+ amazon_ads_api_base_url: str = Field(
116
+ "https://advertising-api.amazon.com",
117
+ description="Amazon Ads API Base URL",
118
+ )
119
+ amazon_ads_sandbox_mode: bool = Field(
120
+ False, description="Enable sandbox mode for testing"
121
+ )
122
+
123
+ # MCP Server Configuration
124
+ mcp_server_name: str = Field("amazon-ads-api", description="MCP Server Name")
125
+ mcp_server_host: str = Field("localhost", description="MCP Server Host")
126
+ mcp_server_port: int = Field(8000, description="MCP Server Port")
127
+
128
+ # Runtime configuration (set by CLI or Docker)
129
+ port: Optional[int] = Field(
130
+ None, description="HTTP server port (from PORT env var or CLI)"
131
+ )
132
+
133
+ # Logging
134
+ log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = Field(
135
+ "INFO", description="Logging level"
136
+ )
137
+
138
+ # FastMCP Configuration
139
+ fastmcp_use_experimental_parser: bool = Field(
140
+ True,
141
+ description="Enable FastMCP experimental OpenAPI parser for better performance",
142
+ )
143
+
144
+ # OAuth Configuration (optional, for web-based authentication flows)
145
+ oauth_client_id: Optional[str] = Field(
146
+ None,
147
+ alias="OAUTH_CLIENT_ID",
148
+ description="OAuth Client ID for web authentication flow",
149
+ )
150
+ oauth_client_secret: Optional[str] = Field(
151
+ None,
152
+ alias="OAUTH_CLIENT_SECRET",
153
+ description="OAuth Client Secret for web authentication flow",
154
+ )
155
+ oauth_redirect_uri: Optional[str] = Field(
156
+ None,
157
+ alias="OAUTH_REDIRECT_URI",
158
+ description="OAuth Redirect URI for web authentication flow",
159
+ )
160
+
161
+ # Sampling Configuration (optional, for testing)
162
+ enable_sampling: bool = Field(
163
+ False,
164
+ alias="SAMPLING_ENABLED",
165
+ description="Enable request/response sampling for testing",
166
+ )
167
+ sampling_config_path: Optional[str] = Field(
168
+ None,
169
+ alias="SAMPLING_CONFIG_PATH",
170
+ description="Path to sampling configuration file",
171
+ )
172
+ default_sampling_rate: float = Field(
173
+ 0.1,
174
+ alias="DEFAULT_SAMPLING_RATE",
175
+ description="Default sampling rate (0.0-1.0)",
176
+ )
177
+
178
+ # Response Caching Configuration
179
+ enable_response_caching: bool = Field(
180
+ False,
181
+ alias="ENABLE_RESPONSE_CACHING",
182
+ description="Enable response caching for safe read-only tools",
183
+ )
184
+
185
+ # File Download Configuration
186
+ download_auth_token: Optional[str] = Field(
187
+ None,
188
+ validation_alias=AliasChoices(
189
+ "AMAZON_ADS_DOWNLOAD_AUTH_TOKEN", "DOWNLOAD_AUTH_TOKEN"
190
+ ),
191
+ description="Bearer token for authenticating download requests (optional)",
192
+ )
193
+ download_max_file_size: int = Field(
194
+ 512 * 1024 * 1024, # 512MB default
195
+ alias="AMAZON_ADS_DOWNLOAD_MAX_FILE_SIZE",
196
+ description="Maximum file size in bytes for downloads (default 512MB)",
197
+ )
198
+ download_allowed_extensions: Optional[str] = Field(
199
+ None,
200
+ alias="AMAZON_ADS_DOWNLOAD_ALLOWED_EXTENSIONS",
201
+ description="Comma-separated list of allowed file extensions (e.g., '.json,.csv,.gz')",
202
+ )
203
+
204
+ @field_validator("auth_method")
205
+ @classmethod
206
+ def auto_detect_auth_method(cls, v: str, info) -> str:
207
+ """Auto-detect authentication method based on available credentials.
208
+
209
+ If direct Amazon Ads API credentials are provided, automatically
210
+ switch to 'direct' auth method. Prioritizes direct credentials
211
+ over OpenBridge when both are available.
212
+
213
+ :param v: The original auth method value
214
+ :type v: str
215
+ :param info: Validation info containing other field values
216
+ :type info: Any
217
+ :return: Detected or original authentication method
218
+ :rtype: str
219
+ """
220
+ # Get the credential values
221
+ client_id = info.data.get("ad_api_client_id") or info.data.get(
222
+ "amazon_ads_client_id"
223
+ )
224
+ client_secret = info.data.get("ad_api_client_secret") or info.data.get(
225
+ "amazon_ads_client_secret"
226
+ )
227
+ refresh_token = info.data.get("ad_api_refresh_token") or info.data.get(
228
+ "amazon_ads_refresh_token"
229
+ )
230
+
231
+ # Check for direct API credentials (all three must be present and non-empty)
232
+ has_direct_creds = all(
233
+ [
234
+ client_id and client_id.strip(),
235
+ client_secret and client_secret.strip(),
236
+ refresh_token and refresh_token.strip(),
237
+ ]
238
+ )
239
+
240
+ if has_direct_creds:
241
+ return "direct"
242
+
243
+ # Check for OpenBridge credentials
244
+ has_openbridge = info.data.get("openbridge_refresh_token")
245
+ if has_openbridge and has_openbridge.strip():
246
+ return "openbridge"
247
+
248
+ # Check for partial direct credentials to provide helpful error
249
+ has_partial_direct = any([client_id, client_secret, refresh_token])
250
+ if has_partial_direct and not has_openbridge:
251
+ missing = []
252
+ if not client_id or not client_id.strip():
253
+ missing.append("AMAZON_AD_API_CLIENT_ID")
254
+ if not client_secret or not client_secret.strip():
255
+ missing.append("AMAZON_AD_API_CLIENT_SECRET")
256
+ if not refresh_token or not refresh_token.strip():
257
+ missing.append("AMAZON_AD_API_REFRESH_TOKEN")
258
+
259
+ if missing:
260
+ # Return the original value, the provider setup will give detailed error
261
+ return v
262
+
263
+ return v # Return the original value if no credentials detected
264
+
265
+ @field_validator("amazon_ads_api_base_url")
266
+ @classmethod
267
+ def validate_api_base_url(cls, v: str, info) -> str:
268
+ """Adjust API URL based on sandbox mode.
269
+
270
+ Automatically modifies the API base URL to use the test endpoint
271
+ when sandbox mode is enabled.
272
+
273
+ :param v: The original API base URL
274
+ :type v: str
275
+ :param info: Validation info containing other field values
276
+ :type info: Any
277
+ :return: Modified API base URL for sandbox mode if enabled
278
+ :rtype: str
279
+ """
280
+ if info.data.get("amazon_ads_sandbox_mode"):
281
+ return v.replace("advertising-api", "advertising-api-test")
282
+ return v
283
+
284
+ @property
285
+ def effective_client_id(self) -> Optional[str]:
286
+ """Get the effective client ID (new or legacy).
287
+
288
+ Returns the client ID from either the new AMAZON_AD_API_CLIENT_ID
289
+ environment variable or the legacy amazon_ads_client_id.
290
+
291
+ :return: Effective client ID or None
292
+ :rtype: Optional[str]
293
+ """
294
+ return self.ad_api_client_id or self.amazon_ads_client_id
295
+
296
+ @property
297
+ def effective_client_secret(self) -> Optional[str]:
298
+ """Get the effective client secret (new or legacy).
299
+
300
+ Returns the client secret from either the new AMAZON_AD_API_CLIENT_SECRET
301
+ environment variable or the legacy amazon_ads_client_secret.
302
+
303
+ :return: Effective client secret or None
304
+ :rtype: Optional[str]
305
+ """
306
+ return self.ad_api_client_secret or self.amazon_ads_client_secret
307
+
308
+ @property
309
+ def effective_refresh_token(self) -> Optional[str]:
310
+ """Get the effective refresh token (new or legacy).
311
+
312
+ Returns the refresh token from either the new AMAZON_AD_API_REFRESH_TOKEN
313
+ environment variable or the legacy amazon_ads_refresh_token.
314
+
315
+ NOTE: Using refresh tokens via environment variables is deprecated.
316
+ Prefer OAuth flow or secure token storage for production use.
317
+
318
+ :return: Effective refresh token or None
319
+ :rtype: Optional[str]
320
+ """
321
+ token = self.ad_api_refresh_token or self.amazon_ads_refresh_token
322
+ if token:
323
+ import warnings
324
+
325
+ warnings.warn(
326
+ "Using refresh tokens via environment variables is deprecated and insecure. "
327
+ "Please use OAuth flow or secure token storage instead.",
328
+ DeprecationWarning,
329
+ stacklevel=2,
330
+ )
331
+ return token
332
+
333
+ @property
334
+ def effective_profile_id(self) -> Optional[str]:
335
+ """Get the effective profile ID.
336
+
337
+ Returns the profile ID from the AMAZON_AD_API_PROFILE_ID
338
+ environment variable.
339
+
340
+ :return: Profile ID or None
341
+ :rtype: Optional[str]
342
+ """
343
+ return self.ad_api_profile_id
344
+
345
+ @property
346
+ def region_endpoint(self) -> str:
347
+ """Get the region-specific endpoint.
348
+
349
+ Returns the appropriate Amazon Ads API endpoint based on the
350
+ configured region and sandbox mode setting.
351
+
352
+ :return: Region-specific API endpoint URL
353
+ :rtype: str
354
+ """
355
+ base = RegionConfig.get_api_endpoint(self.amazon_ads_region)
356
+ if self.amazon_ads_sandbox_mode:
357
+ base = base.replace("advertising-api", "advertising-api-test")
358
+ return base
359
+
360
+
361
+ settings = Settings()
362
+ """Global settings instance for the Amazon Ads MCP server.
363
+
364
+ This instance is created once and used throughout the application
365
+ to access configuration settings.
366
+ """