mcp-code-indexer 4.2.19__py3-none-any.whl → 4.2.20__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/cleanup_manager.py +12 -12
- mcp_code_indexer/database/database.py +54 -34
- mcp_code_indexer/database/exceptions.py +2 -0
- mcp_code_indexer/database/retry_executor.py +1 -0
- mcp_code_indexer/logging_config.py +8 -3
- mcp_code_indexer/server/mcp_server.py +36 -36
- {mcp_code_indexer-4.2.19.dist-info → mcp_code_indexer-4.2.20.dist-info}/METADATA +3 -3
- {mcp_code_indexer-4.2.19.dist-info → mcp_code_indexer-4.2.20.dist-info}/RECORD +11 -11
- {mcp_code_indexer-4.2.19.dist-info → mcp_code_indexer-4.2.20.dist-info}/WHEEL +0 -0
- {mcp_code_indexer-4.2.19.dist-info → mcp_code_indexer-4.2.20.dist-info}/entry_points.txt +0 -0
- {mcp_code_indexer-4.2.19.dist-info → mcp_code_indexer-4.2.20.dist-info}/licenses/LICENSE +0 -0
|
@@ -51,10 +51,8 @@ class CleanupManager:
|
|
|
51
51
|
"""
|
|
52
52
|
cleanup_timestamp = int(time.time())
|
|
53
53
|
|
|
54
|
-
async
|
|
55
|
-
|
|
56
|
-
) as db:
|
|
57
|
-
cursor = await db.execute(
|
|
54
|
+
async def operation(conn: Any) -> bool:
|
|
55
|
+
cursor = await conn.execute(
|
|
58
56
|
"""
|
|
59
57
|
UPDATE file_descriptions
|
|
60
58
|
SET to_be_cleaned = ?
|
|
@@ -62,11 +60,13 @@ class CleanupManager:
|
|
|
62
60
|
""",
|
|
63
61
|
(cleanup_timestamp, project_id, file_path),
|
|
64
62
|
)
|
|
65
|
-
await db.commit()
|
|
66
|
-
|
|
67
63
|
# Check if any rows were affected
|
|
68
64
|
return cursor.rowcount > 0
|
|
69
65
|
|
|
66
|
+
return await self.db_manager.execute_transaction_with_retry(
|
|
67
|
+
operation, "mark_file_for_cleanup"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
70
|
async def mark_files_for_cleanup(
|
|
71
71
|
self, project_id: str, file_paths: List[str]
|
|
72
72
|
) -> int:
|
|
@@ -117,10 +117,8 @@ class CleanupManager:
|
|
|
117
117
|
Returns:
|
|
118
118
|
True if file was restored, False if file not found
|
|
119
119
|
"""
|
|
120
|
-
async
|
|
121
|
-
|
|
122
|
-
) as db:
|
|
123
|
-
cursor = await db.execute(
|
|
120
|
+
async def operation(conn: Any) -> bool:
|
|
121
|
+
cursor = await conn.execute(
|
|
124
122
|
"""
|
|
125
123
|
UPDATE file_descriptions
|
|
126
124
|
SET to_be_cleaned = NULL
|
|
@@ -128,10 +126,12 @@ class CleanupManager:
|
|
|
128
126
|
""",
|
|
129
127
|
(project_id, file_path),
|
|
130
128
|
)
|
|
131
|
-
await db.commit()
|
|
132
|
-
|
|
133
129
|
return cursor.rowcount > 0
|
|
134
130
|
|
|
131
|
+
return await self.db_manager.execute_transaction_with_retry(
|
|
132
|
+
operation, "restore_file_from_cleanup"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
135
|
async def get_files_to_be_cleaned(self, project_id: str) -> List[dict]:
|
|
136
136
|
"""
|
|
137
137
|
Get list of files marked for cleanup in a project.
|
|
@@ -450,11 +450,23 @@ class DatabaseManager:
|
|
|
450
450
|
}
|
|
451
451
|
},
|
|
452
452
|
)
|
|
453
|
-
|
|
453
|
+
# Shield rollback from cancellation to prevent leaked transactions
|
|
454
|
+
await asyncio.shield(conn.rollback())
|
|
454
455
|
raise
|
|
455
|
-
except
|
|
456
|
-
|
|
457
|
-
|
|
456
|
+
except BaseException as e:
|
|
457
|
+
# Catch BaseException to handle asyncio.CancelledError and ensure
|
|
458
|
+
# proper rollback on task cancellation. Shield the rollback to
|
|
459
|
+
# prevent cancellation from interrupting cleanup.
|
|
460
|
+
if isinstance(e, asyncio.CancelledError):
|
|
461
|
+
logger.warning(f"Transaction cancelled for {operation_name}")
|
|
462
|
+
else:
|
|
463
|
+
logger.error(f"Transaction failed for {operation_name}: {e}")
|
|
464
|
+
try:
|
|
465
|
+
await asyncio.shield(conn.rollback())
|
|
466
|
+
except Exception as rollback_error:
|
|
467
|
+
logger.error(
|
|
468
|
+
f"Rollback failed for {operation_name}: {rollback_error}"
|
|
469
|
+
)
|
|
458
470
|
raise
|
|
459
471
|
|
|
460
472
|
async def execute_transaction_with_retry(
|
|
@@ -638,8 +650,8 @@ class DatabaseManager:
|
|
|
638
650
|
|
|
639
651
|
async def create_project(self, project: Project) -> None:
|
|
640
652
|
"""Create a new project record."""
|
|
641
|
-
async
|
|
642
|
-
await
|
|
653
|
+
async def operation(conn: aiosqlite.Connection) -> None:
|
|
654
|
+
await conn.execute(
|
|
643
655
|
"""
|
|
644
656
|
INSERT INTO projects (id, name, aliases, created, last_accessed)
|
|
645
657
|
VALUES (?, ?, ?, ?, ?)
|
|
@@ -652,8 +664,9 @@ class DatabaseManager:
|
|
|
652
664
|
project.last_accessed,
|
|
653
665
|
),
|
|
654
666
|
)
|
|
655
|
-
|
|
656
|
-
|
|
667
|
+
|
|
668
|
+
await self.execute_transaction_with_retry(operation, "create_project")
|
|
669
|
+
logger.debug(f"Created project: {project.id}")
|
|
657
670
|
|
|
658
671
|
async def get_project(self, project_id: str) -> Optional[Project]:
|
|
659
672
|
"""Get project by ID."""
|
|
@@ -768,19 +781,20 @@ class DatabaseManager:
|
|
|
768
781
|
|
|
769
782
|
async def update_project_access_time(self, project_id: str) -> None:
|
|
770
783
|
"""Update the last accessed time for a project."""
|
|
771
|
-
async
|
|
772
|
-
|
|
773
|
-
) as db:
|
|
774
|
-
await db.execute(
|
|
784
|
+
async def operation(conn: aiosqlite.Connection) -> None:
|
|
785
|
+
await conn.execute(
|
|
775
786
|
"UPDATE projects SET last_accessed = ? WHERE id = ?",
|
|
776
787
|
(datetime.utcnow(), project_id),
|
|
777
788
|
)
|
|
778
|
-
|
|
789
|
+
|
|
790
|
+
await self.execute_transaction_with_retry(
|
|
791
|
+
operation, "update_project_access_time"
|
|
792
|
+
)
|
|
779
793
|
|
|
780
794
|
async def update_project(self, project: Project) -> None:
|
|
781
795
|
"""Update an existing project record."""
|
|
782
|
-
async
|
|
783
|
-
await
|
|
796
|
+
async def operation(conn: aiosqlite.Connection) -> None:
|
|
797
|
+
await conn.execute(
|
|
784
798
|
"""
|
|
785
799
|
UPDATE projects
|
|
786
800
|
SET name = ?, aliases = ?, last_accessed = ?
|
|
@@ -793,27 +807,28 @@ class DatabaseManager:
|
|
|
793
807
|
project.id,
|
|
794
808
|
),
|
|
795
809
|
)
|
|
796
|
-
|
|
797
|
-
|
|
810
|
+
|
|
811
|
+
await self.execute_transaction_with_retry(operation, "update_project")
|
|
812
|
+
logger.debug(f"Updated project: {project.id}")
|
|
798
813
|
|
|
799
814
|
async def set_project_vector_mode(self, project_id: str, enabled: bool) -> None:
|
|
800
815
|
"""Set the vector_mode for a specific project."""
|
|
801
|
-
async
|
|
802
|
-
|
|
803
|
-
) as db:
|
|
804
|
-
await db.execute(
|
|
816
|
+
async def operation(conn: aiosqlite.Connection) -> None:
|
|
817
|
+
await conn.execute(
|
|
805
818
|
"UPDATE projects SET vector_mode = ? WHERE id = ?",
|
|
806
819
|
(int(enabled), project_id),
|
|
807
820
|
)
|
|
808
821
|
|
|
809
822
|
# Check if the project was actually updated
|
|
810
|
-
cursor = await
|
|
823
|
+
cursor = await conn.execute("SELECT changes()")
|
|
811
824
|
changes = await cursor.fetchone()
|
|
812
825
|
if changes[0] == 0:
|
|
813
826
|
raise DatabaseError(f"Project not found: {project_id}")
|
|
814
827
|
|
|
815
|
-
|
|
816
|
-
|
|
828
|
+
await self.execute_transaction_with_retry(
|
|
829
|
+
operation, "set_project_vector_mode"
|
|
830
|
+
)
|
|
831
|
+
logger.debug(f"Set vector_mode={enabled} for project: {project_id}")
|
|
817
832
|
|
|
818
833
|
async def get_all_projects(self) -> List[Project]:
|
|
819
834
|
"""Get all projects in the database."""
|
|
@@ -1084,23 +1099,25 @@ class DatabaseManager:
|
|
|
1084
1099
|
"""Cache token count with TTL."""
|
|
1085
1100
|
expires = datetime.utcnow() + timedelta(hours=ttl_hours)
|
|
1086
1101
|
|
|
1087
|
-
async
|
|
1088
|
-
await
|
|
1102
|
+
async def operation(conn: aiosqlite.Connection) -> None:
|
|
1103
|
+
await conn.execute(
|
|
1089
1104
|
"""
|
|
1090
1105
|
INSERT OR REPLACE INTO token_cache (cache_key, token_count, expires)
|
|
1091
1106
|
VALUES (?, ?, ?)
|
|
1092
1107
|
""",
|
|
1093
1108
|
(cache_key, token_count, expires),
|
|
1094
1109
|
)
|
|
1095
|
-
|
|
1110
|
+
|
|
1111
|
+
await self.execute_transaction_with_retry(operation, "cache_token_count")
|
|
1096
1112
|
|
|
1097
1113
|
async def cleanup_expired_cache(self) -> None:
|
|
1098
1114
|
"""Remove expired cache entries."""
|
|
1099
|
-
async
|
|
1100
|
-
await
|
|
1115
|
+
async def operation(conn: aiosqlite.Connection) -> None:
|
|
1116
|
+
await conn.execute(
|
|
1101
1117
|
"DELETE FROM token_cache WHERE expires < ?", (datetime.utcnow(),)
|
|
1102
1118
|
)
|
|
1103
|
-
|
|
1119
|
+
|
|
1120
|
+
await self.execute_transaction_with_retry(operation, "cleanup_expired_cache")
|
|
1104
1121
|
|
|
1105
1122
|
# Utility operations
|
|
1106
1123
|
|
|
@@ -1320,9 +1337,9 @@ class DatabaseManager:
|
|
|
1320
1337
|
Returns:
|
|
1321
1338
|
Number of projects removed
|
|
1322
1339
|
"""
|
|
1323
|
-
async
|
|
1340
|
+
async def operation(conn: aiosqlite.Connection) -> int:
|
|
1324
1341
|
# Find projects with no descriptions and no overview
|
|
1325
|
-
cursor = await
|
|
1342
|
+
cursor = await conn.execute(
|
|
1326
1343
|
"""
|
|
1327
1344
|
SELECT p.id, p.name
|
|
1328
1345
|
FROM projects p
|
|
@@ -1343,14 +1360,17 @@ class DatabaseManager:
|
|
|
1343
1360
|
project_name = project["name"]
|
|
1344
1361
|
|
|
1345
1362
|
# Remove from projects table (cascading will handle related data)
|
|
1346
|
-
await
|
|
1363
|
+
await conn.execute("DELETE FROM projects WHERE id = ?", (project_id,))
|
|
1347
1364
|
removed_count += 1
|
|
1348
1365
|
|
|
1349
1366
|
logger.info(f"Removed empty project: {project_name} (ID: {project_id})")
|
|
1350
1367
|
|
|
1351
|
-
await db.commit()
|
|
1352
1368
|
return removed_count
|
|
1353
1369
|
|
|
1370
|
+
return await self.execute_transaction_with_retry(
|
|
1371
|
+
operation, "cleanup_empty_projects"
|
|
1372
|
+
)
|
|
1373
|
+
|
|
1354
1374
|
async def get_project_map_data(
|
|
1355
1375
|
self, project_identifier: str
|
|
1356
1376
|
) -> Optional[Dict[str, Any]]:
|
|
@@ -191,6 +191,7 @@ def classify_sqlite_error(error: Exception, operation_name: str = "") -> Databas
|
|
|
191
191
|
"database is locked",
|
|
192
192
|
"sqlite_locked",
|
|
193
193
|
"attempt to write a readonly database",
|
|
194
|
+
"timeout waiting for database lock",
|
|
194
195
|
]
|
|
195
196
|
):
|
|
196
197
|
lock_type = (
|
|
@@ -297,6 +298,7 @@ def is_retryable_error(error: Exception) -> bool:
|
|
|
297
298
|
"sqlite_busy",
|
|
298
299
|
"sqlite_locked",
|
|
299
300
|
"cannot start a transaction within a transaction",
|
|
301
|
+
"timeout waiting for database lock",
|
|
300
302
|
]
|
|
301
303
|
|
|
302
304
|
return any(pattern in error_message for pattern in retryable_patterns)
|
|
@@ -34,16 +34,21 @@ def setup_logging(
|
|
|
34
34
|
Returns:
|
|
35
35
|
Configured root logger
|
|
36
36
|
"""
|
|
37
|
-
# Get root logger
|
|
37
|
+
# Get root logger - set to DEBUG so all logs reach handlers.
|
|
38
|
+
# Each handler filters to its own level.
|
|
38
39
|
root_logger = logging.getLogger()
|
|
39
|
-
root_logger.setLevel(
|
|
40
|
+
root_logger.setLevel(logging.DEBUG)
|
|
40
41
|
|
|
41
42
|
# Clear existing handlers
|
|
42
43
|
root_logger.handlers.clear()
|
|
43
44
|
|
|
44
45
|
# Console handler (stderr to avoid interfering with MCP stdout)
|
|
45
46
|
console_handler = logging.StreamHandler(sys.stderr)
|
|
46
|
-
|
|
47
|
+
# Force console logging to at least WARNING to prevent stderr buffer blocking
|
|
48
|
+
# when MCP clients don't consume stderr fast enough. File logging captures
|
|
49
|
+
# everything (DEBUG+) for detailed diagnostics.
|
|
50
|
+
requested_level = getattr(logging, log_level.upper())
|
|
51
|
+
console_handler.setLevel(max(requested_level, logging.WARNING))
|
|
47
52
|
|
|
48
53
|
# Use structured formatter for all handlers
|
|
49
54
|
structured_formatter = StructuredFormatter()
|
|
@@ -240,10 +240,10 @@ class MCPCodeIndexServer:
|
|
|
240
240
|
try:
|
|
241
241
|
result = json.loads(repaired)
|
|
242
242
|
if isinstance(result, dict):
|
|
243
|
-
logger.
|
|
243
|
+
logger.debug(
|
|
244
244
|
f"Successfully repaired JSON. Original: {json_str[:100]}..."
|
|
245
245
|
)
|
|
246
|
-
logger.
|
|
246
|
+
logger.debug(f"Repaired: {repaired[:100]}...")
|
|
247
247
|
return result
|
|
248
248
|
else:
|
|
249
249
|
raise ValueError(
|
|
@@ -793,8 +793,8 @@ class MCPCodeIndexServer:
|
|
|
793
793
|
|
|
794
794
|
start_time = time.time()
|
|
795
795
|
|
|
796
|
-
logger.
|
|
797
|
-
logger.
|
|
796
|
+
logger.debug(f"=== MCP Tool Call: {name} ===")
|
|
797
|
+
logger.debug(f"Arguments: {', '.join(arguments.keys())}")
|
|
798
798
|
|
|
799
799
|
# Map tool names to handler methods
|
|
800
800
|
tool_handlers = {
|
|
@@ -828,7 +828,7 @@ class MCPCodeIndexServer:
|
|
|
828
828
|
result = await wrapped_handler(arguments)
|
|
829
829
|
|
|
830
830
|
elapsed_time = time.time() - start_time
|
|
831
|
-
logger.
|
|
831
|
+
logger.debug(
|
|
832
832
|
f"MCP Tool '{name}' completed successfully in {elapsed_time:.2f}s"
|
|
833
833
|
)
|
|
834
834
|
|
|
@@ -887,7 +887,7 @@ class MCPCodeIndexServer:
|
|
|
887
887
|
if datetime.utcnow() - project.last_accessed > timedelta(minutes=5):
|
|
888
888
|
await db_manager.update_project_access_time(project.id)
|
|
889
889
|
|
|
890
|
-
logger.
|
|
890
|
+
logger.debug(
|
|
891
891
|
f"Using existing local project: {project.name} (ID: {project.id})"
|
|
892
892
|
)
|
|
893
893
|
return project.id
|
|
@@ -904,7 +904,7 @@ class MCPCodeIndexServer:
|
|
|
904
904
|
last_accessed=datetime.utcnow(),
|
|
905
905
|
)
|
|
906
906
|
await db_manager.create_project(project)
|
|
907
|
-
logger.
|
|
907
|
+
logger.debug(
|
|
908
908
|
f"Created new local project: {project_name} (ID: {project_id})"
|
|
909
909
|
)
|
|
910
910
|
return project_id
|
|
@@ -932,7 +932,7 @@ class MCPCodeIndexServer:
|
|
|
932
932
|
last_accessed=datetime.utcnow(),
|
|
933
933
|
)
|
|
934
934
|
await db_manager.create_project(project)
|
|
935
|
-
logger.
|
|
935
|
+
logger.debug(
|
|
936
936
|
f"Created new global project: {normalized_name} (ID: {project_id})"
|
|
937
937
|
)
|
|
938
938
|
|
|
@@ -975,7 +975,7 @@ class MCPCodeIndexServer:
|
|
|
975
975
|
if score > best_score:
|
|
976
976
|
best_score = score
|
|
977
977
|
best_match = project
|
|
978
|
-
logger.
|
|
978
|
+
logger.debug(
|
|
979
979
|
f"Match for project {project.name} "
|
|
980
980
|
f"(score: {score}, factors: {match_factors})"
|
|
981
981
|
)
|
|
@@ -983,7 +983,7 @@ class MCPCodeIndexServer:
|
|
|
983
983
|
# If only name matches, check file similarity for potential matches
|
|
984
984
|
elif score == 1 and "name" in match_factors:
|
|
985
985
|
if await self._check_file_similarity(project, folder_path):
|
|
986
|
-
logger.
|
|
986
|
+
logger.debug(
|
|
987
987
|
f"File similarity match for project {project.name} "
|
|
988
988
|
f"(factor: {match_factors[0]})"
|
|
989
989
|
)
|
|
@@ -1060,7 +1060,7 @@ class MCPCodeIndexServer:
|
|
|
1060
1060
|
project_aliases.append(folder_path)
|
|
1061
1061
|
project.aliases = project_aliases
|
|
1062
1062
|
should_update = True
|
|
1063
|
-
logger.
|
|
1063
|
+
logger.debug(
|
|
1064
1064
|
f"Added new folder alias to project {project.name}: {folder_path}"
|
|
1065
1065
|
)
|
|
1066
1066
|
|
|
@@ -1098,17 +1098,17 @@ class MCPCodeIndexServer:
|
|
|
1098
1098
|
self, arguments: Dict[str, Any]
|
|
1099
1099
|
) -> Dict[str, Any]:
|
|
1100
1100
|
"""Handle update_file_description tool calls."""
|
|
1101
|
-
logger.
|
|
1102
|
-
logger.
|
|
1101
|
+
logger.debug(f"Updating file description for: {arguments['filePath']}")
|
|
1102
|
+
logger.debug(f"Project: {arguments.get('projectName', 'Unknown')}")
|
|
1103
1103
|
|
|
1104
1104
|
description_length = len(arguments.get("description", ""))
|
|
1105
|
-
logger.
|
|
1105
|
+
logger.debug(f"Description length: {description_length} characters")
|
|
1106
1106
|
|
|
1107
1107
|
folder_path = arguments["folderPath"]
|
|
1108
1108
|
db_manager = await self.db_factory.get_database_manager(folder_path)
|
|
1109
1109
|
project_id = await self._get_or_create_project_id(arguments)
|
|
1110
1110
|
|
|
1111
|
-
logger.
|
|
1111
|
+
logger.debug(f"Resolved project_id: {project_id}")
|
|
1112
1112
|
|
|
1113
1113
|
file_desc = FileDescription(
|
|
1114
1114
|
id=None, # Will be set by database
|
|
@@ -1124,7 +1124,7 @@ class MCPCodeIndexServer:
|
|
|
1124
1124
|
|
|
1125
1125
|
await db_manager.create_file_description(file_desc)
|
|
1126
1126
|
|
|
1127
|
-
logger.
|
|
1127
|
+
logger.debug(f"Successfully updated description for: {arguments['filePath']}")
|
|
1128
1128
|
|
|
1129
1129
|
return {
|
|
1130
1130
|
"success": True,
|
|
@@ -1137,17 +1137,17 @@ class MCPCodeIndexServer:
|
|
|
1137
1137
|
self, arguments: Dict[str, Any]
|
|
1138
1138
|
) -> Dict[str, Any]:
|
|
1139
1139
|
"""Handle check_codebase_size tool calls."""
|
|
1140
|
-
logger.
|
|
1140
|
+
logger.debug(
|
|
1141
1141
|
f"Checking codebase size for: {arguments.get('projectName', 'Unknown')}"
|
|
1142
1142
|
)
|
|
1143
|
-
logger.
|
|
1143
|
+
logger.debug(f"Folder path: {arguments.get('folderPath', 'Unknown')}")
|
|
1144
1144
|
|
|
1145
1145
|
folder_path = arguments["folderPath"]
|
|
1146
1146
|
db_manager = await self.db_factory.get_database_manager(folder_path)
|
|
1147
1147
|
project_id = await self._get_or_create_project_id(arguments)
|
|
1148
1148
|
folder_path_obj = Path(folder_path)
|
|
1149
1149
|
|
|
1150
|
-
logger.
|
|
1150
|
+
logger.debug(f"Resolved project_id: {project_id}")
|
|
1151
1151
|
|
|
1152
1152
|
# Run cleanup if needed (respects 30-minute cooldown)
|
|
1153
1153
|
cleaned_up_count = await self._run_cleanup_if_needed(
|
|
@@ -1155,17 +1155,17 @@ class MCPCodeIndexServer:
|
|
|
1155
1155
|
)
|
|
1156
1156
|
|
|
1157
1157
|
# Get file descriptions for this project (after cleanup)
|
|
1158
|
-
logger.
|
|
1158
|
+
logger.debug("Retrieving file descriptions...")
|
|
1159
1159
|
file_descriptions = await db_manager.get_all_file_descriptions(
|
|
1160
1160
|
project_id=project_id
|
|
1161
1161
|
)
|
|
1162
|
-
logger.
|
|
1162
|
+
logger.debug(f"Found {len(file_descriptions)} file descriptions")
|
|
1163
1163
|
|
|
1164
1164
|
# Use provided token limit or fall back to server default
|
|
1165
1165
|
token_limit = arguments.get("tokenLimit", self.token_limit)
|
|
1166
1166
|
|
|
1167
1167
|
# Calculate total tokens for descriptions (offload to executor to avoid blocking)
|
|
1168
|
-
logger.
|
|
1168
|
+
logger.debug("Calculating total token count...")
|
|
1169
1169
|
loop = asyncio.get_running_loop()
|
|
1170
1170
|
descriptions_tokens = await loop.run_in_executor(
|
|
1171
1171
|
None,
|
|
@@ -1197,16 +1197,16 @@ class MCPCodeIndexServer:
|
|
|
1197
1197
|
else:
|
|
1198
1198
|
recommendation = "use_search"
|
|
1199
1199
|
|
|
1200
|
-
logger.
|
|
1200
|
+
logger.debug(
|
|
1201
1201
|
f"Codebase analysis complete: {total_tokens} tokens total "
|
|
1202
1202
|
f"({descriptions_tokens} descriptions + {overview_tokens} overview), "
|
|
1203
1203
|
f"{len(file_descriptions)} files"
|
|
1204
1204
|
)
|
|
1205
|
-
logger.
|
|
1205
|
+
logger.debug(
|
|
1206
1206
|
f"Size assessment: {'LARGE' if is_large else 'SMALL'} "
|
|
1207
1207
|
f"(limit: {token_limit})"
|
|
1208
1208
|
)
|
|
1209
|
-
logger.
|
|
1209
|
+
logger.debug(f"Recommendation: {recommendation}")
|
|
1210
1210
|
|
|
1211
1211
|
return {
|
|
1212
1212
|
"fileDescriptionTokens": descriptions_tokens,
|
|
@@ -1223,29 +1223,29 @@ class MCPCodeIndexServer:
|
|
|
1223
1223
|
self, arguments: Dict[str, Any]
|
|
1224
1224
|
) -> Dict[str, Any]:
|
|
1225
1225
|
"""Handle find_missing_descriptions tool calls."""
|
|
1226
|
-
logger.
|
|
1226
|
+
logger.debug(
|
|
1227
1227
|
f"Finding missing descriptions for: "
|
|
1228
1228
|
f"{arguments.get('projectName', 'Unknown')}"
|
|
1229
1229
|
)
|
|
1230
|
-
logger.
|
|
1230
|
+
logger.debug(f"Folder path: {arguments.get('folderPath', 'Unknown')}")
|
|
1231
1231
|
|
|
1232
1232
|
folder_path = arguments["folderPath"]
|
|
1233
1233
|
db_manager = await self.db_factory.get_database_manager(folder_path)
|
|
1234
1234
|
project_id = await self._get_or_create_project_id(arguments)
|
|
1235
1235
|
folder_path_obj = Path(folder_path)
|
|
1236
1236
|
|
|
1237
|
-
logger.
|
|
1237
|
+
logger.debug(f"Resolved project_id: {project_id}")
|
|
1238
1238
|
|
|
1239
1239
|
# Get existing file descriptions
|
|
1240
|
-
logger.
|
|
1240
|
+
logger.debug("Retrieving existing file descriptions...")
|
|
1241
1241
|
existing_descriptions = await db_manager.get_all_file_descriptions(
|
|
1242
1242
|
project_id=project_id
|
|
1243
1243
|
)
|
|
1244
1244
|
existing_paths = {desc.file_path for desc in existing_descriptions}
|
|
1245
|
-
logger.
|
|
1245
|
+
logger.debug(f"Found {len(existing_paths)} existing descriptions")
|
|
1246
1246
|
|
|
1247
1247
|
# Scan directory for files
|
|
1248
|
-
logger.
|
|
1248
|
+
logger.debug(f"Scanning project directory: {folder_path_obj}")
|
|
1249
1249
|
scanner = FileScanner(folder_path_obj)
|
|
1250
1250
|
if not await scanner.is_valid_project_directory_async():
|
|
1251
1251
|
logger.error(
|
|
@@ -1258,25 +1258,25 @@ class MCPCodeIndexServer:
|
|
|
1258
1258
|
missing_files = await scanner.find_missing_files_async(existing_paths)
|
|
1259
1259
|
missing_paths = [scanner.get_relative_path(f) for f in missing_files]
|
|
1260
1260
|
|
|
1261
|
-
logger.
|
|
1261
|
+
logger.debug(f"Found {len(missing_paths)} files without descriptions")
|
|
1262
1262
|
|
|
1263
1263
|
# Apply randomization if specified
|
|
1264
1264
|
randomize = arguments.get("randomize", False)
|
|
1265
1265
|
if randomize:
|
|
1266
1266
|
random.shuffle(missing_paths)
|
|
1267
|
-
logger.
|
|
1267
|
+
logger.debug("Randomized file order for parallel processing")
|
|
1268
1268
|
|
|
1269
1269
|
# Apply limit if specified
|
|
1270
1270
|
limit = arguments.get("limit")
|
|
1271
1271
|
total_missing = len(missing_paths)
|
|
1272
1272
|
if limit is not None and isinstance(limit, int) and limit > 0:
|
|
1273
1273
|
missing_paths = missing_paths[:limit]
|
|
1274
|
-
logger.
|
|
1274
|
+
logger.debug(f"Applied limit {limit}, returning {len(missing_paths)} files")
|
|
1275
1275
|
|
|
1276
1276
|
# Get project stats (offload to executor to avoid blocking)
|
|
1277
1277
|
loop = asyncio.get_running_loop()
|
|
1278
1278
|
stats = await loop.run_in_executor(None, scanner.get_project_stats)
|
|
1279
|
-
logger.
|
|
1279
|
+
logger.debug(f"Project stats: {stats.get('total_files', 0)} total files")
|
|
1280
1280
|
|
|
1281
1281
|
return {
|
|
1282
1282
|
"missingFiles": missing_paths,
|
|
@@ -1646,7 +1646,7 @@ class MCPCodeIndexServer:
|
|
|
1646
1646
|
project_name = arguments["projectName"]
|
|
1647
1647
|
folder_path = arguments["folderPath"]
|
|
1648
1648
|
|
|
1649
|
-
logger.
|
|
1649
|
+
logger.debug(
|
|
1650
1650
|
"Processing find_similar_code request",
|
|
1651
1651
|
extra={
|
|
1652
1652
|
"structured_data": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-code-indexer
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.20
|
|
4
4
|
Summary: MCP server that tracks file descriptions across codebases, enabling AI agents to efficiently navigate and understand code through searchable summaries and token-aware overviews.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -49,8 +49,8 @@ Description-Content-Type: text/markdown
|
|
|
49
49
|
|
|
50
50
|
# MCP Code Indexer 🚀
|
|
51
51
|
|
|
52
|
-
[](https://badge.fury.io/py/mcp-code-indexer)
|
|
53
|
+
[](https://pypi.org/project/mcp-code-indexer/)
|
|
54
54
|
[](https://opensource.org/licenses/MIT)
|
|
55
55
|
|
|
56
56
|
A production-ready **Model Context Protocol (MCP) server** that revolutionizes how AI agents navigate and understand codebases. Built for high-concurrency environments with advanced database resilience, the server provides instant access to intelligent descriptions, semantic search, and context-aware recommendations while maintaining 800+ writes/sec throughput.
|
|
@@ -2,23 +2,23 @@ mcp_code_indexer/__init__.py,sha256=IG3xW6SGlqnOCnGOCio_05IxTXWRWqaJF4y25ChbYMA,
|
|
|
2
2
|
mcp_code_indexer/__main__.py,sha256=4Edinoe0ug43hobuLYcjTmGp2YJnlFYN4_8iKvUBJ0Q,213
|
|
3
3
|
mcp_code_indexer/ask_handler.py,sha256=EiobL_Daii7wwcHvwDDtxLvqjZvhCJWvz_PIiRm14V4,9106
|
|
4
4
|
mcp_code_indexer/claude_api_handler.py,sha256=_PVhHxwVyY3ojNp-tIGp73nWD8MYMmIlCMIwKNMJXi8,13845
|
|
5
|
-
mcp_code_indexer/cleanup_manager.py,sha256
|
|
5
|
+
mcp_code_indexer/cleanup_manager.py,sha256=40YjboL0AzA7K79WPd83oNQFxVc5g4Fg8Oe7myP4cBo,9835
|
|
6
6
|
mcp_code_indexer/commands/__init__.py,sha256=141U722dS_NnFTZyxTPipzhXKdU21kCv-mcrN4djyHo,45
|
|
7
7
|
mcp_code_indexer/commands/makelocal.py,sha256=T_44so96jcs1FNlft9E3nAq0LlOzQLhjLd8P31Myfr4,9140
|
|
8
8
|
mcp_code_indexer/data/stop_words_english.txt,sha256=feRGP8WG5hQPo-wZN5ralJiSv1CGw4h3010NBJnJ0Z8,6344
|
|
9
9
|
mcp_code_indexer/database/__init__.py,sha256=aPq_aaRp0aSwOBIq9GkuMNjmLxA411zg2vhdrAuHm-w,38
|
|
10
10
|
mcp_code_indexer/database/connection_health.py,sha256=jZr3tCbfjUJujdXe_uxtm1N4c31dMV4euiSY4ulamOE,25497
|
|
11
|
-
mcp_code_indexer/database/database.py,sha256=
|
|
11
|
+
mcp_code_indexer/database/database.py,sha256=sxl9OMmPo7bQVDrJOGpPLqshOWF-5xeEr2A-ZxdhG0s,61716
|
|
12
12
|
mcp_code_indexer/database/database_factory.py,sha256=VMw0tlutGgZoTI7Q_PCuFy5zAimq2xuMtDFAlF_FKtc,4316
|
|
13
|
-
mcp_code_indexer/database/exceptions.py,sha256=
|
|
13
|
+
mcp_code_indexer/database/exceptions.py,sha256=DieJQ2WVH7_CLmFhwDFdRH1XMED0WpZwLRWS1kovnLg,10567
|
|
14
14
|
mcp_code_indexer/database/models.py,sha256=w1U9zMGNt0LQeCiifYeXKW_Cia9BKV5uPChbOve-FZY,13467
|
|
15
15
|
mcp_code_indexer/database/path_resolver.py,sha256=1Ubx6Ly5F2dnvhbdN3tqyowBHslABXpoA6wgL4BQYGo,3461
|
|
16
|
-
mcp_code_indexer/database/retry_executor.py,sha256=
|
|
16
|
+
mcp_code_indexer/database/retry_executor.py,sha256=cQ8o6sh6ks7AOVo0CKJ7GQr7-GbOOHDq_g784m2Tz78,14458
|
|
17
17
|
mcp_code_indexer/deepask_handler.py,sha256=qI9h_Me5WQAbt3hzzDG8XDBMZlnvx-I9R7OsmO_o8aA,18497
|
|
18
18
|
mcp_code_indexer/error_handler.py,sha256=ylciEM-cR7E8Gmd8cfh5olcllJm0FnaYBGH86yayFic,12530
|
|
19
19
|
mcp_code_indexer/file_scanner.py,sha256=r8WR1Or1pbB63PmUIno5XYwBrmYg9xr37Ef6zmtt4yQ,15538
|
|
20
20
|
mcp_code_indexer/git_hook_handler.py,sha256=sTtZV3-Yy1Evt06R5NZclELeepM4Ia9OQoR2O6BK3Hk,45517
|
|
21
|
-
mcp_code_indexer/logging_config.py,sha256=
|
|
21
|
+
mcp_code_indexer/logging_config.py,sha256=yTnmUXpBe7KujMFzWopiYBep29rpj3p9VvpqrNEgkZs,10843
|
|
22
22
|
mcp_code_indexer/main.py,sha256=byM0Y9EwDa0616dEkx2p_1rUdJmDNeKAG41o5_AJago,41084
|
|
23
23
|
mcp_code_indexer/middleware/__init__.py,sha256=UCEPzOlZldlqFzYEfrXw1HvCDvY1jpLvyaDGUzVr2aw,368
|
|
24
24
|
mcp_code_indexer/middleware/auth.py,sha256=4HkHMDZBNsyPA1VE8qF7pRNKbqG4xIDZjllENbgynxI,7258
|
|
@@ -33,7 +33,7 @@ mcp_code_indexer/migrations/005_remove_git_remotes.sql,sha256=vT84AaV1hyN4zq5W67
|
|
|
33
33
|
mcp_code_indexer/migrations/006_vector_mode.sql,sha256=kN-UBPGoagqtpxpGEjdz-V3hevPAXxAdNmxF4iIPsY8,7448
|
|
34
34
|
mcp_code_indexer/query_preprocessor.py,sha256=vi23sK2ffs4T5PGY7lHrbCBDL421AlPz2dldqX_3JKA,5491
|
|
35
35
|
mcp_code_indexer/server/__init__.py,sha256=16xMcuriUOBlawRqWNBk6niwrvtv_JD5xvI36X1Vsmk,41
|
|
36
|
-
mcp_code_indexer/server/mcp_server.py,sha256=
|
|
36
|
+
mcp_code_indexer/server/mcp_server.py,sha256=I_oHoUv00ZBrikjBxHwoOvuHIeCGMUcKxXOyT5TAge0,85150
|
|
37
37
|
mcp_code_indexer/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4,sha256=Ijkht27pm96ZW3_3OFE-7xAPtR0YyTWXoRO8_-hlsqc,1681126
|
|
38
38
|
mcp_code_indexer/token_counter.py,sha256=e6WsyCEWMMSkMwLbcVtr5e8vEqh-kFqNmiJErCNdqHE,8220
|
|
39
39
|
mcp_code_indexer/tools/__init__.py,sha256=m01mxML2UdD7y5rih_XNhNSCMzQTz7WQ_T1TeOcYlnE,49
|
|
@@ -65,8 +65,8 @@ mcp_code_indexer/vector_mode/services/vector_mode_tools_service.py,sha256=K1_STy
|
|
|
65
65
|
mcp_code_indexer/vector_mode/services/vector_storage_service.py,sha256=JI3VUc2mG8xZ_YqOvfKJivuMi4imeBLr2UFVrWgDWhk,21193
|
|
66
66
|
mcp_code_indexer/vector_mode/types.py,sha256=M4lUF43FzjiVUezoRqozx_u0g1-xrX9qcRcRn-u65yw,1222
|
|
67
67
|
mcp_code_indexer/vector_mode/utils.py,sha256=XtHrpOw0QJ0EjdzJ85jrbkmHy8Slkq_t7hz-q4RP-10,1283
|
|
68
|
-
mcp_code_indexer-4.2.
|
|
69
|
-
mcp_code_indexer-4.2.
|
|
70
|
-
mcp_code_indexer-4.2.
|
|
71
|
-
mcp_code_indexer-4.2.
|
|
72
|
-
mcp_code_indexer-4.2.
|
|
68
|
+
mcp_code_indexer-4.2.20.dist-info/METADATA,sha256=K9Ku7UI9xcu1HU9WEqlLCacCWmfRONU1YgdZV5kxpzo,27689
|
|
69
|
+
mcp_code_indexer-4.2.20.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
|
|
70
|
+
mcp_code_indexer-4.2.20.dist-info/entry_points.txt,sha256=UABj7HZ0mC6rvF22gxaz2LLNLGQShTrFmp5u00iUtvo,67
|
|
71
|
+
mcp_code_indexer-4.2.20.dist-info/licenses/LICENSE,sha256=JN9dyPPgYwH9C-UjYM7FLNZjQ6BF7kAzpF3_4PwY4rY,1086
|
|
72
|
+
mcp_code_indexer-4.2.20.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|