mcp-code-indexer 3.1.4__py3-none-any.whl → 3.1.5__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.
- mcp_code_indexer/__init__.py +8 -6
- mcp_code_indexer/ask_handler.py +105 -75
- mcp_code_indexer/claude_api_handler.py +125 -82
- mcp_code_indexer/cleanup_manager.py +107 -81
- mcp_code_indexer/database/connection_health.py +212 -161
- mcp_code_indexer/database/database.py +529 -415
- mcp_code_indexer/database/exceptions.py +167 -118
- mcp_code_indexer/database/models.py +54 -19
- mcp_code_indexer/database/retry_executor.py +139 -103
- mcp_code_indexer/deepask_handler.py +178 -140
- mcp_code_indexer/error_handler.py +88 -76
- mcp_code_indexer/file_scanner.py +163 -141
- mcp_code_indexer/git_hook_handler.py +352 -261
- mcp_code_indexer/logging_config.py +76 -94
- mcp_code_indexer/main.py +406 -320
- mcp_code_indexer/middleware/error_middleware.py +106 -71
- mcp_code_indexer/query_preprocessor.py +40 -40
- mcp_code_indexer/server/mcp_server.py +785 -470
- mcp_code_indexer/token_counter.py +54 -47
- {mcp_code_indexer-3.1.4.dist-info → mcp_code_indexer-3.1.5.dist-info}/METADATA +3 -3
- mcp_code_indexer-3.1.5.dist-info/RECORD +37 -0
- mcp_code_indexer-3.1.4.dist-info/RECORD +0 -37
- {mcp_code_indexer-3.1.4.dist-info → mcp_code_indexer-3.1.5.dist-info}/WHEEL +0 -0
- {mcp_code_indexer-3.1.4.dist-info → mcp_code_indexer-3.1.5.dist-info}/entry_points.txt +0 -0
- {mcp_code_indexer-3.1.4.dist-info → mcp_code_indexer-3.1.5.dist-info}/licenses/LICENSE +0 -0
- {mcp_code_indexer-3.1.4.dist-info → mcp_code_indexer-3.1.5.dist-info}/top_level.txt +0 -0
@@ -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
|
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__(
|
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
|
-
|
149
|
-
|
150
|
-
|
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,
|
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(
|
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 = [
|
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 = [
|
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
|