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