mcp-code-indexer 4.2.15__py3-none-any.whl → 4.2.17__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.
Files changed (28) hide show
  1. mcp_code_indexer/database/database.py +334 -115
  2. mcp_code_indexer/database/database_factory.py +1 -1
  3. mcp_code_indexer/database/exceptions.py +1 -1
  4. mcp_code_indexer/database/models.py +66 -24
  5. mcp_code_indexer/database/retry_executor.py +15 -5
  6. mcp_code_indexer/file_scanner.py +107 -12
  7. mcp_code_indexer/main.py +43 -30
  8. mcp_code_indexer/server/mcp_server.py +201 -7
  9. mcp_code_indexer/vector_mode/chunking/ast_chunker.py +103 -84
  10. mcp_code_indexer/vector_mode/chunking/chunk_optimizer.py +1 -0
  11. mcp_code_indexer/vector_mode/config.py +113 -45
  12. mcp_code_indexer/vector_mode/const.py +24 -0
  13. mcp_code_indexer/vector_mode/daemon.py +860 -98
  14. mcp_code_indexer/vector_mode/monitoring/change_detector.py +113 -97
  15. mcp_code_indexer/vector_mode/monitoring/file_watcher.py +175 -121
  16. mcp_code_indexer/vector_mode/providers/turbopuffer_client.py +291 -98
  17. mcp_code_indexer/vector_mode/providers/voyage_client.py +140 -38
  18. mcp_code_indexer/vector_mode/services/__init__.py +9 -0
  19. mcp_code_indexer/vector_mode/services/embedding_service.py +389 -0
  20. mcp_code_indexer/vector_mode/services/vector_mode_tools_service.py +459 -0
  21. mcp_code_indexer/vector_mode/services/vector_storage_service.py +580 -0
  22. mcp_code_indexer/vector_mode/types.py +46 -0
  23. mcp_code_indexer/vector_mode/utils.py +50 -0
  24. {mcp_code_indexer-4.2.15.dist-info → mcp_code_indexer-4.2.17.dist-info}/METADATA +13 -10
  25. {mcp_code_indexer-4.2.15.dist-info → mcp_code_indexer-4.2.17.dist-info}/RECORD +28 -21
  26. {mcp_code_indexer-4.2.15.dist-info → mcp_code_indexer-4.2.17.dist-info}/WHEEL +1 -1
  27. {mcp_code_indexer-4.2.15.dist-info → mcp_code_indexer-4.2.17.dist-info}/entry_points.txt +0 -0
  28. {mcp_code_indexer-4.2.15.dist-info → mcp_code_indexer-4.2.17.dist-info/licenses}/LICENSE +0 -0
@@ -13,7 +13,7 @@ import random
13
13
  import re
14
14
  import time
15
15
  import uuid
16
- from datetime import datetime
16
+ from datetime import datetime, timedelta
17
17
  from pathlib import Path
18
18
  from typing import Any, Dict, List, Optional, Callable, cast
19
19
 
@@ -56,7 +56,7 @@ class MCPCodeIndexServer:
56
56
  cache_dir: Optional[Path] = None,
57
57
  db_pool_size: int = 3,
58
58
  db_retry_count: int = 5,
59
- db_timeout: float = 10.0,
59
+ db_timeout: float = 30.0,
60
60
  enable_wal_mode: bool = True,
61
61
  health_check_interval: float = 30.0,
62
62
  retry_min_wait: float = 0.1,
@@ -684,6 +684,104 @@ class MCPCodeIndexServer:
684
684
  "additionalProperties": False,
685
685
  },
686
686
  ),
687
+ types.Tool(
688
+ name="enabled_vector_mode",
689
+ description=(
690
+ "Enables or disables vector mode for a project. Vector mode "
691
+ "provides semantic search capabilities with embeddings for "
692
+ "enhanced code navigation and discovery."
693
+ ),
694
+ inputSchema={
695
+ "type": "object",
696
+ "properties": {
697
+ "projectName": {
698
+ "type": "string",
699
+ "description": "The name of the project",
700
+ },
701
+ "folderPath": {
702
+ "type": "string",
703
+ "description": (
704
+ "Absolute path to the project folder on disk"
705
+ ),
706
+ },
707
+ "enabled": {
708
+ "type": "boolean",
709
+ "description": (
710
+ "Whether to enable (true) or disable (false) vector mode"
711
+ ),
712
+ },
713
+ },
714
+ "required": ["projectName", "folderPath", "enabled"],
715
+ "additionalProperties": False,
716
+ },
717
+ ),
718
+ types.Tool(
719
+ name="find_similar_code",
720
+ description=(
721
+ "Find code similar to a given code snippet or file section using "
722
+ "vector-based semantic search. This tool uses AI embeddings to "
723
+ "understand code context and meaning, providing more intelligent "
724
+ "similarity detection than text-based matching. Requires vector "
725
+ "mode to be enabled for the project."
726
+ ),
727
+ inputSchema={
728
+ "type": "object",
729
+ "properties": {
730
+ "projectName": {
731
+ "type": "string",
732
+ "description": "The name of the project",
733
+ },
734
+ "folderPath": {
735
+ "type": "string",
736
+ "description": (
737
+ "Absolute path to the project folder on disk"
738
+ ),
739
+ },
740
+ "code_snippet": {
741
+ "type": "string",
742
+ "description": (
743
+ "Direct code snippet to search for similarities (mutually "
744
+ "exclusive with file_path)"
745
+ ),
746
+ },
747
+ "file_path": {
748
+ "type": "string",
749
+ "description": (
750
+ "Path to file containing code to analyze (mutually "
751
+ "exclusive with code_snippet)"
752
+ ),
753
+ },
754
+ "line_start": {
755
+ "type": "integer",
756
+ "description": (
757
+ "Starting line number for file section (1-indexed, "
758
+ "used with file_path)"
759
+ ),
760
+ },
761
+ "line_end": {
762
+ "type": "integer",
763
+ "description": (
764
+ "Ending line number for file section (1-indexed, "
765
+ "used with file_path)"
766
+ ),
767
+ },
768
+ "similarity_threshold": {
769
+ "type": "number",
770
+ "description": (
771
+ "Minimum similarity score (0.0-1.0, optional)"
772
+ ),
773
+ },
774
+ "max_results": {
775
+ "type": "integer",
776
+ "description": (
777
+ "Maximum number of results to return (optional)"
778
+ ),
779
+ },
780
+ },
781
+ "required": ["projectName", "folderPath"],
782
+ "additionalProperties": False,
783
+ },
784
+ ),
687
785
  ]
688
786
 
689
787
  @self.server.call_tool() # type: ignore[misc]
@@ -711,6 +809,8 @@ class MCPCodeIndexServer:
711
809
  "get_word_frequency": self._handle_get_word_frequency,
712
810
  "check_database_health": self._handle_check_database_health,
713
811
  "search_codebase_overview": self._handle_search_codebase_overview,
812
+ "enabled_vector_mode": self._handle_enabled_vector_mode,
813
+ "find_similar_code": self._handle_find_similar_code,
714
814
  }
715
815
 
716
816
  if name not in tool_handlers:
@@ -782,8 +882,11 @@ class MCPCodeIndexServer:
782
882
  all_projects = await db_manager.get_all_projects()
783
883
  if all_projects:
784
884
  project = all_projects[0] # Use the first (and should be only) project
785
- # Update last accessed time
786
- await db_manager.update_project_access_time(project.id)
885
+
886
+ # Update last accessed time only if older than 5 minutes
887
+ if datetime.utcnow() - project.last_accessed > timedelta(minutes=5):
888
+ await db_manager.update_project_access_time(project.id)
889
+
787
890
  logger.info(
788
891
  f"Using existing local project: {project.name} (ID: {project.id})"
789
892
  )
@@ -834,7 +937,9 @@ class MCPCodeIndexServer:
834
937
  )
835
938
 
836
939
  if project is None:
837
- raise RuntimeError("Project should always be set in if/else branches above")
940
+ raise RuntimeError(
941
+ "Project should always be set in if/else branches above"
942
+ )
838
943
  return project.id
839
944
 
840
945
  async def _find_matching_project(
@@ -938,8 +1043,9 @@ class MCPCodeIndexServer:
938
1043
  db_manager: DatabaseManager,
939
1044
  ) -> None:
940
1045
  """Update an existing project with new metadata and folder alias."""
941
- # Update last accessed time
942
- await db_manager.update_project_access_time(project.id)
1046
+ # Update last accessed time only if older than 5 minutes
1047
+ if datetime.utcnow() - project.last_accessed > timedelta(minutes=5):
1048
+ await db_manager.update_project_access_time(project.id)
943
1049
 
944
1050
  should_update = False
945
1051
 
@@ -1103,6 +1209,7 @@ class MCPCodeIndexServer:
1103
1209
  "isLarge": is_large,
1104
1210
  "recommendation": recommendation,
1105
1211
  "tokenLimit": token_limit,
1212
+ "totalTokens": total_tokens,
1106
1213
  "totalFiles": len(file_descriptions),
1107
1214
  "cleanedUpCount": cleaned_up_count,
1108
1215
  }
@@ -1481,6 +1588,93 @@ class MCPCodeIndexServer:
1481
1588
  "status_summary": self._generate_health_summary(comprehensive_diagnostics),
1482
1589
  }
1483
1590
 
1591
+ async def _handle_enabled_vector_mode(
1592
+ self, arguments: Dict[str, Any]
1593
+ ) -> Dict[str, Any]:
1594
+ """Handle enabled_vector_mode tool calls."""
1595
+ folder_path = arguments["folderPath"]
1596
+ db_manager = await self.db_factory.get_database_manager(folder_path)
1597
+ project_id = await self._get_or_create_project_id(arguments)
1598
+ enabled = arguments["enabled"]
1599
+
1600
+ try:
1601
+ await db_manager.set_project_vector_mode(project_id, enabled)
1602
+
1603
+ return {
1604
+ "success": True,
1605
+ "message": f"Vector mode {'enabled' if enabled else 'disabled'} for project",
1606
+ "project_id": project_id,
1607
+ "vector_mode": enabled,
1608
+ }
1609
+ except ValueError as e:
1610
+ return {
1611
+ "success": False,
1612
+ "error": str(e),
1613
+ "project_id": project_id,
1614
+ "vector_mode": None,
1615
+ }
1616
+
1617
+ async def _handle_find_similar_code(
1618
+ self, arguments: Dict[str, Any]
1619
+ ) -> Dict[str, Any]:
1620
+ """Handle find_similar_code tool calls."""
1621
+ try:
1622
+ from mcp_code_indexer.vector_mode.services.vector_mode_tools_service import (
1623
+ VectorModeToolsService,
1624
+ )
1625
+
1626
+ # Initialize the tools service (handles all vector mode setup internally)
1627
+ tools_service = VectorModeToolsService()
1628
+
1629
+ # Extract project info
1630
+ project_name = arguments["projectName"]
1631
+ folder_path = arguments["folderPath"]
1632
+
1633
+ logger.info(
1634
+ "Processing find_similar_code request",
1635
+ extra={
1636
+ "structured_data": {
1637
+ "project_name": project_name,
1638
+ "has_code_snippet": "code_snippet" in arguments,
1639
+ "has_file_path": "file_path" in arguments,
1640
+ }
1641
+ },
1642
+ )
1643
+
1644
+ # Call the service method
1645
+ result = await tools_service.find_similar_code(
1646
+ project_name=project_name,
1647
+ folder_path=folder_path,
1648
+ code_snippet=arguments.get("code_snippet"),
1649
+ file_path=arguments.get("file_path"),
1650
+ line_start=arguments.get("line_start"),
1651
+ line_end=arguments.get("line_end"),
1652
+ similarity_threshold=arguments.get("similarity_threshold"),
1653
+ max_results=arguments.get("max_results"),
1654
+ )
1655
+
1656
+ # Add success indicator to the result
1657
+ result["success"] = True
1658
+ return result
1659
+
1660
+ except Exception as e:
1661
+ logger.error(
1662
+ "Failed to execute find_similar_code",
1663
+ extra={
1664
+ "structured_data": {
1665
+ "error": str(e),
1666
+ "project_name": arguments.get("projectName", "unknown"),
1667
+ }
1668
+ },
1669
+ exc_info=True,
1670
+ )
1671
+ return {
1672
+ "success": False,
1673
+ "error": str(e),
1674
+ "results": [],
1675
+ "total_results": 0,
1676
+ }
1677
+
1484
1678
  def _generate_health_summary(self, diagnostics: Dict[str, Any]) -> Dict[str, Any]:
1485
1679
  """Generate a concise health summary from comprehensive diagnostics."""
1486
1680
  if "resilience_indicators" not in diagnostics: