mcp-code-indexer 1.0.3__py3-none-any.whl → 1.0.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.
@@ -6,7 +6,7 @@ intelligent codebase navigation through searchable file descriptions,
6
6
  token-aware overviews, and advanced merge capabilities.
7
7
  """
8
8
 
9
- __version__ = "1.0.3"
9
+ __version__ = "1.0.5"
10
10
  __author__ = "MCP Code Indexer Contributors"
11
11
  __email__ = ""
12
12
  __license__ = "MIT"
@@ -338,7 +338,7 @@ class FileScanner:
338
338
  Get statistics about the project directory.
339
339
 
340
340
  Returns:
341
- Dictionary with project statistics
341
+ Dictionary with project statistics for trackable files only
342
342
  """
343
343
  stats = {
344
344
  'total_files': 0,
@@ -349,8 +349,17 @@ class FileScanner:
349
349
  }
350
350
 
351
351
  try:
352
+ all_files_count = 0
352
353
  for file_path in self._walk_directory():
353
- stats['total_files'] += 1
354
+ all_files_count += 1
355
+
356
+ # Check if trackable first
357
+ if self.should_ignore_file(file_path):
358
+ stats['ignored_files'] += 1
359
+ continue
360
+
361
+ # Only process trackable files for detailed stats
362
+ stats['trackable_files'] += 1
354
363
 
355
364
  # Track file size
356
365
  try:
@@ -359,15 +368,12 @@ class FileScanner:
359
368
  except OSError:
360
369
  pass
361
370
 
362
- # Track extensions
371
+ # Track extensions for trackable files only
363
372
  ext = file_path.suffix.lower()
364
373
  stats['file_extensions'][ext] = stats['file_extensions'].get(ext, 0) + 1
365
-
366
- # Check if trackable
367
- if self.should_ignore_file(file_path):
368
- stats['ignored_files'] += 1
369
- else:
370
- stats['trackable_files'] += 1
374
+
375
+ # Total files is just trackable files
376
+ stats['total_files'] = stats['trackable_files']
371
377
 
372
378
  except Exception as e:
373
379
  logger.error(f"Error getting project stats: {e}")
@@ -16,6 +16,7 @@ from typing import Any, Dict, List, Optional
16
16
  from mcp import types
17
17
  from mcp.server import Server
18
18
  from mcp.server.stdio import stdio_server
19
+ from pydantic import ValidationError
19
20
 
20
21
  from mcp_code_indexer.database.database import DatabaseManager
21
22
  from mcp_code_indexer.file_scanner import FileScanner
@@ -651,54 +652,146 @@ class MCPCodeIndexServer:
651
652
  **result
652
653
  }
653
654
 
655
+ async def _run_session_with_retry(self, read_stream, write_stream, initialization_options) -> None:
656
+ """Run a single MCP session with error handling and retry logic."""
657
+ max_retries = 3
658
+ base_delay = 1.0 # seconds
659
+
660
+ for attempt in range(max_retries + 1):
661
+ try:
662
+ logger.info(f"Starting MCP server protocol session (attempt {attempt + 1})...")
663
+ await self.server.run(
664
+ read_stream,
665
+ write_stream,
666
+ initialization_options
667
+ )
668
+ logger.info("MCP server session completed normally")
669
+ return # Success, exit retry loop
670
+
671
+ except ValidationError as e:
672
+ # Handle malformed requests gracefully
673
+ logger.warning(f"Received malformed request (attempt {attempt + 1}): {e}", extra={
674
+ "structured_data": {
675
+ "error_type": "ValidationError",
676
+ "validation_errors": e.errors() if hasattr(e, 'errors') else str(e),
677
+ "attempt": attempt + 1,
678
+ "max_retries": max_retries
679
+ }
680
+ })
681
+
682
+ if attempt < max_retries:
683
+ delay = base_delay * (2 ** attempt) # Exponential backoff
684
+ logger.info(f"Retrying in {delay} seconds...")
685
+ await asyncio.sleep(delay)
686
+ else:
687
+ logger.error("Max retries exceeded for validation errors. Server will continue but this session failed.")
688
+ return
689
+
690
+ except (ConnectionError, BrokenPipeError, EOFError) as e:
691
+ # Handle client disconnection gracefully
692
+ logger.info(f"Client disconnected: {e}")
693
+ return
694
+
695
+ except Exception as e:
696
+ # Handle other exceptions with full logging
697
+ import traceback
698
+ if "unhandled errors in a TaskGroup" in str(e) and "ValidationError" in str(e):
699
+ # This is likely a ValidationError wrapped in a TaskGroup exception
700
+ logger.warning(f"Detected wrapped validation error (attempt {attempt + 1}): {e}", extra={
701
+ "structured_data": {
702
+ "error_type": type(e).__name__,
703
+ "error_message": str(e),
704
+ "attempt": attempt + 1,
705
+ "max_retries": max_retries,
706
+ "likely_validation_error": True
707
+ }
708
+ })
709
+
710
+ if attempt < max_retries:
711
+ delay = base_delay * (2 ** attempt)
712
+ logger.info(f"Retrying in {delay} seconds...")
713
+ await asyncio.sleep(delay)
714
+ else:
715
+ logger.error("Max retries exceeded for validation errors. Server will continue but this session failed.")
716
+ return
717
+ else:
718
+ # This is a genuine error, log and re-raise
719
+ logger.error(f"MCP server session error: {e}", extra={
720
+ "structured_data": {
721
+ "error_type": type(e).__name__,
722
+ "error_message": str(e),
723
+ "traceback": traceback.format_exc()
724
+ }
725
+ })
726
+ raise
727
+
654
728
  async def run(self) -> None:
655
- """Run the MCP server."""
729
+ """Run the MCP server with robust error handling."""
656
730
  logger.info("Starting server initialization...")
657
731
  await self.initialize()
658
732
  logger.info("Server initialization completed, starting MCP protocol...")
659
733
 
660
- try:
661
- async with stdio_server() as (read_stream, write_stream):
662
- logger.info("stdio_server context established")
663
- initialization_options = self.server.create_initialization_options()
664
- logger.debug(f"Initialization options: {initialization_options}")
734
+ max_retries = 5
735
+ base_delay = 2.0 # seconds
736
+
737
+ for attempt in range(max_retries + 1):
738
+ try:
739
+ async with stdio_server() as (read_stream, write_stream):
740
+ logger.info(f"stdio_server context established (attempt {attempt + 1})")
741
+ initialization_options = self.server.create_initialization_options()
742
+ logger.debug(f"Initialization options: {initialization_options}")
743
+
744
+ await self._run_session_with_retry(read_stream, write_stream, initialization_options)
745
+ return # Success, exit retry loop
746
+
747
+ except KeyboardInterrupt:
748
+ logger.info("Server stopped by user interrupt")
749
+ return
665
750
 
666
- try:
667
- logger.info("Starting MCP server protocol session...")
668
- await self.server.run(
669
- read_stream,
670
- write_stream,
671
- initialization_options
672
- )
673
- logger.info("MCP server session completed normally")
674
- except Exception as e:
675
- # Log the error with full traceback for debugging
676
- import traceback
677
- logger.error(f"MCP server session error: {e}", extra={
751
+ except Exception as e:
752
+ import traceback
753
+
754
+ # Check if this is a wrapped validation error
755
+ error_str = str(e)
756
+ is_validation_error = (
757
+ "ValidationError" in error_str or
758
+ "Field required" in error_str or
759
+ "Input should be" in error_str or
760
+ "pydantic_core._pydantic_core.ValidationError" in error_str
761
+ )
762
+
763
+ if is_validation_error:
764
+ logger.warning(f"Detected validation error in session (attempt {attempt + 1}): Malformed client request", extra={
765
+ "structured_data": {
766
+ "error_type": "ValidationError",
767
+ "error_message": "Client sent malformed request (likely missing clientInfo)",
768
+ "attempt": attempt + 1,
769
+ "max_retries": max_retries,
770
+ "will_retry": attempt < max_retries
771
+ }
772
+ })
773
+
774
+ if attempt < max_retries:
775
+ delay = base_delay * (2 ** min(attempt, 3)) # Cap exponential growth
776
+ logger.info(f"Retrying server in {delay} seconds...")
777
+ await asyncio.sleep(delay)
778
+ continue
779
+ else:
780
+ logger.warning("Max retries exceeded for validation errors. Server is robust against malformed requests.")
781
+ return
782
+ else:
783
+ # This is a genuine fatal error
784
+ logger.error(f"Fatal server error: {e}", extra={
678
785
  "structured_data": {
679
786
  "error_type": type(e).__name__,
680
787
  "error_message": str(e),
681
788
  "traceback": traceback.format_exc()
682
789
  }
683
790
  })
684
- # Re-raise to let the MCP framework handle protocol-level errors
685
791
  raise
686
-
687
- except KeyboardInterrupt:
688
- logger.info("Server stopped by user interrupt")
689
- except Exception as e:
690
- import traceback
691
- logger.error(f"Fatal server error: {e}", extra={
692
- "structured_data": {
693
- "error_type": type(e).__name__,
694
- "error_message": str(e),
695
- "traceback": traceback.format_exc()
696
- }
697
- })
698
- raise
699
- finally:
700
- # Clean shutdown
701
- await self.shutdown()
792
+
793
+ # Clean shutdown
794
+ await self.shutdown()
702
795
 
703
796
  async def shutdown(self) -> None:
704
797
  """Clean shutdown of server resources."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-code-indexer
3
- Version: 1.0.3
3
+ Version: 1.0.5
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
  Author: MCP Code Indexer Contributors
6
6
  Maintainer: MCP Code Indexer Contributors
@@ -1,6 +1,6 @@
1
- mcp_code_indexer/__init__.py,sha256=ofXDjKUt-BX9vnnbzqbW-ocPaO9dDlkSb55Atk7uzA0,473
1
+ mcp_code_indexer/__init__.py,sha256=PUkiM7VGRk7n2B_Ma0fzZWC0wmHCjyE15wxsvU9I54E,473
2
2
  mcp_code_indexer/error_handler.py,sha256=cNSUFFrGBMLDv4qa78c7495L1wSl_dXCRbzCJOidx-Q,11590
3
- mcp_code_indexer/file_scanner.py,sha256=1Z6wq7H14V1OMAHIF4v9G7SY8hC1puDmU5IXsCKH4kU,11442
3
+ mcp_code_indexer/file_scanner.py,sha256=ctXeZMROgDThEtjzsANTK9TbK-fhTScMBd4iyuleBT4,11734
4
4
  mcp_code_indexer/logging_config.py,sha256=5L1cYIG8IAX91yCjc5pzkbO_KPt0bvm_ABHB53LBZjI,5184
5
5
  mcp_code_indexer/main.py,sha256=Rou-mAN9-12PPP8jC7dIs2_UNambJuC2F8BF--j-0m8,3715
6
6
  mcp_code_indexer/merge_handler.py,sha256=lJR8eVq2qSrF6MW9mR3Fy8UzrNAaQ7RsI2FMNXne3vQ,14692
@@ -11,12 +11,12 @@ mcp_code_indexer/database/models.py,sha256=3wOxHKb6j3zKPWFSwB5g1TLpI507vLNZcqsxZ
11
11
  mcp_code_indexer/middleware/__init__.py,sha256=p-mP0pMsfiU2yajCPvokCUxUEkh_lu4XJP1LyyMW2ug,220
12
12
  mcp_code_indexer/middleware/error_middleware.py,sha256=v6jaHmPxf3qerYdb85X1tHIXLxgcbybpitKVakFLQTA,10109
13
13
  mcp_code_indexer/server/__init__.py,sha256=16xMcuriUOBlawRqWNBk6niwrvtv_JD5xvI36X1Vsmk,41
14
- mcp_code_indexer/server/mcp_server.py,sha256=tdBORkihjnWrAXXQVidwq9JNB9hrvR3brym0qfdNgvQ,35132
14
+ mcp_code_indexer/server/mcp_server.py,sha256=Ok4nrwkRNLGmHTEeW-Ij00gVMMRtS5aX-Hi-lUot5bg,39900
15
15
  mcp_code_indexer/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4,sha256=Ijkht27pm96ZW3_3OFE-7xAPtR0YyTWXoRO8_-hlsqc,1681126
16
16
  mcp_code_indexer/tools/__init__.py,sha256=m01mxML2UdD7y5rih_XNhNSCMzQTz7WQ_T1TeOcYlnE,49
17
- mcp_code_indexer-1.0.3.dist-info/licenses/LICENSE,sha256=JN9dyPPgYwH9C-UjYM7FLNZjQ6BF7kAzpF3_4PwY4rY,1086
18
- mcp_code_indexer-1.0.3.dist-info/METADATA,sha256=yASw5zg4MI23XClkWmle327l1Sm6M_L2QDfGS5Km7M8,11930
19
- mcp_code_indexer-1.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- mcp_code_indexer-1.0.3.dist-info/entry_points.txt,sha256=8HqWOw1Is7jOP1bvIgaSwouvT9z_Boe-9hd4NzyJOhY,68
21
- mcp_code_indexer-1.0.3.dist-info/top_level.txt,sha256=yKYCM-gMGt-cnupGfAhnZaoEsROLB6DQ1KFUuyKx4rw,17
22
- mcp_code_indexer-1.0.3.dist-info/RECORD,,
17
+ mcp_code_indexer-1.0.5.dist-info/licenses/LICENSE,sha256=JN9dyPPgYwH9C-UjYM7FLNZjQ6BF7kAzpF3_4PwY4rY,1086
18
+ mcp_code_indexer-1.0.5.dist-info/METADATA,sha256=JnsvZ1a4k6FP_wulSZAwm6kgB3FNuNRPWaG0HMVHd4o,11930
19
+ mcp_code_indexer-1.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ mcp_code_indexer-1.0.5.dist-info/entry_points.txt,sha256=8HqWOw1Is7jOP1bvIgaSwouvT9z_Boe-9hd4NzyJOhY,68
21
+ mcp_code_indexer-1.0.5.dist-info/top_level.txt,sha256=yKYCM-gMGt-cnupGfAhnZaoEsROLB6DQ1KFUuyKx4rw,17
22
+ mcp_code_indexer-1.0.5.dist-info/RECORD,,