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,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)
|