mcp-code-indexer 4.2.18__py3-none-any.whl → 4.2.19__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/database/database.py +18 -14
- mcp_code_indexer/file_scanner.py +10 -0
- mcp_code_indexer/server/mcp_server.py +29 -13
- {mcp_code_indexer-4.2.18.dist-info → mcp_code_indexer-4.2.19.dist-info}/METADATA +3 -3
- {mcp_code_indexer-4.2.18.dist-info → mcp_code_indexer-4.2.19.dist-info}/RECORD +8 -8
- {mcp_code_indexer-4.2.18.dist-info → mcp_code_indexer-4.2.19.dist-info}/WHEEL +0 -0
- {mcp_code_indexer-4.2.18.dist-info → mcp_code_indexer-4.2.19.dist-info}/entry_points.txt +0 -0
- {mcp_code_indexer-4.2.18.dist-info → mcp_code_indexer-4.2.19.dist-info}/licenses/LICENSE +0 -0
|
@@ -396,7 +396,7 @@ class DatabaseManager:
|
|
|
396
396
|
async def get_immediate_transaction(
|
|
397
397
|
self,
|
|
398
398
|
operation_name: str = "immediate_transaction",
|
|
399
|
-
timeout_seconds: float =
|
|
399
|
+
timeout_seconds: Optional[float] = None,
|
|
400
400
|
) -> AsyncIterator[aiosqlite.Connection]:
|
|
401
401
|
"""
|
|
402
402
|
Get a database connection with BEGIN IMMEDIATE transaction and
|
|
@@ -407,8 +407,10 @@ class DatabaseManager:
|
|
|
407
407
|
|
|
408
408
|
Args:
|
|
409
409
|
operation_name: Name of the operation for monitoring
|
|
410
|
-
timeout_seconds: Transaction timeout in seconds
|
|
410
|
+
timeout_seconds: Transaction timeout in seconds (defaults to
|
|
411
|
+
self.timeout if None)
|
|
411
412
|
"""
|
|
413
|
+
actual_timeout = timeout_seconds if timeout_seconds is not None else self.timeout
|
|
412
414
|
import time
|
|
413
415
|
acquire_start = time.monotonic()
|
|
414
416
|
async with self.get_write_connection_with_retry(operation_name) as conn:
|
|
@@ -420,7 +422,7 @@ class DatabaseManager:
|
|
|
420
422
|
# Start immediate transaction with timeout
|
|
421
423
|
begin_start = time.monotonic()
|
|
422
424
|
await asyncio.wait_for(
|
|
423
|
-
conn.execute("BEGIN IMMEDIATE"), timeout=
|
|
425
|
+
conn.execute("BEGIN IMMEDIATE"), timeout=actual_timeout
|
|
424
426
|
)
|
|
425
427
|
begin_time = time.monotonic() - begin_start
|
|
426
428
|
logger.debug(
|
|
@@ -436,14 +438,14 @@ class DatabaseManager:
|
|
|
436
438
|
except asyncio.TimeoutError:
|
|
437
439
|
logger.warning(
|
|
438
440
|
(
|
|
439
|
-
f"Transaction timeout after {
|
|
441
|
+
f"Transaction timeout after {actual_timeout}s for "
|
|
440
442
|
f"{operation_name}"
|
|
441
443
|
),
|
|
442
444
|
extra={
|
|
443
445
|
"structured_data": {
|
|
444
446
|
"transaction_timeout": {
|
|
445
447
|
"operation": operation_name,
|
|
446
|
-
"timeout_seconds":
|
|
448
|
+
"timeout_seconds": actual_timeout,
|
|
447
449
|
}
|
|
448
450
|
}
|
|
449
451
|
},
|
|
@@ -460,7 +462,7 @@ class DatabaseManager:
|
|
|
460
462
|
operation_func: Callable[[aiosqlite.Connection], Any],
|
|
461
463
|
operation_name: str = "transaction_operation",
|
|
462
464
|
max_retries: int = 3,
|
|
463
|
-
timeout_seconds: float =
|
|
465
|
+
timeout_seconds: Optional[float] = None,
|
|
464
466
|
) -> Any:
|
|
465
467
|
"""
|
|
466
468
|
Execute a database operation within a transaction with automatic
|
|
@@ -475,7 +477,8 @@ class DatabaseManager:
|
|
|
475
477
|
operation_name: Name of the operation for logging
|
|
476
478
|
max_retries: Maximum retry attempts (overrides default retry
|
|
477
479
|
executor config)
|
|
478
|
-
timeout_seconds: Transaction timeout in seconds
|
|
480
|
+
timeout_seconds: Transaction timeout in seconds (defaults to
|
|
481
|
+
self.timeout if None)
|
|
479
482
|
|
|
480
483
|
Returns:
|
|
481
484
|
Result from operation_func
|
|
@@ -489,6 +492,7 @@ class DatabaseManager:
|
|
|
489
492
|
my_operation, "insert_data"
|
|
490
493
|
)
|
|
491
494
|
"""
|
|
495
|
+
actual_timeout = timeout_seconds if timeout_seconds is not None else self.timeout
|
|
492
496
|
|
|
493
497
|
async def execute_transaction() -> Any:
|
|
494
498
|
"""Inner function to execute transaction - retried by executor."""
|
|
@@ -496,11 +500,11 @@ class DatabaseManager:
|
|
|
496
500
|
start_time = time.monotonic()
|
|
497
501
|
logger.debug(
|
|
498
502
|
f"[{operation_name}] Starting transaction "
|
|
499
|
-
f"(timeout={
|
|
503
|
+
f"(timeout={actual_timeout}s, pool_size={len(self._connection_pool)})"
|
|
500
504
|
)
|
|
501
505
|
try:
|
|
502
506
|
async with self.get_immediate_transaction(
|
|
503
|
-
operation_name,
|
|
507
|
+
operation_name, actual_timeout
|
|
504
508
|
) as conn:
|
|
505
509
|
lock_acquired_time = time.monotonic()
|
|
506
510
|
logger.debug(
|
|
@@ -523,7 +527,7 @@ class DatabaseManager:
|
|
|
523
527
|
if self._metrics_collector:
|
|
524
528
|
self._metrics_collector.record_operation(
|
|
525
529
|
operation_name,
|
|
526
|
-
|
|
530
|
+
actual_timeout * 1000, # Convert to ms
|
|
527
531
|
True,
|
|
528
532
|
len(self._connection_pool),
|
|
529
533
|
)
|
|
@@ -555,7 +559,7 @@ class DatabaseManager:
|
|
|
555
559
|
if self._metrics_collector:
|
|
556
560
|
self._metrics_collector.record_operation(
|
|
557
561
|
operation_name,
|
|
558
|
-
|
|
562
|
+
actual_timeout * 1000,
|
|
559
563
|
False,
|
|
560
564
|
len(self._connection_pool),
|
|
561
565
|
)
|
|
@@ -565,7 +569,7 @@ class DatabaseManager:
|
|
|
565
569
|
elapsed = time.monotonic() - start_time
|
|
566
570
|
logger.warning(
|
|
567
571
|
f"[{operation_name}] Timeout after {elapsed*1000:.1f}ms "
|
|
568
|
-
f"waiting for database lock (timeout={
|
|
572
|
+
f"waiting for database lock (timeout={actual_timeout}s)"
|
|
569
573
|
)
|
|
570
574
|
if self._metrics_collector:
|
|
571
575
|
self._metrics_collector.record_locking_event(
|
|
@@ -605,7 +609,7 @@ class DatabaseManager:
|
|
|
605
609
|
if self._metrics_collector:
|
|
606
610
|
self._metrics_collector.record_operation(
|
|
607
611
|
operation_name,
|
|
608
|
-
|
|
612
|
+
actual_timeout * 1000,
|
|
609
613
|
False,
|
|
610
614
|
len(self._connection_pool),
|
|
611
615
|
)
|
|
@@ -624,7 +628,7 @@ class DatabaseManager:
|
|
|
624
628
|
if self._metrics_collector:
|
|
625
629
|
self._metrics_collector.record_operation(
|
|
626
630
|
operation_name,
|
|
627
|
-
|
|
631
|
+
actual_timeout * 1000,
|
|
628
632
|
False,
|
|
629
633
|
len(self._connection_pool),
|
|
630
634
|
)
|
mcp_code_indexer/file_scanner.py
CHANGED
|
@@ -499,3 +499,13 @@ class FileScanner:
|
|
|
499
499
|
"""
|
|
500
500
|
loop = asyncio.get_running_loop()
|
|
501
501
|
return await loop.run_in_executor(None, self.find_missing_files, existing_paths)
|
|
502
|
+
|
|
503
|
+
async def is_valid_project_directory_async(self) -> bool:
|
|
504
|
+
"""
|
|
505
|
+
Async version of is_valid_project_directory running in a thread.
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
True if the directory exists and is accessible
|
|
509
|
+
"""
|
|
510
|
+
loop = asyncio.get_running_loop()
|
|
511
|
+
return await loop.run_in_executor(None, self.is_valid_project_directory)
|
|
@@ -1002,10 +1002,10 @@ class MCPCodeIndexServer:
|
|
|
1002
1002
|
try:
|
|
1003
1003
|
# Get files currently in the folder
|
|
1004
1004
|
scanner = FileScanner(Path(folder_path))
|
|
1005
|
-
if not scanner.
|
|
1005
|
+
if not await scanner.is_valid_project_directory_async():
|
|
1006
1006
|
return False
|
|
1007
1007
|
|
|
1008
|
-
current_files = scanner.
|
|
1008
|
+
current_files = await scanner.scan_directory_async()
|
|
1009
1009
|
current_basenames = {f.name for f in current_files}
|
|
1010
1010
|
|
|
1011
1011
|
if not current_basenames:
|
|
@@ -1164,17 +1164,22 @@ class MCPCodeIndexServer:
|
|
|
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
|
-
# Calculate total tokens for descriptions
|
|
1167
|
+
# Calculate total tokens for descriptions (offload to executor to avoid blocking)
|
|
1168
1168
|
logger.info("Calculating total token count...")
|
|
1169
|
-
|
|
1169
|
+
loop = asyncio.get_running_loop()
|
|
1170
|
+
descriptions_tokens = await loop.run_in_executor(
|
|
1171
|
+
None,
|
|
1172
|
+
self.token_counter.calculate_codebase_tokens,
|
|
1170
1173
|
file_descriptions
|
|
1171
1174
|
)
|
|
1172
1175
|
|
|
1173
|
-
# Get overview tokens if available
|
|
1176
|
+
# Get overview tokens if available (offload to executor to avoid blocking)
|
|
1174
1177
|
overview = await db_manager.get_project_overview(project_id)
|
|
1175
1178
|
overview_tokens = 0
|
|
1176
1179
|
if overview and overview.overview:
|
|
1177
|
-
overview_tokens =
|
|
1180
|
+
overview_tokens = await loop.run_in_executor(
|
|
1181
|
+
None, self.token_counter.count_tokens, overview.overview
|
|
1182
|
+
)
|
|
1178
1183
|
|
|
1179
1184
|
total_tokens = descriptions_tokens + overview_tokens
|
|
1180
1185
|
is_large = total_tokens > token_limit
|
|
@@ -1242,7 +1247,7 @@ class MCPCodeIndexServer:
|
|
|
1242
1247
|
# Scan directory for files
|
|
1243
1248
|
logger.info(f"Scanning project directory: {folder_path_obj}")
|
|
1244
1249
|
scanner = FileScanner(folder_path_obj)
|
|
1245
|
-
if not scanner.
|
|
1250
|
+
if not await scanner.is_valid_project_directory_async():
|
|
1246
1251
|
logger.error(
|
|
1247
1252
|
f"Invalid or inaccessible project directory: {folder_path_obj}"
|
|
1248
1253
|
)
|
|
@@ -1250,7 +1255,7 @@ class MCPCodeIndexServer:
|
|
|
1250
1255
|
"error": f"Invalid or inaccessible project directory: {folder_path_obj}"
|
|
1251
1256
|
}
|
|
1252
1257
|
|
|
1253
|
-
missing_files = scanner.
|
|
1258
|
+
missing_files = await scanner.find_missing_files_async(existing_paths)
|
|
1254
1259
|
missing_paths = [scanner.get_relative_path(f) for f in missing_files]
|
|
1255
1260
|
|
|
1256
1261
|
logger.info(f"Found {len(missing_paths)} files without descriptions")
|
|
@@ -1268,8 +1273,9 @@ class MCPCodeIndexServer:
|
|
|
1268
1273
|
missing_paths = missing_paths[:limit]
|
|
1269
1274
|
logger.info(f"Applied limit {limit}, returning {len(missing_paths)} files")
|
|
1270
1275
|
|
|
1271
|
-
# Get project stats
|
|
1272
|
-
|
|
1276
|
+
# Get project stats (offload to executor to avoid blocking)
|
|
1277
|
+
loop = asyncio.get_running_loop()
|
|
1278
|
+
stats = await loop.run_in_executor(None, scanner.get_project_stats)
|
|
1273
1279
|
logger.info(f"Project stats: {stats.get('total_files', 0)} total files")
|
|
1274
1280
|
|
|
1275
1281
|
return {
|
|
@@ -1325,8 +1331,13 @@ class MCPCodeIndexServer:
|
|
|
1325
1331
|
project_id=project_id
|
|
1326
1332
|
)
|
|
1327
1333
|
|
|
1328
|
-
# Calculate total tokens
|
|
1329
|
-
|
|
1334
|
+
# Calculate total tokens (offload to executor to avoid blocking)
|
|
1335
|
+
loop = asyncio.get_running_loop()
|
|
1336
|
+
total_tokens = await loop.run_in_executor(
|
|
1337
|
+
None,
|
|
1338
|
+
self.token_counter.calculate_codebase_tokens,
|
|
1339
|
+
file_descriptions
|
|
1340
|
+
)
|
|
1330
1341
|
is_large = self.token_counter.is_large_codebase(total_tokens)
|
|
1331
1342
|
|
|
1332
1343
|
# Always build and return the folder structure - if the AI called this
|
|
@@ -1422,7 +1433,12 @@ class MCPCodeIndexServer:
|
|
|
1422
1433
|
)
|
|
1423
1434
|
|
|
1424
1435
|
total_files = len(file_descriptions)
|
|
1425
|
-
|
|
1436
|
+
loop = asyncio.get_running_loop()
|
|
1437
|
+
total_tokens = await loop.run_in_executor(
|
|
1438
|
+
None,
|
|
1439
|
+
self.token_counter.calculate_codebase_tokens,
|
|
1440
|
+
file_descriptions
|
|
1441
|
+
)
|
|
1426
1442
|
|
|
1427
1443
|
# Create overview record
|
|
1428
1444
|
overview = ProjectOverview(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-code-indexer
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.19
|
|
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.
|
|
@@ -8,7 +8,7 @@ mcp_code_indexer/commands/makelocal.py,sha256=T_44so96jcs1FNlft9E3nAq0LlOzQLhjLd
|
|
|
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=Dp2gArUXAY8q2agzj4QQlF-ZtdT0TRbHpGspZDI1jHg,60619
|
|
12
12
|
mcp_code_indexer/database/database_factory.py,sha256=VMw0tlutGgZoTI7Q_PCuFy5zAimq2xuMtDFAlF_FKtc,4316
|
|
13
13
|
mcp_code_indexer/database/exceptions.py,sha256=zciu7fDwF8y0Z4tkzTBFPPvXCvdnEJiA-Wd-cBLZBWw,10473
|
|
14
14
|
mcp_code_indexer/database/models.py,sha256=w1U9zMGNt0LQeCiifYeXKW_Cia9BKV5uPChbOve-FZY,13467
|
|
@@ -16,7 +16,7 @@ mcp_code_indexer/database/path_resolver.py,sha256=1Ubx6Ly5F2dnvhbdN3tqyowBHslABX
|
|
|
16
16
|
mcp_code_indexer/database/retry_executor.py,sha256=r7eKn_xDc6hKz9qs9z9Dg8gyq4uZgnyrgFmQFTyDhdo,14409
|
|
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
|
-
mcp_code_indexer/file_scanner.py,sha256=
|
|
19
|
+
mcp_code_indexer/file_scanner.py,sha256=r8WR1Or1pbB63PmUIno5XYwBrmYg9xr37Ef6zmtt4yQ,15538
|
|
20
20
|
mcp_code_indexer/git_hook_handler.py,sha256=sTtZV3-Yy1Evt06R5NZclELeepM4Ia9OQoR2O6BK3Hk,45517
|
|
21
21
|
mcp_code_indexer/logging_config.py,sha256=NNB5iKCc-Hdognf-UdjuEKB9H_e4UIp3QTVnf69Rc2k,10504
|
|
22
22
|
mcp_code_indexer/main.py,sha256=byM0Y9EwDa0616dEkx2p_1rUdJmDNeKAG41o5_AJago,41084
|
|
@@ -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=hALcOMxPBD0BD3Zpyfb8ifFXS2y2eoGWJ10foDde8R8,85114
|
|
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.19.dist-info/METADATA,sha256=9_qVYC29iCTLIuAV_uLVB4vbq6HEU_ZCo3vdaKZyQ78,27689
|
|
69
|
+
mcp_code_indexer-4.2.19.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
|
|
70
|
+
mcp_code_indexer-4.2.19.dist-info/entry_points.txt,sha256=UABj7HZ0mC6rvF22gxaz2LLNLGQShTrFmp5u00iUtvo,67
|
|
71
|
+
mcp_code_indexer-4.2.19.dist-info/licenses/LICENSE,sha256=JN9dyPPgYwH9C-UjYM7FLNZjQ6BF7kAzpF3_4PwY4rY,1086
|
|
72
|
+
mcp_code_indexer-4.2.19.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|