tree-sitter-analyzer 1.9.17.1__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 (149) hide show
  1. tree_sitter_analyzer/__init__.py +132 -0
  2. tree_sitter_analyzer/__main__.py +11 -0
  3. tree_sitter_analyzer/api.py +853 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +12 -0
  6. tree_sitter_analyzer/cli/argument_validator.py +89 -0
  7. tree_sitter_analyzer/cli/commands/__init__.py +26 -0
  8. tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
  9. tree_sitter_analyzer/cli/commands/base_command.py +181 -0
  10. tree_sitter_analyzer/cli/commands/default_command.py +18 -0
  11. tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
  12. tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
  13. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
  14. tree_sitter_analyzer/cli/commands/query_command.py +109 -0
  15. tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
  16. tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
  17. tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
  18. tree_sitter_analyzer/cli/commands/table_command.py +414 -0
  19. tree_sitter_analyzer/cli/info_commands.py +124 -0
  20. tree_sitter_analyzer/cli_main.py +472 -0
  21. tree_sitter_analyzer/constants.py +85 -0
  22. tree_sitter_analyzer/core/__init__.py +15 -0
  23. tree_sitter_analyzer/core/analysis_engine.py +580 -0
  24. tree_sitter_analyzer/core/cache_service.py +333 -0
  25. tree_sitter_analyzer/core/engine.py +585 -0
  26. tree_sitter_analyzer/core/parser.py +293 -0
  27. tree_sitter_analyzer/core/query.py +605 -0
  28. tree_sitter_analyzer/core/query_filter.py +200 -0
  29. tree_sitter_analyzer/core/query_service.py +340 -0
  30. tree_sitter_analyzer/encoding_utils.py +530 -0
  31. tree_sitter_analyzer/exceptions.py +747 -0
  32. tree_sitter_analyzer/file_handler.py +246 -0
  33. tree_sitter_analyzer/formatters/__init__.py +1 -0
  34. tree_sitter_analyzer/formatters/base_formatter.py +201 -0
  35. tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
  36. tree_sitter_analyzer/formatters/formatter_config.py +197 -0
  37. tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
  38. tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
  39. tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
  40. tree_sitter_analyzer/formatters/go_formatter.py +368 -0
  41. tree_sitter_analyzer/formatters/html_formatter.py +498 -0
  42. tree_sitter_analyzer/formatters/java_formatter.py +423 -0
  43. tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
  44. tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
  45. tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
  46. tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
  47. tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
  48. tree_sitter_analyzer/formatters/php_formatter.py +301 -0
  49. tree_sitter_analyzer/formatters/python_formatter.py +830 -0
  50. tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
  51. tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
  52. tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
  53. tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
  54. tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
  55. tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
  56. tree_sitter_analyzer/interfaces/__init__.py +9 -0
  57. tree_sitter_analyzer/interfaces/cli.py +535 -0
  58. tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
  59. tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
  60. tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
  61. tree_sitter_analyzer/language_detector.py +553 -0
  62. tree_sitter_analyzer/language_loader.py +271 -0
  63. tree_sitter_analyzer/languages/__init__.py +10 -0
  64. tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
  65. tree_sitter_analyzer/languages/css_plugin.py +449 -0
  66. tree_sitter_analyzer/languages/go_plugin.py +836 -0
  67. tree_sitter_analyzer/languages/html_plugin.py +496 -0
  68. tree_sitter_analyzer/languages/java_plugin.py +1299 -0
  69. tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
  70. tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
  71. tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
  72. tree_sitter_analyzer/languages/php_plugin.py +862 -0
  73. tree_sitter_analyzer/languages/python_plugin.py +1636 -0
  74. tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
  75. tree_sitter_analyzer/languages/rust_plugin.py +673 -0
  76. tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
  77. tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
  78. tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
  79. tree_sitter_analyzer/legacy_table_formatter.py +860 -0
  80. tree_sitter_analyzer/mcp/__init__.py +34 -0
  81. tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
  82. tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
  83. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
  84. tree_sitter_analyzer/mcp/server.py +869 -0
  85. tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
  86. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
  87. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
  88. tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
  89. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
  90. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
  91. tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
  92. tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
  93. tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
  94. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
  95. tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
  96. tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
  97. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
  98. tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
  99. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
  100. tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
  101. tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
  102. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
  103. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
  104. tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
  105. tree_sitter_analyzer/models.py +840 -0
  106. tree_sitter_analyzer/mypy_current_errors.txt +2 -0
  107. tree_sitter_analyzer/output_manager.py +255 -0
  108. tree_sitter_analyzer/platform_compat/__init__.py +3 -0
  109. tree_sitter_analyzer/platform_compat/adapter.py +324 -0
  110. tree_sitter_analyzer/platform_compat/compare.py +224 -0
  111. tree_sitter_analyzer/platform_compat/detector.py +67 -0
  112. tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
  113. tree_sitter_analyzer/platform_compat/profiles.py +217 -0
  114. tree_sitter_analyzer/platform_compat/record.py +55 -0
  115. tree_sitter_analyzer/platform_compat/recorder.py +155 -0
  116. tree_sitter_analyzer/platform_compat/report.py +92 -0
  117. tree_sitter_analyzer/plugins/__init__.py +280 -0
  118. tree_sitter_analyzer/plugins/base.py +647 -0
  119. tree_sitter_analyzer/plugins/manager.py +384 -0
  120. tree_sitter_analyzer/project_detector.py +328 -0
  121. tree_sitter_analyzer/queries/__init__.py +27 -0
  122. tree_sitter_analyzer/queries/csharp.py +216 -0
  123. tree_sitter_analyzer/queries/css.py +615 -0
  124. tree_sitter_analyzer/queries/go.py +275 -0
  125. tree_sitter_analyzer/queries/html.py +543 -0
  126. tree_sitter_analyzer/queries/java.py +402 -0
  127. tree_sitter_analyzer/queries/javascript.py +724 -0
  128. tree_sitter_analyzer/queries/kotlin.py +192 -0
  129. tree_sitter_analyzer/queries/markdown.py +258 -0
  130. tree_sitter_analyzer/queries/php.py +95 -0
  131. tree_sitter_analyzer/queries/python.py +859 -0
  132. tree_sitter_analyzer/queries/ruby.py +92 -0
  133. tree_sitter_analyzer/queries/rust.py +223 -0
  134. tree_sitter_analyzer/queries/sql.py +555 -0
  135. tree_sitter_analyzer/queries/typescript.py +871 -0
  136. tree_sitter_analyzer/queries/yaml.py +236 -0
  137. tree_sitter_analyzer/query_loader.py +272 -0
  138. tree_sitter_analyzer/security/__init__.py +22 -0
  139. tree_sitter_analyzer/security/boundary_manager.py +277 -0
  140. tree_sitter_analyzer/security/regex_checker.py +297 -0
  141. tree_sitter_analyzer/security/validator.py +599 -0
  142. tree_sitter_analyzer/table_formatter.py +782 -0
  143. tree_sitter_analyzer/utils/__init__.py +53 -0
  144. tree_sitter_analyzer/utils/logging.py +433 -0
  145. tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
  146. tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
  147. tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
  148. tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
  149. tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
@@ -0,0 +1,747 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Tree-sitter Analyzer Custom Exceptions
4
+
5
+ Unified exception handling system for consistent error management
6
+ across the entire framework.
7
+ """
8
+
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ class TreeSitterAnalyzerError(Exception):
14
+ """Base exception for all tree-sitter analyzer errors."""
15
+
16
+ def __init__(
17
+ self,
18
+ message: str,
19
+ error_code: str | None = None,
20
+ context: dict[str, Any] | None = None,
21
+ ) -> None:
22
+ super().__init__(message)
23
+ self.message = message
24
+ self.error_code = error_code or self.__class__.__name__
25
+ self.context = context or {}
26
+
27
+ def to_dict(self) -> dict[str, Any]:
28
+ """Convert exception to dictionary format."""
29
+ return {
30
+ "error_type": self.__class__.__name__,
31
+ "error_code": self.error_code,
32
+ "message": self.message,
33
+ "context": self.context,
34
+ }
35
+
36
+
37
+ class AnalysisError(TreeSitterAnalyzerError):
38
+ """Raised when file analysis fails."""
39
+
40
+ def __init__(
41
+ self,
42
+ message: str,
43
+ file_path: str | Path | None = None,
44
+ language: str | None = None,
45
+ **kwargs: Any,
46
+ ) -> None:
47
+ context = kwargs.get("context", {})
48
+ if file_path:
49
+ context["file_path"] = str(file_path)
50
+ if language:
51
+ context["language"] = language
52
+ super().__init__(message, context=context, **kwargs)
53
+
54
+
55
+ class ParseError(TreeSitterAnalyzerError):
56
+ """Raised when parsing fails."""
57
+
58
+ def __init__(
59
+ self,
60
+ message: str,
61
+ language: str | None = None,
62
+ source_info: dict[str, Any] | None = None,
63
+ **kwargs: Any,
64
+ ) -> None:
65
+ context = kwargs.get("context", {})
66
+ if language:
67
+ context["language"] = language
68
+ if source_info:
69
+ context.update(source_info)
70
+ super().__init__(message, context=context, **kwargs)
71
+
72
+
73
+ class LanguageNotSupportedError(TreeSitterAnalyzerError):
74
+ """Raised when a language is not supported."""
75
+
76
+ def __init__(
77
+ self, language: str, supported_languages: list[str] | None = None, **kwargs: Any
78
+ ) -> None:
79
+ message = f"Language '{language}' is not supported"
80
+ context = kwargs.get("context", {})
81
+ context["language"] = language
82
+ if supported_languages:
83
+ context["supported_languages"] = supported_languages
84
+ message += f". Supported languages: {', '.join(supported_languages)}"
85
+ super().__init__(message, context=context, **kwargs)
86
+
87
+
88
+ class PluginError(TreeSitterAnalyzerError):
89
+ """Raised when plugin operations fail."""
90
+
91
+ def __init__(
92
+ self,
93
+ message: str,
94
+ plugin_name: str | None = None,
95
+ operation: str | None = None,
96
+ **kwargs: Any,
97
+ ) -> None:
98
+ context = kwargs.get("context", {})
99
+ if plugin_name:
100
+ context["plugin_name"] = plugin_name
101
+ if operation:
102
+ context["operation"] = operation
103
+ super().__init__(message, context=context, **kwargs)
104
+
105
+
106
+ class QueryError(TreeSitterAnalyzerError):
107
+ """Raised when query execution fails."""
108
+
109
+ def __init__(
110
+ self,
111
+ message: str,
112
+ query_name: str | None = None,
113
+ query_string: str | None = None,
114
+ language: str | None = None,
115
+ **kwargs: Any,
116
+ ) -> None:
117
+ context = kwargs.get("context", {})
118
+ if query_name:
119
+ context["query_name"] = query_name
120
+ if query_string:
121
+ context["query_string"] = query_string
122
+ if language:
123
+ context["language"] = language
124
+ super().__init__(message, context=context, **kwargs)
125
+
126
+
127
+ class FileHandlingError(TreeSitterAnalyzerError):
128
+ """Raised when file operations fail."""
129
+
130
+ def __init__(
131
+ self,
132
+ message: str,
133
+ file_path: str | Path | None = None,
134
+ operation: str | None = None,
135
+ **kwargs: Any,
136
+ ) -> None:
137
+ context = kwargs.get("context", {})
138
+ if file_path:
139
+ context["file_path"] = str(file_path)
140
+ if operation:
141
+ context["operation"] = operation
142
+ super().__init__(message, context=context, **kwargs)
143
+
144
+
145
+ class ConfigurationError(TreeSitterAnalyzerError):
146
+ """Raised when configuration is invalid."""
147
+
148
+ def __init__(
149
+ self,
150
+ message: str,
151
+ config_key: str | None = None,
152
+ config_value: Any | None = None,
153
+ **kwargs: Any,
154
+ ) -> None:
155
+ context = kwargs.get("context", {})
156
+ if config_key:
157
+ context["config_key"] = config_key
158
+ if config_value is not None:
159
+ context["config_value"] = config_value
160
+ super().__init__(message, context=context, **kwargs)
161
+
162
+
163
+ class ValidationError(TreeSitterAnalyzerError):
164
+ """Raised when validation fails."""
165
+
166
+ def __init__(
167
+ self,
168
+ message: str,
169
+ validation_type: str | None = None,
170
+ invalid_value: Any | None = None,
171
+ **kwargs: Any,
172
+ ) -> None:
173
+ context = kwargs.pop("context", {})
174
+ if validation_type:
175
+ context["validation_type"] = validation_type
176
+ if invalid_value is not None:
177
+ context["invalid_value"] = invalid_value
178
+ super().__init__(message, context=context, **kwargs)
179
+
180
+
181
+ class MCPError(TreeSitterAnalyzerError):
182
+ """Raised when MCP operations fail."""
183
+
184
+ def __init__(
185
+ self,
186
+ message: str,
187
+ tool_name: str | None = None,
188
+ resource_uri: str | None = None,
189
+ **kwargs: Any,
190
+ ) -> None:
191
+ context = kwargs.pop("context", {})
192
+ if tool_name:
193
+ context["tool_name"] = tool_name
194
+ if resource_uri:
195
+ context["resource_uri"] = resource_uri
196
+ super().__init__(message, context=context, **kwargs)
197
+
198
+
199
+ # Exception handling utilities
200
+ def handle_exception(
201
+ exception: Exception,
202
+ context: dict[str, Any] | None = None,
203
+ reraise_as: type[Exception] | None = None,
204
+ ) -> None:
205
+ """
206
+ Handle exceptions with optional context and re-raising.
207
+
208
+ Args:
209
+ exception: The original exception
210
+ context: Additional context information
211
+ reraise_as: Exception class to re-raise as
212
+ """
213
+ from .utils import log_error
214
+
215
+ # Log the original exception
216
+ error_context = context or {}
217
+ if hasattr(exception, "context"):
218
+ error_context.update(exception.context)
219
+
220
+ log_error(f"Exception handled: {exception}", extra=error_context)
221
+
222
+ # Re-raise as different exception type if requested
223
+ if reraise_as and not isinstance(exception, reraise_as):
224
+ if issubclass(reraise_as, TreeSitterAnalyzerError):
225
+ raise reraise_as(str(exception), context=error_context)
226
+ else:
227
+ raise reraise_as(str(exception))
228
+
229
+ # Re-raise original exception
230
+ raise exception
231
+
232
+
233
+ def safe_execute(
234
+ func: Any,
235
+ *args: Any,
236
+ default_return: Any = None,
237
+ exception_types: tuple[type[Exception], ...] = (Exception,),
238
+ log_errors: bool = True,
239
+ **kwargs: Any,
240
+ ) -> Any:
241
+ """
242
+ Safely execute a function with exception handling.
243
+
244
+ Args:
245
+ func: Function to execute
246
+ *args: Function arguments
247
+ default_return: Value to return on exception
248
+ exception_types: Exception types to catch
249
+ log_errors: Whether to log errors
250
+ **kwargs: Function keyword arguments
251
+
252
+ Returns:
253
+ Function result or default_return on exception
254
+ """
255
+ try:
256
+ return func(*args, **kwargs)
257
+ except exception_types as e:
258
+ if log_errors:
259
+ from .utils import log_error
260
+
261
+ log_error(f"Safe execution failed for {func.__name__}: {e}")
262
+ return default_return
263
+
264
+
265
+ def create_error_response(
266
+ exception: Exception, include_traceback: bool = False
267
+ ) -> dict[str, Any]:
268
+ """
269
+ Create standardized error response dictionary.
270
+
271
+ Args:
272
+ exception: The exception to convert
273
+ include_traceback: Whether to include traceback
274
+
275
+ Returns:
276
+ Error response dictionary
277
+ """
278
+ import traceback
279
+
280
+ response: dict[str, Any] = {
281
+ "success": False,
282
+ "error": {"type": exception.__class__.__name__, "message": str(exception)},
283
+ }
284
+
285
+ # Add context if available
286
+ if hasattr(exception, "context"):
287
+ response["error"]["context"] = exception.context
288
+
289
+ # Add error code if available
290
+ if hasattr(exception, "error_code"):
291
+ response["error"]["code"] = exception.error_code
292
+
293
+ # Add traceback if requested
294
+ if include_traceback:
295
+ response["error"]["traceback"] = traceback.format_exc()
296
+
297
+ return response
298
+
299
+
300
+ # Decorator for exception handling
301
+ def handle_exceptions(
302
+ default_return: Any = None,
303
+ exception_types: tuple[type[Exception], ...] = (Exception,),
304
+ reraise_as: type[Exception] | None = None,
305
+ log_errors: bool = True,
306
+ ) -> Any:
307
+ """
308
+ Decorator for automatic exception handling.
309
+
310
+ Args:
311
+ default_return: Value to return on exception
312
+ exception_types: Exception types to catch
313
+ reraise_as: Exception class to re-raise as
314
+ log_errors: Whether to log errors
315
+ """
316
+
317
+ def decorator(func: Any) -> Any:
318
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
319
+ try:
320
+ return func(*args, **kwargs)
321
+ except exception_types as e:
322
+ if log_errors:
323
+ from .utils import log_error
324
+
325
+ log_error(f"Exception in {func.__name__}: {e}")
326
+
327
+ if reraise_as:
328
+ if issubclass(reraise_as, TreeSitterAnalyzerError):
329
+ raise reraise_as(str(e)) from e
330
+ else:
331
+ raise reraise_as(str(e)) from e
332
+
333
+ return default_return
334
+
335
+ return wrapper
336
+
337
+ return decorator
338
+
339
+
340
+ class SecurityError(TreeSitterAnalyzerError):
341
+ """Raised when security validation fails."""
342
+
343
+ def __init__(
344
+ self,
345
+ message: str,
346
+ security_type: str | None = None,
347
+ file_path: str | Path | None = None,
348
+ **kwargs: Any,
349
+ ) -> None:
350
+ context = kwargs.pop("context", {})
351
+ if security_type:
352
+ context["security_type"] = security_type
353
+ if file_path:
354
+ context["file_path"] = str(file_path)
355
+
356
+ super().__init__(message, context=context, **kwargs)
357
+ self.security_type = security_type
358
+ self.file_path = str(file_path) if file_path else None
359
+
360
+
361
+ class PathTraversalError(SecurityError):
362
+ """Raised when path traversal attack is detected."""
363
+
364
+ def __init__(
365
+ self,
366
+ message: str,
367
+ attempted_path: str | None = None,
368
+ **kwargs: Any,
369
+ ) -> None:
370
+ context = kwargs.pop("context", {})
371
+ if attempted_path:
372
+ context["attempted_path"] = attempted_path
373
+
374
+ super().__init__(
375
+ message, security_type="path_traversal", context=context, **kwargs
376
+ )
377
+ self.attempted_path = attempted_path
378
+
379
+
380
+ class RegexSecurityError(SecurityError):
381
+ """Raised when unsafe regex pattern is detected."""
382
+
383
+ def __init__(
384
+ self,
385
+ message: str,
386
+ pattern: str | None = None,
387
+ dangerous_construct: str | None = None,
388
+ **kwargs: Any,
389
+ ) -> None:
390
+ context = kwargs.pop("context", {})
391
+ if pattern:
392
+ context["pattern"] = pattern
393
+ if dangerous_construct:
394
+ context["dangerous_construct"] = dangerous_construct
395
+
396
+ super().__init__(
397
+ message, security_type="regex_security", context=context, **kwargs
398
+ )
399
+ self.pattern = pattern
400
+ self.dangerous_construct = dangerous_construct
401
+
402
+
403
+ # MCP-specific exceptions for enhanced error handling
404
+ class MCPToolError(MCPError):
405
+ """Raised when MCP tool execution fails."""
406
+
407
+ def __init__(
408
+ self,
409
+ message: str,
410
+ tool_name: str | None = None,
411
+ input_params: dict[str, Any] | None = None,
412
+ execution_stage: str | None = None,
413
+ **kwargs: Any,
414
+ ) -> None:
415
+ context = kwargs.pop("context", {})
416
+ if input_params:
417
+ # Sanitize sensitive information from input params
418
+ sanitized_params = self._sanitize_params(input_params)
419
+ context["input_params"] = sanitized_params
420
+ if execution_stage:
421
+ context["execution_stage"] = execution_stage
422
+
423
+ super().__init__(message, tool_name=tool_name, context=context, **kwargs)
424
+ self.tool_name = tool_name
425
+ self.input_params = input_params
426
+ self.execution_stage = execution_stage
427
+
428
+ @staticmethod
429
+ def _sanitize_params(params: dict[str, Any]) -> dict[str, Any]:
430
+ """Sanitize sensitive information from parameters."""
431
+ sanitized = {}
432
+ sensitive_keys = {"password", "token", "key", "secret", "auth", "credential"}
433
+
434
+ for key, value in params.items():
435
+ if any(sensitive in key.lower() for sensitive in sensitive_keys):
436
+ sanitized[key] = "***REDACTED***"
437
+ elif isinstance(value, str) and len(value) > 100:
438
+ sanitized[key] = value[:100] + "...[TRUNCATED]"
439
+ else:
440
+ sanitized[key] = value
441
+
442
+ return sanitized
443
+
444
+
445
+ class MCPResourceError(MCPError):
446
+ """Raised when MCP resource access fails."""
447
+
448
+ def __init__(
449
+ self,
450
+ message: str,
451
+ resource_uri: str | None = None,
452
+ resource_type: str | None = None,
453
+ access_mode: str | None = None,
454
+ **kwargs: Any,
455
+ ) -> None:
456
+ context = kwargs.pop("context", {})
457
+ if resource_type:
458
+ context["resource_type"] = resource_type
459
+ if access_mode:
460
+ context["access_mode"] = access_mode
461
+
462
+ super().__init__(message, resource_uri=resource_uri, context=context, **kwargs)
463
+ self.resource_uri = resource_uri
464
+ self.resource_type = resource_type
465
+ self.access_mode = access_mode
466
+
467
+
468
+ class MCPTimeoutError(MCPError):
469
+ """Raised when MCP operation times out."""
470
+
471
+ def __init__(
472
+ self,
473
+ message: str,
474
+ timeout_seconds: float | None = None,
475
+ operation_type: str | None = None,
476
+ **kwargs: Any,
477
+ ) -> None:
478
+ context = kwargs.pop("context", {})
479
+ if timeout_seconds:
480
+ context["timeout_seconds"] = timeout_seconds
481
+ if operation_type:
482
+ context["operation_type"] = operation_type
483
+
484
+ super().__init__(message, context=context, **kwargs)
485
+ self.timeout_seconds = timeout_seconds
486
+ self.operation_type = operation_type
487
+
488
+
489
+ class MCPValidationError(ValidationError):
490
+ """Raised when MCP input validation fails."""
491
+
492
+ def __init__(
493
+ self,
494
+ message: str,
495
+ tool_name: str | None = None,
496
+ parameter_name: str | None = None,
497
+ parameter_value: Any | None = None,
498
+ validation_rule: str | None = None,
499
+ **kwargs: Any,
500
+ ) -> None:
501
+ context = kwargs.pop("context", {})
502
+ if tool_name:
503
+ context["tool_name"] = tool_name
504
+ if parameter_name:
505
+ context["parameter_name"] = parameter_name
506
+ if validation_rule:
507
+ context["validation_rule"] = validation_rule
508
+
509
+ # Sanitize parameter value for logging
510
+ if parameter_value is not None:
511
+ if isinstance(parameter_value, str) and len(parameter_value) > 200:
512
+ context["parameter_value"] = parameter_value[:200] + "...[TRUNCATED]"
513
+ else:
514
+ context["parameter_value"] = parameter_value
515
+
516
+ super().__init__(
517
+ message, validation_type="mcp_parameter", context=context, **kwargs
518
+ )
519
+ self.tool_name = tool_name
520
+ self.parameter_name = parameter_name
521
+ self.validation_rule = validation_rule
522
+
523
+
524
+ class FileRestrictionError(SecurityError):
525
+ """Raised when file access is restricted by mode or security policy."""
526
+
527
+ def __init__(
528
+ self,
529
+ message: str,
530
+ file_path: str | Path | None = None,
531
+ current_mode: str | None = None,
532
+ allowed_patterns: list[str] | None = None,
533
+ **kwargs: Any,
534
+ ) -> None:
535
+ context = kwargs.pop("context", {})
536
+ if current_mode:
537
+ context["current_mode"] = current_mode
538
+ if allowed_patterns:
539
+ context["allowed_patterns"] = allowed_patterns
540
+
541
+ super().__init__(
542
+ message,
543
+ security_type="file_restriction",
544
+ file_path=file_path,
545
+ context=context,
546
+ **kwargs,
547
+ )
548
+ self.current_mode = current_mode
549
+ self.allowed_patterns = allowed_patterns
550
+
551
+
552
+ # Enhanced error response utilities for MCP
553
+ def create_mcp_error_response(
554
+ exception: Exception,
555
+ tool_name: str | None = None,
556
+ include_debug_info: bool = False,
557
+ sanitize_sensitive: bool = True,
558
+ ) -> dict[str, Any]:
559
+ """
560
+ Create standardized MCP error response dictionary.
561
+
562
+ Args:
563
+ exception: The exception to convert
564
+ tool_name: Name of the MCP tool that failed
565
+ include_debug_info: Whether to include debug information
566
+ sanitize_sensitive: Whether to sanitize sensitive information
567
+
568
+ Returns:
569
+ MCP-compliant error response dictionary
570
+ """
571
+ import traceback
572
+
573
+ response: dict[str, Any] = {
574
+ "success": False,
575
+ "error": {
576
+ "type": exception.__class__.__name__,
577
+ "message": str(exception),
578
+ "timestamp": __import__("datetime").datetime.utcnow().isoformat() + "Z",
579
+ },
580
+ }
581
+
582
+ # Add tool name if provided
583
+ if tool_name:
584
+ response["error"]["tool"] = tool_name
585
+
586
+ # Add context if available
587
+ if hasattr(exception, "context") and exception.context:
588
+ context = exception.context.copy()
589
+
590
+ # Sanitize sensitive information if requested
591
+ if sanitize_sensitive:
592
+ context = _sanitize_error_context(context)
593
+
594
+ response["error"]["context"] = context
595
+
596
+ # Add error code if available
597
+ if hasattr(exception, "error_code"):
598
+ response["error"]["code"] = exception.error_code
599
+
600
+ # Add debug information if requested
601
+ if include_debug_info:
602
+ response["error"]["debug"] = {
603
+ "traceback": traceback.format_exc(),
604
+ "exception_args": list(exception.args) if exception.args else [],
605
+ }
606
+
607
+ # Add specific error details for known exception types
608
+ if isinstance(exception, MCPToolError):
609
+ response["error"]["execution_stage"] = exception.execution_stage
610
+ elif isinstance(exception, MCPTimeoutError):
611
+ response["error"]["timeout_seconds"] = exception.timeout_seconds
612
+ elif isinstance(exception, FileRestrictionError):
613
+ response["error"]["current_mode"] = exception.current_mode
614
+ response["error"]["allowed_patterns"] = exception.allowed_patterns
615
+
616
+ return response
617
+
618
+
619
+ def _sanitize_error_context(context: dict[str, Any]) -> dict[str, Any]:
620
+ """Sanitize sensitive information from error context."""
621
+ sanitized: dict[str, Any] = {}
622
+ sensitive_keys = {
623
+ "password",
624
+ "token",
625
+ "key",
626
+ "secret",
627
+ "auth",
628
+ "credential",
629
+ "api_key",
630
+ "access_token",
631
+ "private_key",
632
+ "session_id",
633
+ }
634
+
635
+ for key, value in context.items():
636
+ if any(sensitive in key.lower() for sensitive in sensitive_keys):
637
+ sanitized[key] = "***REDACTED***"
638
+ elif isinstance(value, str) and len(value) > 500:
639
+ sanitized[key] = value[:500] + "...[TRUNCATED]"
640
+ elif isinstance(value, list | tuple) and len(value) > 10:
641
+ sanitized[key] = list(value[:10]) + ["...[TRUNCATED]"]
642
+ elif isinstance(value, dict) and len(value) > 20:
643
+ # Recursively sanitize nested dictionaries
644
+ truncated_dict = dict(list(value.items())[:20])
645
+ sanitized[key] = _sanitize_error_context(truncated_dict)
646
+ sanitized[key]["__truncated__"] = True
647
+ else:
648
+ sanitized[key] = value
649
+
650
+ return sanitized
651
+
652
+
653
+ # Async exception handling utilities for MCP tools
654
+ async def safe_execute_async(
655
+ coro: Any,
656
+ default_return: Any = None,
657
+ exception_types: tuple[type[Exception], ...] = (Exception,),
658
+ log_errors: bool = True,
659
+ tool_name: str | None = None,
660
+ ) -> Any:
661
+ """
662
+ Safely execute an async function with exception handling.
663
+
664
+ Args:
665
+ coro: Coroutine to execute
666
+ default_return: Value to return on exception
667
+ exception_types: Exception types to catch
668
+ log_errors: Whether to log errors
669
+ tool_name: Name of the tool for error context
670
+
671
+ Returns:
672
+ Coroutine result or default_return on exception
673
+ """
674
+ try:
675
+ return await coro
676
+ except exception_types as e:
677
+ if log_errors:
678
+ from .utils import log_error
679
+
680
+ error_context = {"tool_name": tool_name} if tool_name else {}
681
+ log_error(f"Async execution failed: {e}", extra=error_context)
682
+
683
+ return default_return
684
+
685
+
686
+ def mcp_exception_handler(
687
+ tool_name: str,
688
+ include_debug: bool = False,
689
+ sanitize_sensitive: bool = True,
690
+ ) -> Any:
691
+ """
692
+ Decorator for MCP tool exception handling.
693
+
694
+ Args:
695
+ tool_name: Name of the MCP tool
696
+ include_debug: Whether to include debug information
697
+ sanitize_sensitive: Whether to sanitize sensitive information
698
+ """
699
+
700
+ def decorator(func: Any) -> Any:
701
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
702
+ try:
703
+ return await func(*args, **kwargs)
704
+ except Exception as e:
705
+ from .utils import log_error
706
+
707
+ # Log the error with tool context
708
+ log_error(
709
+ f"MCP tool '{tool_name}' failed: {e}",
710
+ extra={"tool_name": tool_name, "exception_type": type(e).__name__},
711
+ )
712
+
713
+ # Return standardized error response
714
+ return create_mcp_error_response(
715
+ e,
716
+ tool_name=tool_name,
717
+ include_debug_info=include_debug,
718
+ sanitize_sensitive=sanitize_sensitive,
719
+ )
720
+
721
+ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
722
+ try:
723
+ return func(*args, **kwargs)
724
+ except Exception as e:
725
+ from .utils import log_error
726
+
727
+ # Log the error with tool context
728
+ log_error(
729
+ f"MCP tool '{tool_name}' failed: {e}",
730
+ extra={"tool_name": tool_name, "exception_type": type(e).__name__},
731
+ )
732
+
733
+ # Return standardized error response
734
+ return create_mcp_error_response(
735
+ e,
736
+ tool_name=tool_name,
737
+ include_debug_info=include_debug,
738
+ sanitize_sensitive=sanitize_sensitive,
739
+ )
740
+
741
+ # Return appropriate wrapper based on function type
742
+ if __import__("asyncio").iscoroutinefunction(func):
743
+ return async_wrapper
744
+ else:
745
+ return sync_wrapper
746
+
747
+ return decorator