mcp-code-indexer 1.0.2__py3-none-any.whl → 1.0.4__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 +1 -1
- mcp_code_indexer/main.py +2 -0
- mcp_code_indexer/server/mcp_server.py +139 -10
- {mcp_code_indexer-1.0.2.dist-info → mcp_code_indexer-1.0.4.dist-info}/METADATA +2 -2
- {mcp_code_indexer-1.0.2.dist-info → mcp_code_indexer-1.0.4.dist-info}/RECORD +9 -9
- {mcp_code_indexer-1.0.2.dist-info → mcp_code_indexer-1.0.4.dist-info}/WHEEL +0 -0
- {mcp_code_indexer-1.0.2.dist-info → mcp_code_indexer-1.0.4.dist-info}/entry_points.txt +0 -0
- {mcp_code_indexer-1.0.2.dist-info → mcp_code_indexer-1.0.4.dist-info}/licenses/LICENSE +0 -0
- {mcp_code_indexer-1.0.2.dist-info → mcp_code_indexer-1.0.4.dist-info}/top_level.txt +0 -0
mcp_code_indexer/__init__.py
CHANGED
@@ -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.
|
9
|
+
__version__ = "1.0.4"
|
10
10
|
__author__ = "MCP Code Indexer Contributors"
|
11
11
|
__email__ = ""
|
12
12
|
__license__ = "MIT"
|
mcp_code_indexer/main.py
CHANGED
@@ -124,7 +124,9 @@ def cli_main():
|
|
124
124
|
pass
|
125
125
|
except Exception as e:
|
126
126
|
# Log critical errors to stderr, not stdout
|
127
|
+
import traceback
|
127
128
|
print(f"Server failed to start: {e}", file=sys.stderr)
|
129
|
+
print(f"Traceback: {traceback.format_exc()}", file=sys.stderr)
|
128
130
|
sys.exit(1)
|
129
131
|
|
130
132
|
|
@@ -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
|
@@ -75,6 +76,9 @@ class MCPCodeIndexServer:
|
|
75
76
|
# Register handlers
|
76
77
|
self._register_handlers()
|
77
78
|
|
79
|
+
# Add debug logging for server events
|
80
|
+
self.logger.debug("MCP server instance created and handlers registered")
|
81
|
+
|
78
82
|
self.logger.info(
|
79
83
|
"MCP Code Index Server initialized",
|
80
84
|
extra={"structured_data": {"initialization": {"token_limit": token_limit}}}
|
@@ -648,21 +652,146 @@ class MCPCodeIndexServer:
|
|
648
652
|
**result
|
649
653
|
}
|
650
654
|
|
651
|
-
async def
|
652
|
-
"""Run
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
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})...")
|
658
663
|
await self.server.run(
|
659
664
|
read_stream,
|
660
665
|
write_stream,
|
661
666
|
initialization_options
|
662
667
|
)
|
663
|
-
|
664
|
-
|
665
|
-
|
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
|
+
|
728
|
+
async def run(self) -> None:
|
729
|
+
"""Run the MCP server with robust error handling."""
|
730
|
+
logger.info("Starting server initialization...")
|
731
|
+
await self.initialize()
|
732
|
+
logger.info("Server initialization completed, starting MCP protocol...")
|
733
|
+
|
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
|
750
|
+
|
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={
|
785
|
+
"structured_data": {
|
786
|
+
"error_type": type(e).__name__,
|
787
|
+
"error_message": str(e),
|
788
|
+
"traceback": traceback.format_exc()
|
789
|
+
}
|
790
|
+
})
|
791
|
+
raise
|
792
|
+
|
793
|
+
# Clean shutdown
|
794
|
+
await self.shutdown()
|
666
795
|
|
667
796
|
async def shutdown(self) -> None:
|
668
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
|
+
Version: 1.0.4
|
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
|
@@ -30,7 +30,7 @@ Requires-Python: >=3.9
|
|
30
30
|
Description-Content-Type: text/markdown
|
31
31
|
License-File: LICENSE
|
32
32
|
Requires-Dist: tiktoken>=0.9.0
|
33
|
-
Requires-Dist: mcp
|
33
|
+
Requires-Dist: mcp>=1.9.0
|
34
34
|
Requires-Dist: gitignore_parser==0.1.11
|
35
35
|
Requires-Dist: pydantic>=2.8.0
|
36
36
|
Requires-Dist: aiofiles==23.2.0
|
@@ -1,8 +1,8 @@
|
|
1
|
-
mcp_code_indexer/__init__.py,sha256=
|
1
|
+
mcp_code_indexer/__init__.py,sha256=tFNpOTeNAG23YobUnL1oXAdr6CfSQXPtzvGswDz8uPg,473
|
2
2
|
mcp_code_indexer/error_handler.py,sha256=cNSUFFrGBMLDv4qa78c7495L1wSl_dXCRbzCJOidx-Q,11590
|
3
3
|
mcp_code_indexer/file_scanner.py,sha256=1Z6wq7H14V1OMAHIF4v9G7SY8hC1puDmU5IXsCKH4kU,11442
|
4
4
|
mcp_code_indexer/logging_config.py,sha256=5L1cYIG8IAX91yCjc5pzkbO_KPt0bvm_ABHB53LBZjI,5184
|
5
|
-
mcp_code_indexer/main.py,sha256=
|
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
|
7
7
|
mcp_code_indexer/token_counter.py,sha256=WrifOkbF99nWWHlRlhCHAB2KN7qr83GOHl7apE-hJcE,8460
|
8
8
|
mcp_code_indexer/database/__init__.py,sha256=aPq_aaRp0aSwOBIq9GkuMNjmLxA411zg2vhdrAuHm-w,38
|
@@ -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=
|
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.
|
18
|
-
mcp_code_indexer-1.0.
|
19
|
-
mcp_code_indexer-1.0.
|
20
|
-
mcp_code_indexer-1.0.
|
21
|
-
mcp_code_indexer-1.0.
|
22
|
-
mcp_code_indexer-1.0.
|
17
|
+
mcp_code_indexer-1.0.4.dist-info/licenses/LICENSE,sha256=JN9dyPPgYwH9C-UjYM7FLNZjQ6BF7kAzpF3_4PwY4rY,1086
|
18
|
+
mcp_code_indexer-1.0.4.dist-info/METADATA,sha256=ZOl-psh9Y4q9OtUFzuevW53lpm0SsczC7Nr3aSzIso4,11930
|
19
|
+
mcp_code_indexer-1.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
20
|
+
mcp_code_indexer-1.0.4.dist-info/entry_points.txt,sha256=8HqWOw1Is7jOP1bvIgaSwouvT9z_Boe-9hd4NzyJOhY,68
|
21
|
+
mcp_code_indexer-1.0.4.dist-info/top_level.txt,sha256=yKYCM-gMGt-cnupGfAhnZaoEsROLB6DQ1KFUuyKx4rw,17
|
22
|
+
mcp_code_indexer-1.0.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|