claude-mpm 4.5.1__py3-none-any.whl → 4.5.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.
Files changed (48) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/PM_INSTRUCTIONS.md +33 -0
  3. claude_mpm/cli/__init__.py +27 -11
  4. claude_mpm/cli/commands/doctor.py +1 -4
  5. claude_mpm/core/config.py +2 -2
  6. claude_mpm/core/framework/__init__.py +6 -6
  7. claude_mpm/core/unified_paths.py +13 -12
  8. claude_mpm/hooks/kuzu_memory_hook.py +1 -1
  9. claude_mpm/init.py +19 -0
  10. claude_mpm/services/async_session_logger.py +6 -2
  11. claude_mpm/services/claude_session_logger.py +2 -2
  12. claude_mpm/services/diagnostics/checks/mcp_services_check.py +2 -2
  13. claude_mpm/services/diagnostics/doctor_reporter.py +0 -2
  14. claude_mpm/services/mcp_config_manager.py +156 -105
  15. claude_mpm/services/mcp_gateway/core/process_pool.py +258 -36
  16. claude_mpm/services/mcp_gateway/utils/__init__.py +14 -0
  17. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +160 -0
  18. claude_mpm/services/mcp_gateway/utils/update_preferences.py +170 -0
  19. claude_mpm/services/mcp_service_verifier.py +4 -4
  20. claude_mpm/services/monitor/event_emitter.py +6 -2
  21. claude_mpm/services/project/archive_manager.py +7 -9
  22. claude_mpm/services/project/documentation_manager.py +2 -3
  23. claude_mpm/services/project/enhanced_analyzer.py +1 -1
  24. claude_mpm/services/project/project_organizer.py +15 -12
  25. claude_mpm/services/unified/__init__.py +13 -13
  26. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +4 -8
  27. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +0 -1
  28. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +8 -9
  29. claude_mpm/services/unified/config_strategies/__init__.py +37 -37
  30. claude_mpm/services/unified/config_strategies/config_schema.py +18 -22
  31. claude_mpm/services/unified/config_strategies/context_strategy.py +6 -7
  32. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +6 -10
  33. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +5 -9
  34. claude_mpm/services/unified/config_strategies/unified_config_service.py +2 -4
  35. claude_mpm/services/unified/config_strategies/validation_strategy.py +1 -1
  36. claude_mpm/services/unified/deployment_strategies/__init__.py +8 -8
  37. claude_mpm/services/unified/deployment_strategies/local.py +2 -5
  38. claude_mpm/services/unified/deployment_strategies/utils.py +13 -17
  39. claude_mpm/services/unified/deployment_strategies/vercel.py +5 -6
  40. claude_mpm/services/unified/unified_analyzer.py +1 -1
  41. claude_mpm/utils/common.py +3 -7
  42. claude_mpm/utils/database_connector.py +9 -12
  43. {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.5.dist-info}/METADATA +2 -2
  44. {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.5.dist-info}/RECORD +48 -45
  45. {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.5.dist-info}/WHEEL +0 -0
  46. {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.5.dist-info}/entry_points.txt +0 -0
  47. {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.5.dist-info}/licenses/LICENSE +0 -0
  48. {claude_mpm-4.5.1.dist-info → claude_mpm-4.5.5.dist-info}/top_level.txt +0 -0
@@ -47,7 +47,9 @@ class MCPConfigManager:
47
47
  STATIC_MCP_CONFIGS = {
48
48
  "kuzu-memory": {
49
49
  "type": "stdio",
50
- "command": "kuzu-memory", # Will be resolved to full path
50
+ # Use full path to kuzu-memory binary from pipx venv
51
+ # This ensures it runs with the correct Python version
52
+ "command": "kuzu-memory", # Will be resolved to pipx venv path
51
53
  "args": ["mcp", "serve"], # v1.1.0+ uses 'mcp serve' command
52
54
  },
53
55
  "mcp-ticketer": {
@@ -580,57 +582,38 @@ class MCPConfigManager:
580
582
  config["args"] = ["mcp"]
581
583
 
582
584
  elif service_name == "kuzu-memory":
583
- # Determine kuzu-memory command version
584
- kuzu_args = ["mcp", "serve"] # Default to the standard v1.1.0+ format
585
- test_cmd = None
586
-
587
- if use_pipx_run:
588
- test_cmd = ["pipx", "run", "kuzu-memory", "--help"]
589
- elif use_uvx:
590
- test_cmd = ["uvx", "kuzu-memory", "--help"]
591
- elif service_path:
592
- test_cmd = [service_path, "--help"]
593
-
594
- if test_cmd:
595
- try:
596
- result = subprocess.run(
597
- test_cmd,
598
- capture_output=True,
599
- text=True,
600
- timeout=10,
601
- check=False,
602
- )
603
- # Check for MCP support in help output
604
- help_output = result.stdout.lower() + result.stderr.lower()
605
-
606
- # Standard version detection - look for "mcp serve" command (v1.1.0+)
607
- # This is the correct format for kuzu-memory v1.1.0 and later
608
- if "mcp serve" in help_output or (
609
- "mcp" in help_output and "serve" in help_output
610
- ):
611
- # Standard v1.1.0+ version with mcp serve command
612
- kuzu_args = ["mcp", "serve"]
613
- # Legacy version detection - only "serve" without "mcp"
614
- elif "serve" in help_output and "mcp" not in help_output:
615
- # Very old version that only has serve command
616
- kuzu_args = ["serve"]
617
- else:
618
- # Default to the standard mcp serve format (v1.1.0+)
619
- # Note: "claude mcp-server" format is deprecated and does not work
620
- kuzu_args = ["mcp", "serve"]
621
- except Exception:
622
- # Default to the standard mcp serve command on any error
623
- kuzu_args = ["mcp", "serve"]
585
+ # For kuzu-memory, prefer using the binary from pipx venv
586
+ # This ensures it runs with Python 3.12 instead of system Python 3.13
587
+ pipx_binary = (
588
+ Path.home()
589
+ / ".local"
590
+ / "pipx"
591
+ / "venvs"
592
+ / "kuzu-memory"
593
+ / "bin"
594
+ / "kuzu-memory"
595
+ )
624
596
 
625
- if use_pipx_run:
597
+ if pipx_binary.exists():
598
+ # Use pipx venv binary directly - this runs with the correct Python
599
+ config["command"] = str(pipx_binary)
600
+ config["args"] = ["mcp", "serve"]
601
+ elif use_pipx_run:
602
+ # Fallback to pipx run
626
603
  config["command"] = "pipx"
627
- config["args"] = ["run", "kuzu-memory"] + kuzu_args
604
+ config["args"] = ["run", "kuzu-memory", "mcp", "serve"]
628
605
  elif use_uvx:
606
+ # UVX fallback
629
607
  config["command"] = "uvx"
630
- config["args"] = ["kuzu-memory"] + kuzu_args
631
- else:
608
+ config["args"] = ["kuzu-memory", "mcp", "serve"]
609
+ elif service_path:
610
+ # Direct binary path
632
611
  config["command"] = service_path
633
- config["args"] = kuzu_args
612
+ config["args"] = ["mcp", "serve"]
613
+ else:
614
+ # Default fallback
615
+ config["command"] = "pipx"
616
+ config["args"] = ["run", "kuzu-memory", "mcp", "serve"]
634
617
 
635
618
  # Generic config for unknown services
636
619
  elif use_pipx_run:
@@ -741,9 +724,7 @@ class MCPConfigManager:
741
724
  "command"
742
725
  ) or existing_config.get("args") != correct_config.get("args"):
743
726
  needs_update = True
744
- fixed_services.append(
745
- f"{service_name} in {Path(project_key).name}"
746
- )
727
+ fixed_services.append(f"{service_name} in {Path(project_key).name}")
747
728
 
748
729
  # Update configuration if needed
749
730
  if needs_update:
@@ -1017,49 +998,44 @@ class MCPConfigManager:
1017
998
 
1018
999
  return False, "none"
1019
1000
 
1020
- def _check_and_fix_mcp_ticketer_dependencies(self) -> bool:
1021
- """Check and fix mcp-ticketer missing gql dependency.
1022
-
1023
- Note: This is a workaround for mcp-ticketer <= 0.1.8 which is missing
1024
- the gql dependency in its package metadata. Future versions (> 0.1.8)
1025
- should include 'gql[httpx]>=3.0.0' as a dependency, making this fix
1026
- unnecessary. We keep this for backward compatibility with older versions.
1027
- """
1028
- try:
1029
- # Test if gql is available in mcp-ticketer's environment
1030
- test_result = subprocess.run(
1031
- ["pipx", "run", "--spec", "mcp-ticketer", "python", "-c", "import gql"],
1032
- capture_output=True,
1033
- text=True,
1034
- timeout=5,
1035
- check=False,
1036
- )
1037
-
1038
- # If import fails, inject the dependency
1039
- if test_result.returncode != 0:
1040
- self.logger.info("🔧 mcp-ticketer missing gql dependency, fixing...")
1041
-
1042
- inject_result = subprocess.run(
1043
- ["pipx", "inject", "mcp-ticketer", "gql"],
1044
- capture_output=True,
1045
- text=True,
1046
- timeout=30,
1047
- check=False,
1048
- )
1049
-
1050
- if inject_result.returncode == 0:
1051
- self.logger.info(
1052
- "✅ Successfully injected gql dependency into mcp-ticketer"
1053
- )
1054
- return True
1055
- self.logger.warning(f"Failed to inject gql: {inject_result.stderr}")
1056
- return False
1057
-
1058
- return False
1059
-
1060
- except Exception as e:
1061
- self.logger.debug(f"Could not check/fix mcp-ticketer dependencies: {e}")
1062
- return False
1001
+ # COMMENTED OUT: These functions are no longer used
1002
+ # Package maintainers should fix dependency declarations in their packages
1003
+ # Automatic dependency injection can cause conflicts and is not recommended
1004
+
1005
+ # def _get_mcp_ticketer_version(self) -> Optional[str]:
1006
+ # """Get the installed version of mcp-ticketer.
1007
+ #
1008
+ # Returns:
1009
+ # Version string (e.g., "0.1.8") or None if not installed
1010
+ # """
1011
+ # try:
1012
+ # result = subprocess.run(
1013
+ # ["pipx", "runpip", "mcp-ticketer", "show", "mcp-ticketer"],
1014
+ # capture_output=True,
1015
+ # text=True,
1016
+ # timeout=5,
1017
+ # check=False,
1018
+ # )
1019
+ #
1020
+ # if result.returncode == 0:
1021
+ # # Parse version from output
1022
+ # for line in result.stdout.split("\n"):
1023
+ # if line.startswith("Version:"):
1024
+ # return line.split(":", 1)[1].strip()
1025
+ # return None
1026
+ # except Exception:
1027
+ # return None
1028
+ #
1029
+ # def _check_and_fix_mcp_ticketer_dependencies(self) -> bool:
1030
+ # """Check and fix mcp-ticketer missing gql dependency.
1031
+ #
1032
+ # DEPRECATED: This workaround is no longer used.
1033
+ # Package maintainers should fix dependency declarations.
1034
+ #
1035
+ # Returns:
1036
+ # False (no longer performs injection)
1037
+ # """
1038
+ # return False
1063
1039
 
1064
1040
  def fix_mcp_service_issues(self) -> Tuple[bool, str]:
1065
1041
  """
@@ -1109,24 +1085,26 @@ class MCPConfigManager:
1109
1085
  )
1110
1086
  success = self._reinstall_service(service_name)
1111
1087
  if success:
1112
- # Special handling for mcp-ticketer - inject missing gql dependency
1113
- if service_name == "mcp-ticketer":
1114
- self._check_and_fix_mcp_ticketer_dependencies()
1088
+ # NOTE: Removed automatic dependency injection workaround
1089
+ # Package maintainers should fix dependency declarations
1115
1090
  fixed_services.append(f"{service_name} (reinstalled)")
1116
1091
  else:
1117
1092
  failed_services.append(f"{service_name} (reinstall failed)")
1118
1093
 
1119
1094
  elif issue_type == "missing_dependency":
1120
- # Fix missing dependencies
1121
- if service_name == "mcp-ticketer":
1122
- if self._check_and_fix_mcp_ticketer_dependencies():
1123
- fixed_services.append(f"{service_name} (dependency fixed)")
1124
- else:
1125
- failed_services.append(
1126
- f"{service_name} (dependency fix failed)"
1127
- )
1095
+ # Fix missing dependencies by automatically reinstalling
1096
+ self.logger.info(
1097
+ f" {service_name} has missing dependencies - auto-reinstalling..."
1098
+ )
1099
+ success = self._auto_reinstall_mcp_service(service_name)
1100
+ if success:
1101
+ fixed_services.append(f"{service_name} (auto-reinstalled)")
1128
1102
  else:
1129
- failed_services.append(f"{service_name} (unknown dependency issue)")
1103
+ self.logger.warning(
1104
+ f" Auto-reinstall failed for {service_name}. Manual fix: "
1105
+ f"pipx uninstall {service_name} && pipx install {service_name}"
1106
+ )
1107
+ failed_services.append(f"{service_name} (auto-reinstall failed)")
1130
1108
 
1131
1109
  elif issue_type == "path_issue":
1132
1110
  # Path issues are handled by config updates
@@ -1280,6 +1258,79 @@ class MCPConfigManager:
1280
1258
  self.logger.error(f"Error reinstalling {service_name}: {e}")
1281
1259
  return False
1282
1260
 
1261
+ def _auto_reinstall_mcp_service(self, service_name: str) -> bool:
1262
+ """
1263
+ Automatically reinstall an MCP service with missing dependencies.
1264
+
1265
+ This method:
1266
+ 1. Uninstalls the corrupted/incomplete service
1267
+ 2. Reinstalls it fresh from pipx
1268
+ 3. Verifies the reinstall was successful
1269
+ 4. Updates status after successful reinstall
1270
+
1271
+ Args:
1272
+ service_name: Name of the MCP service to reinstall
1273
+
1274
+ Returns:
1275
+ True if reinstall successful, False otherwise
1276
+ """
1277
+ try:
1278
+ import shutil
1279
+
1280
+ # Verify pipx is available
1281
+ if not shutil.which("pipx"):
1282
+ self.logger.error("pipx not found - cannot auto-reinstall")
1283
+ return False
1284
+
1285
+ self.logger.info(f" → Uninstalling {service_name}...")
1286
+ uninstall_result = subprocess.run(
1287
+ ["pipx", "uninstall", service_name],
1288
+ capture_output=True,
1289
+ text=True,
1290
+ timeout=30,
1291
+ check=False,
1292
+ )
1293
+
1294
+ # Log result but don't fail if uninstall had issues
1295
+ if uninstall_result.returncode != 0:
1296
+ self.logger.debug(
1297
+ f"Uninstall had warnings (expected if corrupted): {uninstall_result.stderr}"
1298
+ )
1299
+
1300
+ self.logger.info(f" → Installing fresh {service_name}...")
1301
+ install_result = subprocess.run(
1302
+ ["pipx", "install", service_name],
1303
+ capture_output=True,
1304
+ text=True,
1305
+ timeout=120,
1306
+ check=False,
1307
+ )
1308
+
1309
+ if install_result.returncode != 0:
1310
+ self.logger.error(
1311
+ f"Install failed for {service_name}: {install_result.stderr}"
1312
+ )
1313
+ return False
1314
+
1315
+ # Verify the reinstall worked
1316
+ self.logger.info(f" → Verifying {service_name} installation...")
1317
+ issue = self._detect_service_issue(service_name)
1318
+
1319
+ if issue is None:
1320
+ self.logger.info(f" ✅ Successfully reinstalled {service_name}")
1321
+ return True
1322
+ self.logger.warning(
1323
+ f"Reinstalled {service_name} but still has issue: {issue}"
1324
+ )
1325
+ return False
1326
+
1327
+ except subprocess.TimeoutExpired:
1328
+ self.logger.error(f"Timeout while reinstalling {service_name}")
1329
+ return False
1330
+ except Exception as e:
1331
+ self.logger.error(f"Error auto-reinstalling {service_name}: {e}")
1332
+ return False
1333
+
1283
1334
  def _verify_service_installed(self, service_name: str, method: str) -> bool:
1284
1335
  """
1285
1336
  Verify that a service was successfully installed and is functional.
@@ -20,6 +20,7 @@ import json
20
20
  import os
21
21
  import signal
22
22
  import subprocess
23
+ import sys
23
24
  import threading
24
25
  import time
25
26
  from pathlib import Path
@@ -484,6 +485,21 @@ async def auto_initialize_vector_search():
484
485
 
485
486
  if vector_config.exists():
486
487
  logger.debug(f"Vector search already initialized for {current_dir}")
488
+
489
+ # Ensure .mcp-vector-search is in gitignore even if already initialized
490
+ try:
491
+ from ....services.project.project_organizer import ProjectOrganizer
492
+
493
+ if (current_dir / ".claude-mpm").exists() or (
494
+ current_dir / ".git"
495
+ ).exists():
496
+ organizer = ProjectOrganizer(current_dir)
497
+ organizer.update_gitignore(
498
+ additional_patterns=[".mcp-vector-search/"]
499
+ )
500
+ logger.debug("Ensured .mcp-vector-search is in gitignore")
501
+ except Exception as e:
502
+ logger.debug(f"Could not update gitignore for .mcp-vector-search: {e}")
487
503
  # Check if index needs rebuilding (corrupted database)
488
504
  chroma_db = current_dir / ".mcp-vector-search/chroma.sqlite3"
489
505
  if chroma_db.exists():
@@ -518,6 +534,23 @@ async def auto_initialize_vector_search():
518
534
  if proc.returncode == 0:
519
535
  logger.info("✅ Vector search initialization completed")
520
536
 
537
+ # Ensure .mcp-vector-search is in gitignore
538
+ try:
539
+ from ....services.project.project_organizer import ProjectOrganizer
540
+
541
+ # Check if we're in a git repository (parent of .claude-mpm)
542
+ if (current_dir / ".claude-mpm").exists() or (
543
+ current_dir / ".git"
544
+ ).exists():
545
+ organizer = ProjectOrganizer(current_dir)
546
+ organizer.update_gitignore(
547
+ additional_patterns=[".mcp-vector-search/"]
548
+ )
549
+ logger.debug("Ensured .mcp-vector-search is in gitignore")
550
+ except Exception as e:
551
+ logger.debug(f"Could not update gitignore for .mcp-vector-search: {e}")
552
+ # Non-critical, don't fail initialization
553
+
521
554
  # Start background indexing (non-blocking)
522
555
  def background_index():
523
556
  try:
@@ -659,6 +692,12 @@ async def auto_initialize_kuzu_memory():
659
692
  logger.debug("kuzu-memory command not found after installation")
660
693
  return
661
694
 
695
+ # Check for kuzu-memory updates (non-blocking)
696
+ try:
697
+ await _check_kuzu_memory_updates(kuzu_memory_cmd)
698
+ except Exception as e:
699
+ logger.debug(f"Update check failed (non-critical): {e}")
700
+
662
701
  # Initialize kuzu-memory database in current project
663
702
  current_dir = Path.cwd()
664
703
  kuzu_memories_dir = current_dir / "kuzu-memories"
@@ -668,6 +707,19 @@ async def auto_initialize_kuzu_memory():
668
707
  logger.debug(
669
708
  f"Kuzu-memory database already initialized at {kuzu_memories_dir}"
670
709
  )
710
+
711
+ # Ensure kuzu-memories is in gitignore even if already initialized
712
+ try:
713
+ from ....services.project.project_organizer import ProjectOrganizer
714
+
715
+ if (current_dir / ".claude-mpm").exists() or (
716
+ current_dir / ".git"
717
+ ).exists():
718
+ organizer = ProjectOrganizer(current_dir)
719
+ organizer.update_gitignore(additional_patterns=["kuzu-memories/"])
720
+ logger.debug("Ensured kuzu-memories is in gitignore")
721
+ except Exception as e:
722
+ logger.debug(f"Could not update gitignore for kuzu-memories: {e}")
671
723
  else:
672
724
  logger.info(
673
725
  f"🎯 Initializing kuzu-memory database for project: {current_dir}"
@@ -687,6 +739,22 @@ async def auto_initialize_kuzu_memory():
687
739
 
688
740
  if proc.returncode == 0:
689
741
  logger.info("✅ Kuzu-memory database initialized successfully")
742
+
743
+ # Ensure kuzu-memories is in gitignore
744
+ try:
745
+ from ....services.project.project_organizer import ProjectOrganizer
746
+
747
+ if (current_dir / ".claude-mpm").exists() or (
748
+ current_dir / ".git"
749
+ ).exists():
750
+ organizer = ProjectOrganizer(current_dir)
751
+ organizer.update_gitignore(
752
+ additional_patterns=["kuzu-memories/"]
753
+ )
754
+ logger.debug("Ensured kuzu-memories is in gitignore")
755
+ except Exception as e:
756
+ logger.debug(f"Could not update gitignore for kuzu-memories: {e}")
757
+ # Non-critical, don't fail initialization
690
758
  else:
691
759
  logger.warning(f"⚠️ Kuzu-memory initialization failed: {proc.stderr}")
692
760
 
@@ -694,47 +762,201 @@ async def auto_initialize_kuzu_memory():
694
762
  logger.debug(f"Kuzu-memory auto-initialization error (non-critical): {e}")
695
763
 
696
764
 
697
- async def pre_warm_mcp_servers():
698
- """Pre-warm MCP servers from configuration."""
699
- # Auto-initialize vector search for current project
700
- await auto_initialize_vector_search()
765
+ async def _check_kuzu_memory_updates(kuzu_cmd: Path) -> None:
766
+ """
767
+ Check for kuzu-memory updates and prompt user.
701
768
 
702
- # Auto-initialize kuzu-memory for persistent knowledge
703
- await auto_initialize_kuzu_memory()
769
+ Args:
770
+ kuzu_cmd: Path to kuzu-memory command
704
771
 
705
- pool = get_process_pool()
772
+ WHY: Keep users informed about important updates that may fix bugs
773
+ or add features they need.
706
774
 
707
- # Load MCP configurations
708
- configs = {}
775
+ DESIGN DECISIONS:
776
+ - Non-blocking with timeout to prevent startup delays
777
+ - Respects user preferences and environment variables
778
+ - Only prompts in interactive TTY sessions
779
+ """
780
+ logger = get_logger("kuzu_memory_update")
709
781
 
710
- # Check .claude.json for MCP server configs
711
- claude_config_path = Path.home() / ".claude.json"
712
- if not claude_config_path.exists():
713
- # Try project-local config
714
- claude_config_path = Path.cwd() / ".claude.json"
782
+ # Skip if environment variable set
783
+ if os.environ.get("CLAUDE_MPM_SKIP_UPDATE_CHECK"):
784
+ return
715
785
 
716
- if claude_config_path.exists():
717
- try:
718
- with open(claude_config_path) as f:
719
- config_data = json.load(f)
720
- mcp_servers = config_data.get("mcpServers", {})
721
- configs.update(mcp_servers)
722
- except Exception as e:
723
- get_logger("MCPProcessPool").warning(f"Failed to load Claude config: {e}")
786
+ # Skip if not TTY (can't prompt)
787
+ if not sys.stdin.isatty():
788
+ return
724
789
 
725
- # Check .mcp.json for additional configs
726
- mcp_config_path = Path.cwd() / ".mcp.json"
727
- if mcp_config_path.exists():
728
- try:
729
- with open(mcp_config_path) as f:
730
- config_data = json.load(f)
731
- mcp_servers = config_data.get("mcpServers", {})
732
- configs.update(mcp_servers)
733
- except Exception as e:
734
- get_logger("MCPProcessPool").warning(f"Failed to load MCP config: {e}")
790
+ # Import update utilities
791
+ from ..utils.package_version_checker import PackageVersionChecker
792
+ from ..utils.update_preferences import UpdatePreferences
793
+
794
+ # Check if updates are enabled for this package
795
+ if not UpdatePreferences.should_check_package("kuzu-memory"):
796
+ return
735
797
 
736
- if configs:
737
- await pool.pre_warm_servers(configs)
738
- await pool.start_health_monitoring()
798
+ try:
799
+ # Get current version from pipx
800
+ result = subprocess.run(
801
+ ["pipx", "list", "--json"],
802
+ capture_output=True,
803
+ text=True,
804
+ timeout=5,
805
+ check=False,
806
+ )
807
+
808
+ if result.returncode == 0:
809
+ pipx_data = json.loads(result.stdout)
810
+ venvs = pipx_data.get("venvs", {})
811
+ kuzu_info = venvs.get("kuzu-memory", {})
812
+ metadata = kuzu_info.get("metadata", {})
813
+ current_version = metadata.get("main_package", {}).get(
814
+ "package_version", "unknown"
815
+ )
816
+
817
+ if current_version != "unknown":
818
+ # Check for updates
819
+ checker = PackageVersionChecker()
820
+ update_info = await checker.check_for_update(
821
+ "kuzu-memory", current_version
822
+ )
739
823
 
740
- return pool
824
+ if update_info and update_info.get("update_available"):
825
+ latest_version = update_info["latest"]
826
+
827
+ # Check if user wants to skip this version
828
+ if UpdatePreferences.should_skip_version(
829
+ "kuzu-memory", latest_version
830
+ ):
831
+ logger.debug(
832
+ f"Skipping kuzu-memory update to {latest_version} per user preference"
833
+ )
834
+ return
835
+
836
+ # Prompt for update
837
+ _prompt_kuzu_update(update_info["current"], latest_version)
838
+
839
+ except Exception as e:
840
+ logger.debug(f"Update check error: {e}")
841
+
842
+
843
+ def _prompt_kuzu_update(current: str, latest: str) -> None:
844
+ """
845
+ Prompt user to update kuzu-memory.
846
+
847
+ Args:
848
+ current: Current installed version
849
+ latest: Latest available version
850
+ """
851
+ from ...cli.shared.error_handling import confirm_operation
852
+ from ..utils.update_preferences import UpdatePreferences
853
+
854
+ logger = get_logger("kuzu_memory_update")
855
+
856
+ message = (
857
+ f"\n🔄 A new version of kuzu-memory is available!\n"
858
+ f" Current: v{current}\n"
859
+ f" Latest: v{latest}\n\n"
860
+ f" This update may include bug fixes and performance improvements.\n"
861
+ f" Update now?"
862
+ )
863
+
864
+ # Check if running in a non-interactive context
865
+ try:
866
+ if confirm_operation(message):
867
+ print("🚀 Updating kuzu-memory...")
868
+ try:
869
+ result = subprocess.run(
870
+ ["pipx", "upgrade", "kuzu-memory"],
871
+ capture_output=True,
872
+ text=True,
873
+ timeout=30,
874
+ check=False,
875
+ )
876
+ if result.returncode == 0:
877
+ print("✅ Successfully updated kuzu-memory!")
878
+ logger.info(f"Updated kuzu-memory from {current} to {latest}")
879
+ else:
880
+ print(f"⚠️ Update failed: {result.stderr}")
881
+ logger.warning(f"kuzu-memory update failed: {result.stderr}")
882
+ except subprocess.TimeoutExpired:
883
+ print("⚠️ Update timed out. Please try again later.")
884
+ logger.warning("kuzu-memory update timed out")
885
+ except Exception as e:
886
+ print(f"⚠️ Update failed: {e}")
887
+ logger.warning(f"kuzu-memory update error: {e}")
888
+ else:
889
+ # User declined update
890
+ print("\n To skip this version permanently, run:")
891
+ print(f" claude-mpm config set-skip-version kuzu-memory {latest}")
892
+ print(" To disable update checks for kuzu-memory:")
893
+ print(" claude-mpm config disable-update-checks kuzu-memory")
894
+
895
+ # Ask if user wants to skip this version
896
+ if confirm_operation("\n Skip this version in future checks?"):
897
+ UpdatePreferences.set_skip_version("kuzu-memory", latest)
898
+ print(f" Version {latest} will be skipped in future checks.")
899
+ except (KeyboardInterrupt, EOFError):
900
+ # User interrupted or input not available
901
+ pass
902
+
903
+
904
+ async def pre_warm_mcp_servers():
905
+ """
906
+ Pre-warm MCP servers from configuration.
907
+
908
+ DISABLED: This function is currently disabled to avoid conflicts with
909
+ Claude Code's native MCP server management. When enabled, this can
910
+ cause issues with MCP server initialization and stderr/stdout handling.
911
+
912
+ TODO: Re-enable after ensuring compatibility with Claude Code's MCP handling.
913
+ """
914
+ logger = get_logger("MCPProcessPool")
915
+ logger.debug("MCP server pre-warming is currently disabled")
916
+
917
+ # COMMENTED OUT: Auto-initialization that can interfere with Claude Code
918
+ # # Auto-initialize vector search for current project
919
+ # await auto_initialize_vector_search()
920
+ #
921
+ # # Auto-initialize kuzu-memory for persistent knowledge
922
+ # await auto_initialize_kuzu_memory()
923
+ #
924
+ # pool = get_process_pool()
925
+ #
926
+ # # Load MCP configurations
927
+ # configs = {}
928
+ #
929
+ # # Check .claude.json for MCP server configs
930
+ # claude_config_path = Path.home() / ".claude.json"
931
+ # if not claude_config_path.exists():
932
+ # # Try project-local config
933
+ # claude_config_path = Path.cwd() / ".claude.json"
934
+ #
935
+ # if claude_config_path.exists():
936
+ # try:
937
+ # with open(claude_config_path) as f:
938
+ # config_data = json.load(f)
939
+ # mcp_servers = config_data.get("mcpServers", {})
940
+ # configs.update(mcp_servers)
941
+ # except Exception as e:
942
+ # get_logger("MCPProcessPool").warning(f"Failed to load Claude config: {e}")
943
+ #
944
+ # # Check .mcp.json for additional configs
945
+ # mcp_config_path = Path.cwd() / ".mcp.json"
946
+ # if mcp_config_path.exists():
947
+ # try:
948
+ # with open(mcp_config_path) as f:
949
+ # config_data = json.load(f)
950
+ # mcp_servers = config_data.get("mcpServers", {})
951
+ # configs.update(mcp_servers)
952
+ # except Exception as e:
953
+ # get_logger("MCPProcessPool").warning(f"Failed to load MCP config: {e}")
954
+ #
955
+ # if configs:
956
+ # await pool.pre_warm_servers(configs)
957
+ # await pool.start_health_monitoring()
958
+ #
959
+ # return pool
960
+
961
+ # Return a basic pool instance without pre-warming
962
+ return get_process_pool()