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,113 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ MCP Utils Module
4
+
5
+ This module provides utility functions and classes for the MCP server
6
+ including error handling and other utilities.
7
+
8
+ Note: Cache and performance monitoring functionality has been moved to
9
+ the unified core services for better architecture.
10
+ """
11
+
12
+ from typing import Any
13
+
14
+ # Export main utility classes and functions
15
+ from .error_handler import (
16
+ AnalysisError,
17
+ ErrorCategory,
18
+ ErrorHandler,
19
+ ErrorSeverity,
20
+ FileAccessError,
21
+ MCPError,
22
+ ParsingError,
23
+ ResourceError,
24
+ ValidationError,
25
+ get_error_handler,
26
+ handle_mcp_errors,
27
+ )
28
+
29
+ # Export path resolver utilities
30
+ from .path_resolver import PathResolver, resolve_path
31
+
32
+ # Module metadata
33
+ __author__ = "Tree-Sitter Analyzer Team"
34
+
35
+ # MCP Utils capabilities
36
+ MCP_UTILS_CAPABILITIES = {
37
+ "version": "1.1.0",
38
+ "features": [
39
+ "Comprehensive Error Handling",
40
+ "Unified Core Services Integration",
41
+ "Cross-Platform Path Resolution",
42
+ ],
43
+ "deprecated_features": [
44
+ "LRU Cache with TTL (moved to core.cache_service)",
45
+ "Performance Monitoring (moved to core.analysis_engine)",
46
+ ],
47
+ }
48
+
49
+ # Import unified services for backward compatibility
50
+ try:
51
+ from ...core.analysis_engine import UnifiedAnalysisEngine
52
+ from ...core.cache_service import CacheService as UnifiedCacheService
53
+
54
+ # Provide backward compatibility aliases
55
+ class BackwardCompatibleCacheManager:
56
+ """Backward compatible cache manager wrapper"""
57
+
58
+ def __init__(self) -> None:
59
+ self._cache_service = UnifiedCacheService()
60
+
61
+ def clear_all_caches(self) -> None:
62
+ """Backward compatibility: clear all caches"""
63
+ return self._cache_service.clear()
64
+
65
+ def get_cache_stats(self) -> dict[str, Any]:
66
+ """Backward compatibility: get cache statistics"""
67
+ return self._cache_service.get_stats()
68
+
69
+ def __getattr__(self, name: str) -> Any:
70
+ """Delegate other methods to the cache service"""
71
+ return getattr(self._cache_service, name)
72
+
73
+ def get_cache_manager() -> Any:
74
+ """Backward compatibility: Get unified cache service"""
75
+ return BackwardCompatibleCacheManager()
76
+
77
+ def get_performance_monitor() -> Any:
78
+ """Backward compatibility: Get unified analysis engine for performance monitoring"""
79
+ return UnifiedAnalysisEngine()
80
+
81
+ except ImportError:
82
+ # Fallback if core services are not available
83
+ def get_cache_manager() -> Any:
84
+ """Fallback cache manager"""
85
+ return None
86
+
87
+ def get_performance_monitor() -> Any:
88
+ """Fallback performance monitor"""
89
+ return None
90
+
91
+
92
+ __all__ = [
93
+ # Error handling
94
+ "ErrorHandler",
95
+ "MCPError",
96
+ "FileAccessError",
97
+ "ParsingError",
98
+ "AnalysisError",
99
+ "ValidationError",
100
+ "ResourceError",
101
+ "ErrorSeverity",
102
+ "ErrorCategory",
103
+ "handle_mcp_errors",
104
+ "get_error_handler",
105
+ # Path resolution
106
+ "PathResolver",
107
+ "resolve_path",
108
+ # Backward compatibility
109
+ "get_cache_manager",
110
+ "get_performance_monitor",
111
+ # Module metadata
112
+ "MCP_UTILS_CAPABILITIES",
113
+ ]
@@ -0,0 +1,569 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Error Handler for MCP Server
4
+
5
+ This module provides comprehensive error handling and recovery
6
+ mechanisms for the MCP server operations.
7
+ """
8
+
9
+ import asyncio
10
+ import logging
11
+ import traceback
12
+ from collections.abc import Callable
13
+ from datetime import datetime
14
+ from enum import Enum
15
+ from functools import wraps
16
+ from typing import Any
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class ErrorSeverity(Enum):
22
+ """Error severity levels"""
23
+
24
+ LOW = "low"
25
+ MEDIUM = "medium"
26
+ HIGH = "high"
27
+ CRITICAL = "critical"
28
+
29
+
30
+ class ErrorCategory(Enum):
31
+ """Error categories for classification"""
32
+
33
+ FILE_ACCESS = "file_access"
34
+ PARSING = "parsing"
35
+ ANALYSIS = "analysis"
36
+ NETWORK = "network"
37
+ VALIDATION = "validation"
38
+ RESOURCE = "resource"
39
+ CONFIGURATION = "configuration"
40
+ UNKNOWN = "unknown"
41
+
42
+
43
+ class MCPError(Exception):
44
+ """Base exception class for MCP-specific errors"""
45
+
46
+ def __init__(
47
+ self,
48
+ message: str,
49
+ category: ErrorCategory = ErrorCategory.UNKNOWN,
50
+ severity: ErrorSeverity = ErrorSeverity.MEDIUM,
51
+ details: dict[str, Any] | None = None,
52
+ recoverable: bool = True,
53
+ ):
54
+ super().__init__(message)
55
+ self.message = message
56
+ self.category = category
57
+ self.severity = severity
58
+ self.details = details or {}
59
+ self.recoverable = recoverable
60
+ self.timestamp = datetime.now()
61
+
62
+ def to_dict(self) -> dict[str, Any]:
63
+ """Convert error to dictionary representation"""
64
+ return {
65
+ "error_type": self.__class__.__name__,
66
+ "message": self.message,
67
+ "category": self.category.value,
68
+ "severity": self.severity.value,
69
+ "details": self.details,
70
+ "recoverable": self.recoverable,
71
+ "timestamp": self.timestamp.isoformat(),
72
+ }
73
+
74
+
75
+ class FileAccessError(MCPError):
76
+ """Error related to file access operations"""
77
+
78
+ def __init__(self, message: str, file_path: str, **kwargs: Any):
79
+ super().__init__(
80
+ message,
81
+ category=ErrorCategory.FILE_ACCESS,
82
+ details={"file_path": file_path},
83
+ **kwargs,
84
+ )
85
+
86
+
87
+ class ParsingError(MCPError):
88
+ """Error related to code parsing operations"""
89
+
90
+ def __init__(
91
+ self,
92
+ message: str,
93
+ file_path: str,
94
+ language: str | None = None,
95
+ **kwargs: Any,
96
+ ):
97
+ super().__init__(
98
+ message,
99
+ category=ErrorCategory.PARSING,
100
+ details={"file_path": file_path, "language": language},
101
+ **kwargs,
102
+ )
103
+
104
+
105
+ class AnalysisError(MCPError):
106
+ """Error related to code analysis operations"""
107
+
108
+ def __init__(self, message: str, operation: str, **kwargs: Any):
109
+ super().__init__(
110
+ message,
111
+ category=ErrorCategory.ANALYSIS,
112
+ details={"operation": operation},
113
+ **kwargs,
114
+ )
115
+
116
+
117
+ class ValidationError(MCPError):
118
+ """Error related to input validation"""
119
+
120
+ def __init__(self, message: str, field: str, value: Any = None, **kwargs: Any):
121
+ super().__init__(
122
+ message,
123
+ category=ErrorCategory.VALIDATION,
124
+ details={
125
+ "field": field,
126
+ "value": str(value) if value is not None else None,
127
+ },
128
+ **kwargs,
129
+ )
130
+
131
+
132
+ class ResourceError(MCPError):
133
+ """Error related to resource operations"""
134
+
135
+ def __init__(self, message: str, resource_uri: str, **kwargs: Any):
136
+ super().__init__(
137
+ message,
138
+ category=ErrorCategory.RESOURCE,
139
+ details={"resource_uri": resource_uri},
140
+ **kwargs,
141
+ )
142
+
143
+
144
+ class ErrorHandler:
145
+ """
146
+ Centralized error handling and recovery system
147
+
148
+ Provides error classification, logging, recovery mechanisms,
149
+ and error statistics for the MCP server.
150
+ """
151
+
152
+ def __init__(self) -> None:
153
+ """Initialize error handler"""
154
+ self.error_counts: dict[str, int] = {}
155
+ self.error_history: list[dict[str, Any]] = []
156
+ self.max_history_size = 1000
157
+ self.recovery_strategies: dict[type[Exception], Callable] = {}
158
+
159
+ # Register default recovery strategies
160
+ self._register_default_strategies()
161
+
162
+ logger.info("Error handler initialized")
163
+
164
+ def _register_default_strategies(self) -> None:
165
+ """Register default error recovery strategies"""
166
+
167
+ def file_not_found_recovery(
168
+ error: FileNotFoundError, context: dict[str, Any]
169
+ ) -> dict[str, Any]:
170
+ """Recovery strategy for file not found errors"""
171
+ return {
172
+ "error": f"File not found: {context.get('file_path', 'unknown')}",
173
+ "suggestion": "Please check the file path and ensure the file exists",
174
+ "recoverable": True,
175
+ }
176
+
177
+ def permission_error_recovery(
178
+ error: PermissionError, context: dict[str, Any]
179
+ ) -> dict[str, Any]:
180
+ """Recovery strategy for permission errors"""
181
+ return {
182
+ "error": f"Permission denied: {context.get('file_path', 'unknown')}",
183
+ "suggestion": "Please check file permissions or run with appropriate privileges",
184
+ "recoverable": False,
185
+ }
186
+
187
+ def value_error_recovery(
188
+ error: ValueError, context: dict[str, Any]
189
+ ) -> dict[str, Any]:
190
+ """Recovery strategy for value errors"""
191
+ return {
192
+ "error": f"Invalid value: {str(error)}",
193
+ "suggestion": "Please check input parameters and try again",
194
+ "recoverable": True,
195
+ }
196
+
197
+ self.recovery_strategies.update(
198
+ {
199
+ FileNotFoundError: file_not_found_recovery,
200
+ PermissionError: permission_error_recovery,
201
+ ValueError: value_error_recovery,
202
+ }
203
+ )
204
+
205
+ def register_recovery_strategy(
206
+ self,
207
+ exception_type: type[Exception],
208
+ strategy: Callable[[Exception, dict[str, Any]], dict[str, Any]],
209
+ ) -> None:
210
+ """
211
+ Register a custom recovery strategy for an exception type
212
+
213
+ Args:
214
+ exception_type: Type of exception to handle
215
+ strategy: Recovery function that takes (exception, context) and returns recovery info
216
+ """
217
+ self.recovery_strategies[exception_type] = strategy
218
+ logger.debug(f"Registered recovery strategy for {exception_type.__name__}")
219
+
220
+ def handle_error(
221
+ self,
222
+ error: Exception,
223
+ context: dict[str, Any] | None = None,
224
+ operation: str = "unknown",
225
+ ) -> dict[str, Any]:
226
+ """
227
+ Handle an error with classification, logging, and recovery
228
+
229
+ Args:
230
+ error: The exception that occurred
231
+ context: Additional context information
232
+ operation: Name of the operation that failed
233
+
234
+ Returns:
235
+ Error information dictionary with recovery suggestions
236
+ """
237
+ context = context or {}
238
+
239
+ # Classify error
240
+ if isinstance(error, MCPError):
241
+ error_info = error.to_dict()
242
+ else:
243
+ error_info = self._classify_error(error, context, operation)
244
+
245
+ # Log error
246
+ self._log_error(error, error_info, context, operation)
247
+
248
+ # Update statistics
249
+ self._update_error_stats(error_info)
250
+
251
+ # Add to history
252
+ self._add_to_history(error_info, context, operation)
253
+
254
+ # Attempt recovery
255
+ recovery_info = self._attempt_recovery(error, context)
256
+ if recovery_info:
257
+ error_info.update(recovery_info)
258
+
259
+ return error_info
260
+
261
+ def _classify_error(
262
+ self, error: Exception, context: dict[str, Any], operation: str
263
+ ) -> dict[str, Any]:
264
+ """
265
+ Classify a generic exception into MCP error categories
266
+
267
+ Args:
268
+ error: The exception to classify
269
+ context: Error context
270
+ operation: Operation that failed
271
+
272
+ Returns:
273
+ Error information dictionary
274
+ """
275
+ error_type = type(error).__name__
276
+ message = str(error)
277
+
278
+ # Determine category based on error type and context
279
+ category = ErrorCategory.UNKNOWN
280
+ severity = ErrorSeverity.MEDIUM
281
+ recoverable = True
282
+
283
+ if isinstance(
284
+ error, FileNotFoundError | IsADirectoryError | NotADirectoryError
285
+ ):
286
+ category = ErrorCategory.FILE_ACCESS
287
+ severity = ErrorSeverity.MEDIUM
288
+ elif isinstance(error, PermissionError):
289
+ category = ErrorCategory.FILE_ACCESS
290
+ severity = ErrorSeverity.HIGH
291
+ recoverable = False
292
+ elif isinstance(error, ValueError | TypeError):
293
+ category = ErrorCategory.VALIDATION
294
+ severity = ErrorSeverity.LOW
295
+ elif isinstance(error, OSError | IOError):
296
+ category = ErrorCategory.FILE_ACCESS
297
+ severity = ErrorSeverity.HIGH
298
+ elif isinstance(error, RuntimeError | AttributeError):
299
+ category = ErrorCategory.ANALYSIS
300
+ severity = ErrorSeverity.MEDIUM
301
+ elif isinstance(error, MemoryError):
302
+ category = ErrorCategory.RESOURCE
303
+ severity = ErrorSeverity.CRITICAL
304
+ recoverable = False
305
+ elif isinstance(error, asyncio.TimeoutError):
306
+ category = ErrorCategory.NETWORK
307
+ severity = ErrorSeverity.MEDIUM
308
+
309
+ return {
310
+ "error_type": error_type,
311
+ "message": message,
312
+ "category": category.value,
313
+ "severity": severity.value,
314
+ "details": context,
315
+ "recoverable": recoverable,
316
+ "timestamp": datetime.now().isoformat(),
317
+ "operation": operation,
318
+ "traceback": traceback.format_exc(),
319
+ }
320
+
321
+ def _log_error(
322
+ self,
323
+ error: Exception,
324
+ error_info: dict[str, Any],
325
+ context: dict[str, Any],
326
+ operation: str,
327
+ ) -> None:
328
+ """
329
+ Log error with appropriate level based on severity
330
+
331
+ Args:
332
+ error: The original exception
333
+ error_info: Classified error information
334
+ context: Error context
335
+ operation: Operation that failed
336
+ """
337
+ severity = error_info.get("severity", "medium")
338
+ message = f"Error in {operation}: {error_info['message']}"
339
+
340
+ if severity == "critical":
341
+ logger.critical(
342
+ message, extra={"error_info": error_info, "context": context}
343
+ )
344
+ elif severity == "high":
345
+ logger.error(message, extra={"error_info": error_info, "context": context})
346
+ elif severity == "medium":
347
+ logger.warning(
348
+ message, extra={"error_info": error_info, "context": context}
349
+ )
350
+ else:
351
+ logger.info(message, extra={"error_info": error_info, "context": context})
352
+
353
+ def _update_error_stats(self, error_info: dict[str, Any]) -> None:
354
+ """
355
+ Update error statistics
356
+
357
+ Args:
358
+ error_info: Error information
359
+ """
360
+ error_type = error_info.get("error_type", "Unknown")
361
+ category = error_info.get("category", "unknown")
362
+
363
+ # Count by type
364
+ self.error_counts[f"type:{error_type}"] = (
365
+ self.error_counts.get(f"type:{error_type}", 0) + 1
366
+ )
367
+
368
+ # Count by category
369
+ self.error_counts[f"category:{category}"] = (
370
+ self.error_counts.get(f"category:{category}", 0) + 1
371
+ )
372
+
373
+ # Count by severity
374
+ severity = error_info.get("severity", "medium")
375
+ self.error_counts[f"severity:{severity}"] = (
376
+ self.error_counts.get(f"severity:{severity}", 0) + 1
377
+ )
378
+
379
+ def _add_to_history(
380
+ self, error_info: dict[str, Any], context: dict[str, Any], operation: str
381
+ ) -> None:
382
+ """
383
+ Add error to history with size limit
384
+
385
+ Args:
386
+ error_info: Error information
387
+ context: Error context
388
+ operation: Operation that failed
389
+ """
390
+ history_entry = {**error_info, "context": context, "operation": operation}
391
+
392
+ self.error_history.append(history_entry)
393
+
394
+ # Maintain history size limit
395
+ if len(self.error_history) > self.max_history_size:
396
+ self.error_history = self.error_history[-self.max_history_size :]
397
+
398
+ def _attempt_recovery(
399
+ self, error: Exception, context: dict[str, Any]
400
+ ) -> dict[str, Any] | None:
401
+ """
402
+ Attempt to recover from error using registered strategies
403
+
404
+ Args:
405
+ error: The exception to recover from
406
+ context: Error context
407
+
408
+ Returns:
409
+ Recovery information or None
410
+ """
411
+ error_type = type(error)
412
+
413
+ # Try exact type match first
414
+ if error_type in self.recovery_strategies:
415
+ try:
416
+ result = self.recovery_strategies[error_type](error, context)
417
+ return result if result is not None else {}
418
+ except Exception as recovery_error:
419
+ logger.warning(f"Recovery strategy failed: {recovery_error}")
420
+
421
+ # Try parent class matches
422
+ for registered_type, strategy in self.recovery_strategies.items():
423
+ if isinstance(error, registered_type):
424
+ try:
425
+ result = strategy(error, context)
426
+ return result if result is not None else {}
427
+ except Exception as recovery_error:
428
+ logger.warning(f"Recovery strategy failed: {recovery_error}")
429
+
430
+ return None
431
+
432
+ def get_error_stats(self) -> dict[str, Any]:
433
+ """
434
+ Get error statistics
435
+
436
+ Returns:
437
+ Dictionary containing error statistics
438
+ """
439
+ total_errors = (
440
+ sum(self.error_counts.values()) // 3
441
+ ) # Divide by 3 because we count type, category, severity
442
+
443
+ return {
444
+ "total_errors": total_errors,
445
+ "error_counts": self.error_counts.copy(),
446
+ "recent_errors": len(
447
+ [
448
+ e
449
+ for e in self.error_history
450
+ if (datetime.now() - datetime.fromisoformat(e["timestamp"])).seconds
451
+ < 3600
452
+ ]
453
+ ),
454
+ "history_size": len(self.error_history),
455
+ }
456
+
457
+ def get_recent_errors(self, limit: int = 10) -> list[dict[str, Any]]:
458
+ """
459
+ Get recent errors from history
460
+
461
+ Args:
462
+ limit: Maximum number of errors to return
463
+
464
+ Returns:
465
+ List of recent error entries
466
+ """
467
+ return self.error_history[-limit:] if self.error_history else []
468
+
469
+ def clear_history(self) -> None:
470
+ """Clear error history and reset statistics"""
471
+ self.error_history.clear()
472
+ self.error_counts.clear()
473
+ logger.info("Error history and statistics cleared")
474
+
475
+
476
+ def handle_mcp_errors(
477
+ operation: str = "unknown",
478
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
479
+ """
480
+ Decorator for automatic error handling in MCP operations
481
+
482
+ Args:
483
+ operation: Name of the operation for logging
484
+
485
+ Returns:
486
+ Decorated function with error handling
487
+ """
488
+
489
+ def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
490
+ @wraps(func)
491
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
492
+ try:
493
+ return await func(*args, **kwargs)
494
+ except RuntimeError as e:
495
+ # Handle initialization errors specifically
496
+ if "not fully initialized" in str(e):
497
+ logger.warning(
498
+ f"Request received before initialization complete: {operation}"
499
+ )
500
+ raise MCPError(
501
+ "Server is still initializing. Please wait a moment and try again.",
502
+ category=ErrorCategory.CONFIGURATION,
503
+ severity=ErrorSeverity.LOW,
504
+ ) from e
505
+ # Handle other runtime errors normally
506
+ error_handler = get_error_handler()
507
+ context = {
508
+ "function": func.__name__,
509
+ "args": str(args)[:200], # Limit length
510
+ "kwargs": str(kwargs)[:200],
511
+ }
512
+ error_info = error_handler.handle_error(e, context, operation)
513
+ raise
514
+ except Exception as e:
515
+ error_handler = get_error_handler()
516
+ context = {
517
+ "function": func.__name__,
518
+ "args": str(args)[:200], # Limit length
519
+ "kwargs": str(kwargs)[:200],
520
+ }
521
+ error_info = error_handler.handle_error(e, context, operation)
522
+
523
+ # Re-raise as MCPError if not already
524
+ if not isinstance(e, MCPError):
525
+ raise AnalysisError(
526
+ f"Operation failed: {error_info['message']}",
527
+ operation=operation,
528
+ severity=ErrorSeverity(error_info["severity"]),
529
+ ) from e
530
+ raise
531
+
532
+ @wraps(func)
533
+ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
534
+ try:
535
+ return func(*args, **kwargs)
536
+ except Exception as e:
537
+ error_handler = get_error_handler()
538
+ context = {
539
+ "function": func.__name__,
540
+ "args": str(args)[:200],
541
+ "kwargs": str(kwargs)[:200],
542
+ }
543
+ error_info = error_handler.handle_error(e, context, operation)
544
+
545
+ if not isinstance(e, MCPError):
546
+ raise AnalysisError(
547
+ f"Operation failed: {error_info['message']}",
548
+ operation=operation,
549
+ severity=ErrorSeverity(error_info["severity"]),
550
+ ) from e
551
+ raise
552
+
553
+ return async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapper
554
+
555
+ return decorator
556
+
557
+
558
+ # Global error handler instance
559
+ _error_handler = ErrorHandler()
560
+
561
+
562
+ def get_error_handler() -> ErrorHandler:
563
+ """
564
+ Get the global error handler instance
565
+
566
+ Returns:
567
+ Global error handler
568
+ """
569
+ return _error_handler