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,215 @@
|
|
|
1
|
+
"""Async compatibility utilities without monkey-patching."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from contextvars import ContextVar
|
|
6
|
+
from typing import Any, Callable, Optional, TypeVar
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
|
|
12
|
+
# Context variable to track if we created the loop
|
|
13
|
+
_loop_creator: ContextVar[bool] = ContextVar("loop_creator", default=False)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CompatibleEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
|
|
17
|
+
"""
|
|
18
|
+
Event loop policy that provides backwards compatibility.
|
|
19
|
+
|
|
20
|
+
This policy creates event loops when needed without monkey-patching
|
|
21
|
+
global asyncio functions.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def get_event_loop(self) -> asyncio.AbstractEventLoop:
|
|
25
|
+
"""
|
|
26
|
+
Get the current event loop, creating one if necessary.
|
|
27
|
+
|
|
28
|
+
This provides compatibility for code that expects get_event_loop()
|
|
29
|
+
to always return a loop.
|
|
30
|
+
"""
|
|
31
|
+
try:
|
|
32
|
+
# Try the normal path first
|
|
33
|
+
return super().get_event_loop()
|
|
34
|
+
except RuntimeError as e:
|
|
35
|
+
if "There is no current event loop" in str(e):
|
|
36
|
+
# Create a new loop for compatibility
|
|
37
|
+
loop = self.new_event_loop()
|
|
38
|
+
self.set_event_loop(loop)
|
|
39
|
+
_loop_creator.set(True)
|
|
40
|
+
logger.debug("Created new event loop for compatibility")
|
|
41
|
+
return loop
|
|
42
|
+
raise
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def ensure_event_loop() -> asyncio.AbstractEventLoop:
|
|
46
|
+
"""
|
|
47
|
+
Ensure an event loop exists, creating one if necessary.
|
|
48
|
+
|
|
49
|
+
This is a safer alternative to monkey-patching get_event_loop().
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The current or newly created event loop.
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
# First try to get the running loop
|
|
56
|
+
return asyncio.get_running_loop()
|
|
57
|
+
except RuntimeError:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
# Try to get existing loop
|
|
62
|
+
loop = asyncio.get_event_loop()
|
|
63
|
+
if loop.is_closed():
|
|
64
|
+
raise RuntimeError("Event loop is closed")
|
|
65
|
+
return loop
|
|
66
|
+
except RuntimeError:
|
|
67
|
+
# No loop exists, create one
|
|
68
|
+
loop = asyncio.new_event_loop()
|
|
69
|
+
asyncio.set_event_loop(loop)
|
|
70
|
+
_loop_creator.set(True)
|
|
71
|
+
logger.debug("Created new event loop")
|
|
72
|
+
return loop
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def run_async_in_sync(coro_func: Callable[..., Any], *args, **kwargs) -> Any:
|
|
76
|
+
"""
|
|
77
|
+
Run an async function from synchronous code safely.
|
|
78
|
+
|
|
79
|
+
This handles the complexity of running async code from sync contexts
|
|
80
|
+
without creating nested loops or monkey-patching.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
coro_func: The async function to run
|
|
84
|
+
*args: Positional arguments for the function
|
|
85
|
+
**kwargs: Keyword arguments for the function
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
The result of the async function
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
# Check if we're already in an async context
|
|
92
|
+
loop = asyncio.get_running_loop()
|
|
93
|
+
# We're in an async context, can't use run_until_complete
|
|
94
|
+
raise RuntimeError(
|
|
95
|
+
"Cannot run async function synchronously from within an async context. "
|
|
96
|
+
"Use 'await' instead."
|
|
97
|
+
)
|
|
98
|
+
except RuntimeError:
|
|
99
|
+
# No running loop, safe to create one
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
# Check if there's an existing loop
|
|
103
|
+
try:
|
|
104
|
+
loop = asyncio.get_event_loop()
|
|
105
|
+
if loop.is_running():
|
|
106
|
+
raise RuntimeError(
|
|
107
|
+
"Cannot run async function synchronously while event loop is running"
|
|
108
|
+
)
|
|
109
|
+
except RuntimeError:
|
|
110
|
+
# No loop exists, we'll create one
|
|
111
|
+
loop = asyncio.new_event_loop()
|
|
112
|
+
asyncio.set_event_loop(loop)
|
|
113
|
+
created_loop = True
|
|
114
|
+
else:
|
|
115
|
+
created_loop = False
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
# Run the coroutine
|
|
119
|
+
coro = coro_func(*args, **kwargs)
|
|
120
|
+
return loop.run_until_complete(coro)
|
|
121
|
+
finally:
|
|
122
|
+
# Clean up if we created the loop
|
|
123
|
+
if created_loop:
|
|
124
|
+
try:
|
|
125
|
+
# Clean up any pending tasks
|
|
126
|
+
pending = asyncio.all_tasks(loop)
|
|
127
|
+
for task in pending:
|
|
128
|
+
task.cancel()
|
|
129
|
+
|
|
130
|
+
# Run loop until tasks are cancelled
|
|
131
|
+
if pending:
|
|
132
|
+
loop.run_until_complete(
|
|
133
|
+
asyncio.gather(*pending, return_exceptions=True)
|
|
134
|
+
)
|
|
135
|
+
finally:
|
|
136
|
+
loop.close()
|
|
137
|
+
asyncio.set_event_loop(None)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class AsyncContextManager:
|
|
141
|
+
"""
|
|
142
|
+
Helper for managing async operations in mixed sync/async code.
|
|
143
|
+
|
|
144
|
+
This provides a clean way to handle async operations without
|
|
145
|
+
monkey-patching or creating event loop issues.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
def __init__(self):
|
|
149
|
+
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
|
150
|
+
self._created_loop = False
|
|
151
|
+
|
|
152
|
+
def __enter__(self):
|
|
153
|
+
"""Enter the context, ensuring an event loop exists."""
|
|
154
|
+
try:
|
|
155
|
+
self._loop = asyncio.get_running_loop()
|
|
156
|
+
# Already in async context
|
|
157
|
+
return self
|
|
158
|
+
except RuntimeError:
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
self._loop = asyncio.get_event_loop()
|
|
163
|
+
if self._loop.is_closed():
|
|
164
|
+
raise RuntimeError("Event loop is closed")
|
|
165
|
+
except RuntimeError:
|
|
166
|
+
# Create new loop
|
|
167
|
+
self._loop = asyncio.new_event_loop()
|
|
168
|
+
asyncio.set_event_loop(self._loop)
|
|
169
|
+
self._created_loop = True
|
|
170
|
+
|
|
171
|
+
return self
|
|
172
|
+
|
|
173
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
174
|
+
"""Exit the context, cleaning up if we created the loop."""
|
|
175
|
+
if self._created_loop and self._loop:
|
|
176
|
+
try:
|
|
177
|
+
# Clean up pending tasks
|
|
178
|
+
pending = asyncio.all_tasks(self._loop)
|
|
179
|
+
for task in pending:
|
|
180
|
+
task.cancel()
|
|
181
|
+
|
|
182
|
+
if pending:
|
|
183
|
+
self._loop.run_until_complete(
|
|
184
|
+
asyncio.gather(*pending, return_exceptions=True)
|
|
185
|
+
)
|
|
186
|
+
finally:
|
|
187
|
+
self._loop.close()
|
|
188
|
+
asyncio.set_event_loop(None)
|
|
189
|
+
self._loop = None
|
|
190
|
+
|
|
191
|
+
def run(self, coro):
|
|
192
|
+
"""Run a coroutine in the managed loop."""
|
|
193
|
+
if not self._loop:
|
|
194
|
+
raise RuntimeError("AsyncContextManager not entered")
|
|
195
|
+
|
|
196
|
+
if self._loop.is_running():
|
|
197
|
+
# Can't use run_until_complete in running loop
|
|
198
|
+
raise RuntimeError("Cannot run coroutine in already running loop")
|
|
199
|
+
|
|
200
|
+
return self._loop.run_until_complete(coro)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def install_compatibility_policy():
|
|
204
|
+
"""
|
|
205
|
+
Install the compatible event loop policy.
|
|
206
|
+
|
|
207
|
+
This should be called once at application startup instead of
|
|
208
|
+
monkey-patching asyncio functions.
|
|
209
|
+
"""
|
|
210
|
+
import os
|
|
211
|
+
|
|
212
|
+
# Only install if compatibility mode is enabled
|
|
213
|
+
if os.getenv("MCP_ASYNC_COMPAT", "").lower() in ("1", "true", "yes"):
|
|
214
|
+
asyncio.set_event_loop_policy(CompatibleEventLoopPolicy())
|
|
215
|
+
logger.info("Installed compatible event loop policy")
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Error-related models for the FastMCP server.
|
|
3
|
+
|
|
4
|
+
This module provides Pydantic-based error models and exception classes
|
|
5
|
+
following the same abstraction pattern as other models in the package.
|
|
6
|
+
|
|
7
|
+
The module provides:
|
|
8
|
+
- Error categories and classification
|
|
9
|
+
- Standardized error response models
|
|
10
|
+
- Compact error data for context window optimization
|
|
11
|
+
- Pydantic validation error handling
|
|
12
|
+
- FastMCP error statistics and optimization
|
|
13
|
+
- Exception classes with Pydantic model integration
|
|
14
|
+
- Error pattern recognition and compression rules
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from typing import Any, Dict, Optional
|
|
19
|
+
|
|
20
|
+
from pydantic import BaseModel, Field
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ErrorCategory(str, Enum):
|
|
24
|
+
"""Categories of errors for proper handling.
|
|
25
|
+
|
|
26
|
+
This enum defines the different categories of errors that can occur
|
|
27
|
+
in the FastMCP server, enabling proper error classification and handling.
|
|
28
|
+
|
|
29
|
+
The categories include:
|
|
30
|
+
- Authentication and permission errors
|
|
31
|
+
- Validation and input errors
|
|
32
|
+
- Network and external service errors
|
|
33
|
+
- Database and internal errors
|
|
34
|
+
- Rate limiting and resource errors
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
AUTHENTICATION = "authentication"
|
|
38
|
+
VALIDATION = "validation"
|
|
39
|
+
NETWORK = "network"
|
|
40
|
+
DATABASE = "database"
|
|
41
|
+
PERMISSION = "permission"
|
|
42
|
+
NOT_FOUND = "not_found"
|
|
43
|
+
RATE_LIMIT = "rate_limit"
|
|
44
|
+
INTERNAL = "internal"
|
|
45
|
+
EXTERNAL_SERVICE = "external_service"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ErrorContext(BaseModel):
|
|
49
|
+
"""Context information for errors.
|
|
50
|
+
|
|
51
|
+
This model provides contextual information about errors including
|
|
52
|
+
source, request tracking, user identification, and metadata.
|
|
53
|
+
|
|
54
|
+
The context includes:
|
|
55
|
+
- Error source identification
|
|
56
|
+
- Request and user tracking
|
|
57
|
+
- Timestamp information
|
|
58
|
+
- Additional metadata for debugging
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
source: str = Field(..., description="Source of the error")
|
|
62
|
+
request_id: Optional[str] = Field(None, description="Request identifier")
|
|
63
|
+
user_id: Optional[str] = Field(None, description="User identifier")
|
|
64
|
+
timestamp: Optional[str] = Field(None, description="Error timestamp")
|
|
65
|
+
metadata: Dict[str, Any] = Field(
|
|
66
|
+
default_factory=dict, description="Additional error metadata"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ErrorResponse(BaseModel):
|
|
71
|
+
"""Standardized error response model.
|
|
72
|
+
|
|
73
|
+
This model provides a standardized format for error responses
|
|
74
|
+
that can be returned to clients, ensuring consistency across
|
|
75
|
+
all error handling in the FastMCP server.
|
|
76
|
+
|
|
77
|
+
The response includes:
|
|
78
|
+
- User-friendly error message
|
|
79
|
+
- Error category classification
|
|
80
|
+
- HTTP status code
|
|
81
|
+
- Request tracking information
|
|
82
|
+
- Additional error details
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
message: str = Field(..., description="User-friendly error message")
|
|
86
|
+
category: ErrorCategory = Field(..., description="Error category")
|
|
87
|
+
code: int = Field(..., description="HTTP status code")
|
|
88
|
+
request_id: Optional[str] = Field(None, description="Request identifier")
|
|
89
|
+
details: Dict[str, Any] = Field(
|
|
90
|
+
default_factory=dict, description="Additional error details"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class CompactErrorData(BaseModel):
|
|
95
|
+
"""Compressed error data optimized for context windows.
|
|
96
|
+
|
|
97
|
+
This model provides compressed error information optimized for
|
|
98
|
+
use in context windows where space is limited, while maintaining
|
|
99
|
+
essential error information.
|
|
100
|
+
|
|
101
|
+
The compressed data includes:
|
|
102
|
+
- Original and compressed message lengths
|
|
103
|
+
- Compression ratio information
|
|
104
|
+
- Field-specific error details
|
|
105
|
+
- Model context for validation errors
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
original_length: int = Field(..., description="Original error message length")
|
|
109
|
+
compressed_length: int = Field(..., description="Compressed error message length")
|
|
110
|
+
compression_ratio: float = Field(..., description="Compression ratio (0-1)")
|
|
111
|
+
compressed_message: str = Field(..., description="Compressed error message")
|
|
112
|
+
field_errors: Dict[str, str] = Field(
|
|
113
|
+
default_factory=dict, description="Field-specific errors"
|
|
114
|
+
)
|
|
115
|
+
model_context: Optional[str] = Field(
|
|
116
|
+
None, description="Model context for the error"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class PydanticErrorInfo(BaseModel):
|
|
121
|
+
"""Pydantic validation error information.
|
|
122
|
+
|
|
123
|
+
This model provides detailed information about Pydantic validation
|
|
124
|
+
errors, including field paths, error types, and compressed messages
|
|
125
|
+
for efficient error handling.
|
|
126
|
+
|
|
127
|
+
The error info includes:
|
|
128
|
+
- Field path to the invalid field
|
|
129
|
+
- Type of validation error
|
|
130
|
+
- Original and compressed error messages
|
|
131
|
+
- Model name for context
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
field_path: str = Field(..., description="Path to the invalid field")
|
|
135
|
+
error_type: str = Field(..., description="Type of validation error")
|
|
136
|
+
error_message: str = Field(..., description="Original error message")
|
|
137
|
+
compressed_message: str = Field(..., description="Compressed error message")
|
|
138
|
+
model_name: Optional[str] = Field(None, description="Name of the Pydantic model")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class FastMCPErrorStats(BaseModel):
|
|
142
|
+
"""Statistics for FastMCP error optimization.
|
|
143
|
+
|
|
144
|
+
This model tracks statistics about error processing and optimization,
|
|
145
|
+
providing insights into error patterns and compression effectiveness.
|
|
146
|
+
|
|
147
|
+
The statistics include:
|
|
148
|
+
- Total errors processed
|
|
149
|
+
- Pydantic error counts
|
|
150
|
+
- Average compression ratios
|
|
151
|
+
- Context window savings
|
|
152
|
+
- Most common field errors
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
total_errors_processed: int = Field(default=0, description="Total errors processed")
|
|
156
|
+
pydantic_errors_count: int = Field(
|
|
157
|
+
default=0, description="Number of Pydantic validation errors"
|
|
158
|
+
)
|
|
159
|
+
average_compression_ratio: float = Field(
|
|
160
|
+
default=0.0, description="Average compression ratio"
|
|
161
|
+
)
|
|
162
|
+
context_window_savings: int = Field(default=0, description="Total characters saved")
|
|
163
|
+
most_common_field_errors: Dict[str, int] = Field(
|
|
164
|
+
default_factory=dict, description="Most common field validation errors"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# =============================================================================
|
|
169
|
+
# Exception Classes (Pydantic-based data + Exception behavior)
|
|
170
|
+
# =============================================================================
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class MCPError(Exception):
|
|
174
|
+
"""Base exception for all MCP errors with Pydantic model integration.
|
|
175
|
+
|
|
176
|
+
This class provides a base exception for all MCP-related errors,
|
|
177
|
+
integrating Pydantic model functionality with standard exception behavior.
|
|
178
|
+
It includes comprehensive error information and conversion methods.
|
|
179
|
+
|
|
180
|
+
The exception includes:
|
|
181
|
+
- Error message and category
|
|
182
|
+
- Status code and details
|
|
183
|
+
- User-friendly message generation
|
|
184
|
+
- Context information
|
|
185
|
+
- Conversion to response models
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
def __init__(
|
|
189
|
+
self,
|
|
190
|
+
message: str,
|
|
191
|
+
category: ErrorCategory = ErrorCategory.INTERNAL,
|
|
192
|
+
status_code: int = 500,
|
|
193
|
+
details: Optional[Dict[str, Any]] = None,
|
|
194
|
+
user_message: Optional[str] = None,
|
|
195
|
+
context: Optional[ErrorContext] = None,
|
|
196
|
+
):
|
|
197
|
+
"""Initialize the MCP error.
|
|
198
|
+
|
|
199
|
+
:param message: Internal error message
|
|
200
|
+
:type message: str
|
|
201
|
+
:param category: Error category for classification
|
|
202
|
+
:type category: ErrorCategory
|
|
203
|
+
:param status_code: HTTP status code
|
|
204
|
+
:type status_code: int
|
|
205
|
+
:param details: Additional error details
|
|
206
|
+
:type details: Optional[Dict[str, Any]]
|
|
207
|
+
:param user_message: User-friendly error message
|
|
208
|
+
:type user_message: Optional[str]
|
|
209
|
+
:param context: Error context information
|
|
210
|
+
:type context: Optional[ErrorContext]
|
|
211
|
+
"""
|
|
212
|
+
super().__init__(message)
|
|
213
|
+
self.message = message
|
|
214
|
+
self.category = category
|
|
215
|
+
self.status_code = status_code
|
|
216
|
+
self.details = details or {}
|
|
217
|
+
self.user_message = user_message or self._get_default_user_message()
|
|
218
|
+
self.context = context
|
|
219
|
+
|
|
220
|
+
def _get_default_user_message(self) -> str:
|
|
221
|
+
"""Get a safe default message for users.
|
|
222
|
+
|
|
223
|
+
Returns a user-friendly error message based on the error category.
|
|
224
|
+
|
|
225
|
+
:return: Default user-friendly error message
|
|
226
|
+
:rtype: str
|
|
227
|
+
"""
|
|
228
|
+
default_messages = {
|
|
229
|
+
ErrorCategory.AUTHENTICATION: "Authentication failed. Please check your credentials.",
|
|
230
|
+
ErrorCategory.VALIDATION: "Invalid input provided. Please check your request.",
|
|
231
|
+
ErrorCategory.NETWORK: "Network error occurred. Please try again later.",
|
|
232
|
+
ErrorCategory.PERMISSION: "Access denied. You don't have permission for this action.",
|
|
233
|
+
ErrorCategory.NOT_FOUND: "Resource not found.",
|
|
234
|
+
ErrorCategory.RATE_LIMIT: "Rate limit exceeded. Please try again later.",
|
|
235
|
+
ErrorCategory.EXTERNAL_SERVICE: "External service error. Please try again later.",
|
|
236
|
+
}
|
|
237
|
+
return default_messages.get(
|
|
238
|
+
self.category, "An error occurred. Please try again later."
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
def to_response_model(self) -> ErrorResponse:
|
|
242
|
+
"""Convert to Pydantic ErrorResponse model.
|
|
243
|
+
|
|
244
|
+
Converts the exception to a standardized ErrorResponse model
|
|
245
|
+
for consistent error handling.
|
|
246
|
+
|
|
247
|
+
:return: ErrorResponse model instance
|
|
248
|
+
:rtype: ErrorResponse
|
|
249
|
+
"""
|
|
250
|
+
return ErrorResponse(
|
|
251
|
+
message=self.user_message,
|
|
252
|
+
category=self.category,
|
|
253
|
+
code=self.status_code,
|
|
254
|
+
request_id=self.details.get("request_id"),
|
|
255
|
+
details=self.details,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def to_response(self) -> Dict[str, Any]:
|
|
259
|
+
"""Convert to response dictionary (backward compatibility).
|
|
260
|
+
|
|
261
|
+
Converts the exception to a dictionary format for backward
|
|
262
|
+
compatibility with existing error handling code.
|
|
263
|
+
|
|
264
|
+
:return: Error response dictionary
|
|
265
|
+
:rtype: Dict[str, Any]
|
|
266
|
+
"""
|
|
267
|
+
return self.to_response_model().model_dump()
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class ValidationError(MCPError):
|
|
271
|
+
"""Input validation errors with field-specific information.
|
|
272
|
+
|
|
273
|
+
This exception class handles validation errors with specific
|
|
274
|
+
field information and detailed error reporting.
|
|
275
|
+
|
|
276
|
+
The validation error includes:
|
|
277
|
+
- Field-specific error information
|
|
278
|
+
- Detailed field error mapping
|
|
279
|
+
- Validation-specific error handling
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
def __init__(
|
|
283
|
+
self,
|
|
284
|
+
message: str,
|
|
285
|
+
field: Optional[str] = None,
|
|
286
|
+
field_errors: Optional[Dict[str, str]] = None,
|
|
287
|
+
**kwargs,
|
|
288
|
+
):
|
|
289
|
+
"""Initialize the validation error.
|
|
290
|
+
|
|
291
|
+
:param message: Validation error message
|
|
292
|
+
:type message: str
|
|
293
|
+
:param field: Specific field that failed validation
|
|
294
|
+
:type field: Optional[str]
|
|
295
|
+
:param field_errors: Dictionary of field-specific errors
|
|
296
|
+
:type field_errors: Optional[Dict[str, str]]
|
|
297
|
+
:param **kwargs: Additional keyword arguments
|
|
298
|
+
"""
|
|
299
|
+
details = kwargs.get("details", {})
|
|
300
|
+
if field:
|
|
301
|
+
details["field"] = field
|
|
302
|
+
if field_errors:
|
|
303
|
+
details["field_errors"] = field_errors
|
|
304
|
+
|
|
305
|
+
super().__init__(
|
|
306
|
+
message,
|
|
307
|
+
category=ErrorCategory.VALIDATION,
|
|
308
|
+
status_code=400,
|
|
309
|
+
details=details,
|
|
310
|
+
**kwargs,
|
|
311
|
+
)
|
|
312
|
+
self.field = field
|
|
313
|
+
self.field_errors = field_errors or {}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class MCPAuthenticationError(MCPError):
|
|
317
|
+
"""MCP authentication-related errors.
|
|
318
|
+
|
|
319
|
+
This exception class handles MCP-specific authentication failures
|
|
320
|
+
and related security errors for the FastMCP server.
|
|
321
|
+
|
|
322
|
+
The authentication error includes:
|
|
323
|
+
- MCP-specific authentication error handling
|
|
324
|
+
- Security-focused error messages
|
|
325
|
+
- Authentication failure details for MCP context
|
|
326
|
+
"""
|
|
327
|
+
|
|
328
|
+
def __init__(self, message: str = "MCP authentication failed", **kwargs):
|
|
329
|
+
"""Initialize the MCP authentication error.
|
|
330
|
+
|
|
331
|
+
:param message: MCP authentication error message
|
|
332
|
+
:type message: str
|
|
333
|
+
:param **kwargs: Additional keyword arguments
|
|
334
|
+
"""
|
|
335
|
+
super().__init__(
|
|
336
|
+
message,
|
|
337
|
+
category=ErrorCategory.AUTHENTICATION,
|
|
338
|
+
status_code=401,
|
|
339
|
+
**kwargs,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class NetworkError(MCPError):
|
|
344
|
+
"""Network-related errors.
|
|
345
|
+
|
|
346
|
+
This exception class handles network failures and
|
|
347
|
+
connectivity issues.
|
|
348
|
+
|
|
349
|
+
The network error includes:
|
|
350
|
+
- Network-specific error handling
|
|
351
|
+
- Connectivity failure details
|
|
352
|
+
- Network error categorization
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
def __init__(self, message: str, **kwargs):
|
|
356
|
+
"""Initialize the network error.
|
|
357
|
+
|
|
358
|
+
:param message: Network error message
|
|
359
|
+
:type message: str
|
|
360
|
+
:param **kwargs: Additional keyword arguments
|
|
361
|
+
"""
|
|
362
|
+
super().__init__(
|
|
363
|
+
message,
|
|
364
|
+
category=ErrorCategory.NETWORK,
|
|
365
|
+
status_code=502,
|
|
366
|
+
**kwargs,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class ExternalServiceError(MCPError):
|
|
371
|
+
"""External service errors.
|
|
372
|
+
|
|
373
|
+
This exception class handles errors from external services
|
|
374
|
+
and third-party integrations.
|
|
375
|
+
|
|
376
|
+
The external service error includes:
|
|
377
|
+
- Service identification
|
|
378
|
+
- External service error details
|
|
379
|
+
- Service-specific error handling
|
|
380
|
+
"""
|
|
381
|
+
|
|
382
|
+
def __init__(self, message: str, service: str, **kwargs):
|
|
383
|
+
"""Initialize the external service error.
|
|
384
|
+
|
|
385
|
+
:param message: External service error message
|
|
386
|
+
:type message: str
|
|
387
|
+
:param service: Name of the external service
|
|
388
|
+
:type service: str
|
|
389
|
+
:param **kwargs: Additional keyword arguments
|
|
390
|
+
"""
|
|
391
|
+
details = kwargs.get("details", {})
|
|
392
|
+
details["service"] = service
|
|
393
|
+
super().__init__(
|
|
394
|
+
message,
|
|
395
|
+
category=ErrorCategory.EXTERNAL_SERVICE,
|
|
396
|
+
status_code=503,
|
|
397
|
+
details=details,
|
|
398
|
+
**kwargs,
|
|
399
|
+
)
|
|
400
|
+
self.service = service
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
# =============================================================================
|
|
404
|
+
# Error Pattern Models
|
|
405
|
+
# =============================================================================
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class ErrorPattern(BaseModel):
|
|
409
|
+
"""Error pattern for compression and recognition.
|
|
410
|
+
|
|
411
|
+
This model defines patterns for error recognition and compression,
|
|
412
|
+
enabling efficient error handling and message optimization.
|
|
413
|
+
|
|
414
|
+
The error pattern includes:
|
|
415
|
+
- Pattern name and identification
|
|
416
|
+
- Error type matching
|
|
417
|
+
- Keyword triggers
|
|
418
|
+
- Compressed format templates
|
|
419
|
+
- Pattern priority for matching
|
|
420
|
+
"""
|
|
421
|
+
|
|
422
|
+
pattern_name: str = Field(..., description="Name of the error pattern")
|
|
423
|
+
error_types: list[str] = Field(
|
|
424
|
+
..., description="Error types that match this pattern"
|
|
425
|
+
)
|
|
426
|
+
keywords: list[str] = Field(..., description="Keywords that trigger this pattern")
|
|
427
|
+
compressed_format: str = Field(..., description="Compressed message format")
|
|
428
|
+
priority: int = Field(default=0, description="Pattern matching priority")
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
class ErrorCompressionRule(BaseModel):
|
|
432
|
+
"""Rules for error message compression.
|
|
433
|
+
|
|
434
|
+
This model defines rules for compressing error messages
|
|
435
|
+
to optimize context window usage while maintaining essential information.
|
|
436
|
+
|
|
437
|
+
The compression rule includes:
|
|
438
|
+
- Rule name and identification
|
|
439
|
+
- Input pattern matching (regex)
|
|
440
|
+
- Output template for compression
|
|
441
|
+
- Maximum length constraints
|
|
442
|
+
- Applicable error categories
|
|
443
|
+
"""
|
|
444
|
+
|
|
445
|
+
rule_name: str = Field(..., description="Name of the compression rule")
|
|
446
|
+
input_pattern: str = Field(..., description="Regex pattern to match")
|
|
447
|
+
output_template: str = Field(..., description="Compressed output template")
|
|
448
|
+
max_length: int = Field(default=50, description="Maximum compressed message length")
|
|
449
|
+
applies_to: list[ErrorCategory] = Field(
|
|
450
|
+
default_factory=list,
|
|
451
|
+
description="Error categories this rule applies to",
|
|
452
|
+
)
|