mcp-code-indexer 3.1.4__py3-none-any.whl → 3.1.6__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.
@@ -10,14 +10,14 @@ import logging
10
10
  import traceback
11
11
  from datetime import datetime
12
12
  from enum import Enum
13
- from typing import Any, Dict, Optional, Type, Union
14
- from pathlib import Path
13
+ from typing import Any, Dict, Optional
15
14
 
16
15
  from mcp import types
17
16
 
18
17
 
19
18
  class ErrorCategory(Enum):
20
19
  """Categories of errors for better handling and logging."""
20
+
21
21
  DATABASE = "database"
22
22
  FILE_SYSTEM = "file_system"
23
23
  VALIDATION = "validation"
@@ -30,13 +30,13 @@ class ErrorCategory(Enum):
30
30
 
31
31
  class MCPError(Exception):
32
32
  """Base exception for MCP-specific errors."""
33
-
33
+
34
34
  def __init__(
35
35
  self,
36
36
  message: str,
37
37
  category: ErrorCategory = ErrorCategory.INTERNAL,
38
38
  code: int = -32603, # JSON-RPC internal error
39
- details: Optional[Dict[str, Any]] = None
39
+ details: Optional[Dict[str, Any]] = None,
40
40
  ):
41
41
  super().__init__(message)
42
42
  self.message = message
@@ -48,53 +48,58 @@ class MCPError(Exception):
48
48
 
49
49
  class DatabaseError(MCPError):
50
50
  """Database-related errors."""
51
-
51
+
52
52
  def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
53
53
  super().__init__(
54
54
  message=message,
55
55
  category=ErrorCategory.DATABASE,
56
56
  code=-32603,
57
- details=details
57
+ details=details,
58
58
  )
59
59
 
60
60
 
61
61
  class ValidationError(MCPError):
62
62
  """Input validation errors."""
63
-
63
+
64
64
  def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
65
65
  super().__init__(
66
66
  message=message,
67
67
  category=ErrorCategory.VALIDATION,
68
68
  code=-32602, # Invalid params
69
- details=details
69
+ details=details,
70
70
  )
71
71
 
72
72
 
73
73
  class FileSystemError(MCPError):
74
74
  """File system access errors."""
75
-
76
- def __init__(self, message: str, path: Optional[str] = None, details: Optional[Dict[str, Any]] = None):
75
+
76
+ def __init__(
77
+ self,
78
+ message: str,
79
+ path: Optional[str] = None,
80
+ details: Optional[Dict[str, Any]] = None,
81
+ ):
77
82
  details = details or {}
78
83
  if path:
79
84
  details["path"] = path
80
-
85
+
81
86
  super().__init__(
82
87
  message=message,
83
88
  category=ErrorCategory.FILE_SYSTEM,
84
89
  code=-32603,
85
- details=details
90
+ details=details,
86
91
  )
87
92
 
88
93
 
89
94
  class ResourceError(MCPError):
90
95
  """Resource exhaustion or limit errors."""
91
-
96
+
92
97
  def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
93
98
  super().__init__(
94
99
  message=message,
95
100
  category=ErrorCategory.RESOURCE,
96
101
  code=-32603,
97
- details=details
102
+ details=details,
98
103
  )
99
104
 
100
105
 
@@ -102,30 +107,30 @@ class ErrorHandler:
102
107
  """
103
108
  Centralized error handling with structured logging and MCP compliance.
104
109
  """
105
-
110
+
106
111
  def __init__(self, logger: Optional[logging.Logger] = None):
107
112
  """Initialize error handler with optional logger."""
108
113
  self.logger = logger or logging.getLogger(__name__)
109
114
  self._setup_structured_logging()
110
-
115
+
111
116
  def _setup_structured_logging(self) -> None:
112
117
  """Configure structured JSON logging."""
113
118
  # Create structured formatter
114
119
  formatter = StructuredFormatter()
115
-
120
+
116
121
  # Apply to logger handlers
117
122
  for handler in self.logger.handlers:
118
123
  handler.setFormatter(formatter)
119
-
124
+
120
125
  def log_error(
121
126
  self,
122
127
  error: Exception,
123
128
  context: Optional[Dict[str, Any]] = None,
124
- tool_name: Optional[str] = None
129
+ tool_name: Optional[str] = None,
125
130
  ) -> None:
126
131
  """
127
132
  Log error with structured format.
128
-
133
+
129
134
  Args:
130
135
  error: Exception to log
131
136
  context: Additional context information
@@ -136,39 +141,38 @@ class ErrorHandler:
136
141
  "error_message": str(error),
137
142
  "timestamp": datetime.utcnow().isoformat(),
138
143
  }
139
-
144
+
140
145
  if tool_name:
141
146
  error_data["tool_name"] = tool_name
142
-
147
+
143
148
  if context:
144
149
  error_data["context"] = context
145
-
150
+
146
151
  if isinstance(error, MCPError):
147
- error_data.update({
148
- "category": error.category.value,
149
- "code": error.code,
150
- "details": error.details
151
- })
152
-
152
+ error_data.update(
153
+ {
154
+ "category": error.category.value,
155
+ "code": error.code,
156
+ "details": error.details,
157
+ }
158
+ )
159
+
153
160
  # Add traceback for debugging
154
161
  error_data["traceback"] = traceback.format_exc()
155
-
162
+
156
163
  self.logger.error("MCP Error occurred", extra={"structured_data": error_data})
157
-
164
+
158
165
  def create_mcp_error_response(
159
- self,
160
- error: Exception,
161
- tool_name: str,
162
- arguments: Dict[str, Any]
166
+ self, error: Exception, tool_name: str, arguments: Dict[str, Any]
163
167
  ) -> types.TextContent:
164
168
  """
165
169
  Create MCP-compliant error response.
166
-
170
+
167
171
  Args:
168
172
  error: Exception that occurred
169
173
  tool_name: Name of the tool
170
174
  arguments: Tool arguments
171
-
175
+
172
176
  Returns:
173
177
  MCP TextContent with error information
174
178
  """
@@ -178,10 +182,10 @@ class ErrorHandler:
178
182
  "code": error.code,
179
183
  "message": error.message,
180
184
  "category": error.category.value,
181
- "details": error.details
185
+ "details": error.details,
182
186
  },
183
187
  "tool": tool_name,
184
- "timestamp": error.timestamp.isoformat()
188
+ "timestamp": error.timestamp.isoformat(),
185
189
  }
186
190
  else:
187
191
  error_response = {
@@ -189,28 +193,28 @@ class ErrorHandler:
189
193
  "code": -32603, # Internal error
190
194
  "message": str(error),
191
195
  "category": ErrorCategory.INTERNAL.value,
192
- "details": {"type": type(error).__name__}
196
+ "details": {"type": type(error).__name__},
193
197
  },
194
198
  "tool": tool_name,
195
- "timestamp": datetime.utcnow().isoformat()
199
+ "timestamp": datetime.utcnow().isoformat(),
196
200
  }
197
-
201
+
198
202
  # Add arguments for debugging (excluding sensitive data)
199
203
  safe_arguments = self._sanitize_arguments(arguments)
200
204
  if safe_arguments:
201
205
  error_response["arguments"] = safe_arguments
202
-
206
+
203
207
  import json
208
+
204
209
  return types.TextContent(
205
- type="text",
206
- text=json.dumps(error_response, indent=2, default=str)
210
+ type="text", text=json.dumps(error_response, indent=2, default=str)
207
211
  )
208
-
212
+
209
213
  def _sanitize_arguments(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
210
214
  """Remove sensitive information from arguments."""
211
215
  sanitized = {}
212
216
  sensitive_keys = {"password", "token", "secret", "key", "auth"}
213
-
217
+
214
218
  for key, value in arguments.items():
215
219
  if any(sensitive in key.lower() for sensitive in sensitive_keys):
216
220
  sanitized[key] = "[REDACTED]"
@@ -218,18 +222,18 @@ class ErrorHandler:
218
222
  sanitized[key] = value[:100] + "..."
219
223
  else:
220
224
  sanitized[key] = value
221
-
225
+
222
226
  return sanitized
223
-
227
+
224
228
  async def handle_async_task_error(
225
229
  self,
226
230
  task: asyncio.Task,
227
231
  task_name: str,
228
- context: Optional[Dict[str, Any]] = None
232
+ context: Optional[Dict[str, Any]] = None,
229
233
  ) -> None:
230
234
  """
231
235
  Handle errors from async tasks.
232
-
236
+
233
237
  Args:
234
238
  task: The completed task
235
239
  task_name: Name of the task for logging
@@ -242,7 +246,7 @@ class ErrorHandler:
242
246
  self.log_error(
243
247
  exception,
244
248
  context={**(context or {}), "task_name": task_name},
245
- tool_name="async_task"
249
+ tool_name="async_task",
246
250
  )
247
251
  except Exception as e:
248
252
  self.logger.error(f"Error handling task error for {task_name}: {e}")
@@ -250,12 +254,12 @@ class ErrorHandler:
250
254
 
251
255
  class StructuredFormatter(logging.Formatter):
252
256
  """Custom formatter for structured JSON logging."""
253
-
257
+
254
258
  def format(self, record: logging.LogRecord) -> str:
255
259
  """Format log record as structured JSON."""
256
260
  import json
257
261
  from . import __version__
258
-
262
+
259
263
  log_data = {
260
264
  "timestamp": datetime.utcnow().isoformat(),
261
265
  "level": record.levelname,
@@ -264,46 +268,42 @@ class StructuredFormatter(logging.Formatter):
264
268
  "module": record.module,
265
269
  "function": record.funcName,
266
270
  "line": record.lineno,
267
- "version": __version__
271
+ "version": __version__,
268
272
  }
269
-
273
+
270
274
  # Add structured data if present
271
- if hasattr(record, 'structured_data'):
275
+ if hasattr(record, "structured_data"):
272
276
  log_data.update(record.structured_data)
273
-
277
+
274
278
  # Add exception info if present
275
279
  if record.exc_info:
276
280
  log_data["exception"] = self.formatException(record.exc_info)
277
-
281
+
278
282
  return json.dumps(log_data, default=str)
279
283
 
280
284
 
281
285
  def setup_error_handling(logger: logging.Logger) -> ErrorHandler:
282
286
  """
283
287
  Set up comprehensive error handling for the application.
284
-
288
+
285
289
  Args:
286
290
  logger: Logger instance to configure
287
-
291
+
288
292
  Returns:
289
293
  Configured ErrorHandler instance
290
294
  """
291
295
  error_handler = ErrorHandler(logger)
292
-
296
+
293
297
  # Set up asyncio exception handler
294
298
  def asyncio_exception_handler(loop, context):
295
- exception = context.get('exception')
299
+ exception = context.get("exception")
296
300
  if exception:
297
301
  error_handler.log_error(
298
- exception,
299
- context={
300
- "asyncio_context": context,
301
- "loop": str(loop)
302
- }
302
+ exception, context={"asyncio_context": context, "loop": str(loop)}
303
303
  )
304
304
  else:
305
305
  logger.error(f"Asyncio error: {context}")
306
-
306
+
307
307
  # Apply to current event loop if available
308
308
  try:
309
309
  loop = asyncio.get_running_loop()
@@ -311,14 +311,16 @@ def setup_error_handling(logger: logging.Logger) -> ErrorHandler:
311
311
  except RuntimeError:
312
312
  # No running loop, will be set when loop starts
313
313
  pass
314
-
314
+
315
315
  return error_handler
316
316
 
317
317
 
318
318
  # Decorators for common error handling patterns
319
319
 
320
+
320
321
  def handle_database_errors(func):
321
322
  """Decorator to handle database errors."""
323
+
322
324
  async def wrapper(*args, **kwargs):
323
325
  try:
324
326
  return await func(*args, **kwargs)
@@ -326,11 +328,13 @@ def handle_database_errors(func):
326
328
  if "database" in str(e).lower() or "sqlite" in str(e).lower():
327
329
  raise DatabaseError(f"Database operation failed: {e}") from e
328
330
  raise
331
+
329
332
  return wrapper
330
333
 
331
334
 
332
335
  def handle_file_errors(func):
333
336
  """Decorator to handle file system errors."""
337
+
334
338
  async def wrapper(*args, **kwargs):
335
339
  try:
336
340
  return await func(*args, **kwargs)
@@ -338,30 +342,38 @@ def handle_file_errors(func):
338
342
  raise FileSystemError(f"File system error: {e}") from e
339
343
  except Exception:
340
344
  raise
345
+
341
346
  return wrapper
342
347
 
343
348
 
344
349
  def validate_arguments(required_fields: list, optional_fields: list = None):
345
350
  """Decorator to validate tool arguments."""
351
+
346
352
  def decorator(func):
347
353
  async def wrapper(self, arguments: Dict[str, Any], *args, **kwargs):
348
354
  # Check required fields
349
- missing_fields = [field for field in required_fields if field not in arguments]
355
+ missing_fields = [
356
+ field for field in required_fields if field not in arguments
357
+ ]
350
358
  if missing_fields:
351
359
  raise ValidationError(
352
360
  f"Missing required fields: {', '.join(missing_fields)}",
353
- details={"missing_fields": missing_fields}
361
+ details={"missing_fields": missing_fields},
354
362
  )
355
-
363
+
356
364
  # Check for unexpected fields
357
365
  all_fields = set(required_fields + (optional_fields or []))
358
- unexpected_fields = [field for field in arguments.keys() if field not in all_fields]
366
+ unexpected_fields = [
367
+ field for field in arguments.keys() if field not in all_fields
368
+ ]
359
369
  if unexpected_fields:
360
370
  raise ValidationError(
361
371
  f"Unexpected fields: {', '.join(unexpected_fields)}",
362
- details={"unexpected_fields": unexpected_fields}
372
+ details={"unexpected_fields": unexpected_fields},
363
373
  )
364
-
374
+
365
375
  return await func(self, arguments, *args, **kwargs)
376
+
366
377
  return wrapper
378
+
367
379
  return decorator