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,178 @@
1
+ """Sidecar loader for FastMCP tool transformations.
2
+
3
+ This module handles loading and applying manifest/transform sidecar files
4
+ to FastMCP servers. It provides functionality to attach input/output
5
+ transforms and call transforms to existing tools based on declarative
6
+ configuration files.
7
+
8
+ The sidecar system allows for tool behavior modification without changing
9
+ the underlying FastMCP server implementation.
10
+ """
11
+
12
+ import json
13
+ import logging
14
+ from pathlib import Path
15
+ from typing import Any, Dict, Optional
16
+
17
+ from ..utils.tool_naming import get_tools
18
+ from .transform_executor import DeclarativeTransformExecutor
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def _json_load(path: Path) -> Dict[str, Any]:
24
+ """Load and parse a JSON file from the given path.
25
+
26
+ :param path: Path to the JSON file to load
27
+ :type path: Path
28
+ :return: Parsed JSON content as a dictionary
29
+ :rtype: Dict[str, Any]
30
+ """
31
+ return json.loads(path.read_text(encoding="utf-8"))
32
+
33
+
34
+ def resolve_tool_name(
35
+ match: Dict[str, Any], manifest: Dict[str, Any], tools: Dict[str, Any]
36
+ ) -> Optional[str]:
37
+ """Resolve the actual tool name from operation metadata.
38
+
39
+ Attempts to find the correct tool name by matching operation ID,
40
+ method+path combinations, or using manifest preferences. Handles
41
+ various naming conventions used by different OpenAPI generators.
42
+
43
+ :param match: Operation matching criteria containing operationId,
44
+ method, and path
45
+ :type match: Dict[str, Any]
46
+ :param manifest: Manifest file containing tool preferences and
47
+ metadata
48
+ :type manifest: Dict[str, Any]
49
+ :param tools: Available tools dictionary mapping names to
50
+ implementations
51
+ :type tools: Dict[str, Any]
52
+ :return: Resolved tool name if found, None otherwise
53
+ :rtype: Optional[str]
54
+ """
55
+ op_id = (match or {}).get("operationId")
56
+ method = (match or {}).get("method")
57
+ path = (match or {}).get("path")
58
+ if not op_id:
59
+ # Try method+path based key used by some generators
60
+ if method and path:
61
+ key = f"{str(method).upper()}_{path.strip('/').replace('/', '_')}"
62
+ if key in tools:
63
+ return key
64
+ return None
65
+
66
+ # map preferred name from manifest
67
+ for t in manifest.get("tools", []):
68
+ if t.get("operationId") == op_id:
69
+ preferred = t.get("preferred_name") or op_id
70
+ # ensure tool exists, else try to find by operationId
71
+ if preferred in tools:
72
+ return preferred
73
+ # try plain operationId
74
+ if op_id in tools:
75
+ return op_id
76
+ # try prefix variants: FastMCP often prefixes tool names with server name
77
+ for prefix in ("sp_", "sd_", "sb_", "dsp_"):
78
+ prefixed = f"{prefix}{op_id}"
79
+ if prefixed in tools:
80
+ return prefixed
81
+ for name in tools.keys():
82
+ if name.endswith(f"_{op_id}") or name.endswith(op_id):
83
+ return name
84
+ return None
85
+
86
+
87
+ async def apply_sidecars(server, spec_path: Path) -> None:
88
+ """Load manifest/transform sidecars and attach transforms when supported.
89
+
90
+ This function loads manifest and transform sidecar files associated
91
+ with an OpenAPI specification and applies tool transformations to
92
+ the FastMCP server. It's designed to be a safe no-op if the server
93
+ doesn't support tool transformations.
94
+
95
+ The function handles version compatibility checks and provides
96
+ detailed metrics about the transformation process. It supports
97
+ both newer FastMCP versions with call_transform support and older
98
+ versions without it.
99
+
100
+ :param server: FastMCP server instance to apply transforms to
101
+ :type server: Any
102
+ :param spec_path: Path to the OpenAPI specification file
103
+ :type spec_path: Path
104
+ :raises Exception: May raise exceptions during transform loading
105
+ or application, but these are logged and don't
106
+ stop the process
107
+ """
108
+ transform_path = spec_path.with_suffix(".transform.json")
109
+ manifest_path = spec_path.with_suffix(".manifest.json")
110
+ if not transform_path.exists() or not manifest_path.exists():
111
+ return
112
+
113
+ try:
114
+ manifest = _json_load(manifest_path)
115
+ transform = _json_load(transform_path)
116
+ except Exception as e:
117
+ logger.warning("Failed to load sidecars for %s: %s", spec_path.name, e)
118
+ return
119
+
120
+ transform_tool = getattr(server, "transform_tool", None)
121
+ if not callable(transform_tool):
122
+ logger.info("Tool transformations not supported by FastMCP runtime; skipping")
123
+ return
124
+
125
+ ex = DeclarativeTransformExecutor(
126
+ namespace=manifest.get("namespace") or spec_path.stem, rules=transform
127
+ )
128
+
129
+ # Version compatibility check
130
+ tx_version = transform.get("version", "1.0")
131
+ if str(tx_version).split(".")[0] != str(ex.version).split(".")[0]:
132
+ logger.warning(
133
+ "Sidecar version %s differs from executor %s",
134
+ tx_version,
135
+ ex.version,
136
+ )
137
+
138
+ tools_map = await get_tools(server)
139
+ metrics = {
140
+ "total_tools": len(tools_map),
141
+ "transforms_attempted": 0,
142
+ "transforms_applied": 0,
143
+ "transforms_failed": 0,
144
+ }
145
+ for rule in transform.get("tools", []):
146
+ metrics["transforms_attempted"] += 1
147
+ tool_name = resolve_tool_name(rule.get("match", {}), manifest, tools_map)
148
+ if not tool_name:
149
+ continue
150
+ input_tx = ex.create_input_transform(rule)
151
+ output_tx = ex.create_output_transform(rule)
152
+ args_cfg = (rule.get("args") or {}).get("expose")
153
+ try:
154
+ call_tx = ex.create_call_transform(rule)
155
+ # Try to attach call_transform if supported
156
+ try:
157
+ transform_tool(
158
+ tool_name,
159
+ input_transform=input_tx,
160
+ output_transform=output_tx,
161
+ call_transform=call_tx,
162
+ arg_schema=args_cfg,
163
+ )
164
+ except TypeError:
165
+ # Older FastMCP without call_transform support
166
+ transform_tool(
167
+ tool_name,
168
+ input_transform=input_tx,
169
+ output_transform=output_tx,
170
+ arg_schema=args_cfg,
171
+ )
172
+ metrics["transforms_applied"] += 1
173
+ logger.debug("Attached transform to tool %s", tool_name)
174
+ except Exception as e:
175
+ metrics["transforms_failed"] += 1
176
+ logger.warning("Failed to attach transform to %s: %s", tool_name, e)
177
+
178
+ logger.info("Transform metrics: %s", metrics)