claude-mpm 5.0.2__py3-none-any.whl → 5.1.9__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 (76) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +1176 -909
  4. claude_mpm/agents/base_agent_loader.py +10 -35
  5. claude_mpm/agents/frontmatter_validator.py +68 -0
  6. claude_mpm/agents/templates/circuit-breakers.md +293 -44
  7. claude_mpm/cli/__init__.py +0 -1
  8. claude_mpm/cli/commands/__init__.py +2 -0
  9. claude_mpm/cli/commands/agent_state_manager.py +64 -11
  10. claude_mpm/cli/commands/agents.py +446 -25
  11. claude_mpm/cli/commands/auto_configure.py +535 -233
  12. claude_mpm/cli/commands/configure.py +545 -89
  13. claude_mpm/cli/commands/postmortem.py +401 -0
  14. claude_mpm/cli/commands/run.py +1 -39
  15. claude_mpm/cli/commands/skills.py +322 -19
  16. claude_mpm/cli/interactive/agent_wizard.py +302 -195
  17. claude_mpm/cli/parsers/agents_parser.py +137 -0
  18. claude_mpm/cli/parsers/auto_configure_parser.py +13 -0
  19. claude_mpm/cli/parsers/base_parser.py +4 -0
  20. claude_mpm/cli/parsers/skills_parser.py +7 -0
  21. claude_mpm/cli/startup.py +73 -32
  22. claude_mpm/commands/mpm-agents-auto-configure.md +2 -2
  23. claude_mpm/commands/mpm-agents-list.md +2 -2
  24. claude_mpm/commands/mpm-config-view.md +2 -2
  25. claude_mpm/commands/mpm-help.md +3 -0
  26. claude_mpm/commands/mpm-postmortem.md +123 -0
  27. claude_mpm/commands/mpm-session-resume.md +2 -2
  28. claude_mpm/commands/mpm-ticket-organize.md +2 -2
  29. claude_mpm/commands/mpm-ticket-view.md +2 -2
  30. claude_mpm/config/agent_presets.py +312 -82
  31. claude_mpm/config/skill_presets.py +392 -0
  32. claude_mpm/constants.py +1 -0
  33. claude_mpm/core/claude_runner.py +2 -25
  34. claude_mpm/core/framework/loaders/file_loader.py +54 -101
  35. claude_mpm/core/interactive_session.py +19 -5
  36. claude_mpm/core/oneshot_session.py +16 -4
  37. claude_mpm/core/output_style_manager.py +173 -43
  38. claude_mpm/core/protocols/__init__.py +23 -0
  39. claude_mpm/core/protocols/runner_protocol.py +103 -0
  40. claude_mpm/core/protocols/session_protocol.py +131 -0
  41. claude_mpm/core/shared/singleton_manager.py +11 -4
  42. claude_mpm/core/system_context.py +38 -0
  43. claude_mpm/core/unified_agent_registry.py +129 -1
  44. claude_mpm/core/unified_config.py +22 -0
  45. claude_mpm/hooks/claude_hooks/memory_integration.py +12 -1
  46. claude_mpm/models/agent_definition.py +7 -0
  47. claude_mpm/services/agents/cache_git_manager.py +621 -0
  48. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +110 -3
  49. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +195 -1
  50. claude_mpm/services/agents/sources/git_source_sync_service.py +37 -5
  51. claude_mpm/services/analysis/__init__.py +25 -0
  52. claude_mpm/services/analysis/postmortem_reporter.py +474 -0
  53. claude_mpm/services/analysis/postmortem_service.py +765 -0
  54. claude_mpm/services/command_deployment_service.py +108 -5
  55. claude_mpm/services/core/base.py +7 -2
  56. claude_mpm/services/diagnostics/checks/mcp_services_check.py +7 -15
  57. claude_mpm/services/git/git_operations_service.py +8 -8
  58. claude_mpm/services/mcp_config_manager.py +75 -145
  59. claude_mpm/services/mcp_gateway/core/process_pool.py +22 -16
  60. claude_mpm/services/mcp_service_verifier.py +6 -3
  61. claude_mpm/services/monitor/daemon.py +28 -8
  62. claude_mpm/services/monitor/daemon_manager.py +96 -19
  63. claude_mpm/services/project/project_organizer.py +4 -0
  64. claude_mpm/services/runner_configuration_service.py +16 -3
  65. claude_mpm/services/session_management_service.py +16 -4
  66. claude_mpm/utils/agent_filters.py +288 -0
  67. claude_mpm/utils/gitignore.py +3 -0
  68. claude_mpm/utils/migration.py +372 -0
  69. claude_mpm/utils/progress.py +5 -1
  70. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/METADATA +69 -8
  71. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/RECORD +76 -62
  72. /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  73. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/WHEEL +0 -0
  74. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/entry_points.txt +0 -0
  75. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/licenses/LICENSE +0 -0
  76. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,6 @@ This service handles:
7
7
  4. Parsing and validating YAML frontmatter for namespace metadata (Phase 1 - 1M-400)
8
8
  """
9
9
 
10
- import shutil
11
10
  from pathlib import Path
12
11
  from typing import Any, Dict, List, Optional, Tuple
13
12
 
@@ -20,6 +19,16 @@ from claude_mpm.core.logger import get_logger
20
19
  class CommandDeploymentService(BaseService):
21
20
  """Service for deploying MPM slash commands."""
22
21
 
22
+ # Deprecated commands that have been replaced (cleanup on startup)
23
+ DEPRECATED_COMMANDS = [
24
+ "mpm-agents.md", # Replaced by mpm-agents-list.md
25
+ "mpm-auto-configure.md", # Replaced by mpm-agents-auto-configure.md
26
+ "mpm-config.md", # Replaced by mpm-config-view.md
27
+ "mpm-organize.md", # Replaced by mpm-ticket-organize.md
28
+ "mpm-resume.md", # Replaced by mpm-session-resume.md
29
+ "mpm-ticket.md", # Replaced by mpm-ticket-view.md
30
+ ]
31
+
23
32
  def __init__(self):
24
33
  """Initialize the command deployment service."""
25
34
  super().__init__(name="command_deployment")
@@ -115,6 +124,33 @@ class CommandDeploymentService(BaseService):
115
124
 
116
125
  return errors
117
126
 
127
+ def _strip_deprecated_aliases(self, content: str) -> str:
128
+ """Strip deprecated_aliases from frontmatter to hide them from Claude Code UI.
129
+
130
+ This prevents deprecated aliases from appearing in the command list while
131
+ maintaining backward compatibility through command routing.
132
+
133
+ Args:
134
+ content: Command file content with frontmatter
135
+
136
+ Returns:
137
+ Content with deprecated_aliases removed from frontmatter
138
+ """
139
+ frontmatter, body = self._parse_frontmatter(content)
140
+
141
+ if not frontmatter or "deprecated_aliases" not in frontmatter:
142
+ return content
143
+
144
+ # Remove deprecated_aliases from frontmatter
145
+ frontmatter_copy = frontmatter.copy()
146
+ del frontmatter_copy["deprecated_aliases"]
147
+
148
+ # Reconstruct the file with modified frontmatter
149
+ frontmatter_yaml = yaml.dump(
150
+ frontmatter_copy, default_flow_style=False, sort_keys=False
151
+ )
152
+ return f"---\n{frontmatter_yaml}---\n{body}"
153
+
118
154
  def deploy_commands(self, force: bool = False) -> Dict[str, Any]:
119
155
  """Deploy MPM slash commands to user's Claude configuration.
120
156
 
@@ -185,8 +221,12 @@ class CommandDeploymentService(BaseService):
185
221
  )
186
222
  continue
187
223
 
188
- # Copy the file
189
- shutil.copy2(source_file, target_file)
224
+ # Strip deprecated_aliases from content before deployment
225
+ # This prevents them from appearing in Claude Code's command list UI
226
+ cleaned_content = self._strip_deprecated_aliases(content)
227
+
228
+ # Write the cleaned content to target
229
+ target_file.write_text(cleaned_content)
190
230
  self.logger.info(f"Deployed command: {source_file.name}")
191
231
  result["deployed"].append(source_file.name)
192
232
 
@@ -252,21 +292,84 @@ class CommandDeploymentService(BaseService):
252
292
 
253
293
  return removed
254
294
 
295
+ def remove_deprecated_commands(self) -> int:
296
+ """Remove deprecated MPM commands that have been replaced.
297
+
298
+ This method cleans up old command files that have been superseded by
299
+ new hierarchical command names. It's called automatically on startup
300
+ to ensure users don't have both old and new versions.
301
+
302
+ Returns:
303
+ Number of deprecated files removed
304
+ """
305
+ if not self.target_dir.exists():
306
+ self.logger.debug(
307
+ f"Target directory does not exist: {self.target_dir}, skipping deprecated command cleanup"
308
+ )
309
+ return 0
310
+
311
+ removed = 0
312
+ self.logger.info("Cleaning up deprecated commands...")
313
+
314
+ # Mapping of deprecated commands to their replacements for informative logging
315
+ replacement_map = {
316
+ "mpm-agents.md": "mpm-agents-list.md",
317
+ "mpm-auto-configure.md": "mpm-agents-auto-configure.md",
318
+ "mpm-config.md": "mpm-config-view.md",
319
+ "mpm-organize.md": "mpm-ticket-organize.md",
320
+ "mpm-resume.md": "mpm-session-resume.md",
321
+ "mpm-ticket.md": "mpm-ticket-view.md",
322
+ }
323
+
324
+ for deprecated_cmd in self.DEPRECATED_COMMANDS:
325
+ deprecated_file = self.target_dir / deprecated_cmd
326
+ replacement = replacement_map.get(deprecated_cmd, "a newer command")
327
+
328
+ if deprecated_file.exists():
329
+ try:
330
+ deprecated_file.unlink()
331
+ self.logger.debug(
332
+ f"Removed deprecated command: {deprecated_cmd} (replaced by {replacement})"
333
+ )
334
+ removed += 1
335
+ except Exception as e:
336
+ # Log error but don't fail startup - this is non-critical
337
+ self.logger.warning(
338
+ f"Failed to remove deprecated command {deprecated_cmd}: {e}"
339
+ )
340
+
341
+ if removed > 0:
342
+ self.logger.info(f"Removed {removed} deprecated command(s)")
343
+ else:
344
+ self.logger.debug("No deprecated commands found to remove")
345
+
346
+ return removed
347
+
255
348
 
256
349
  def deploy_commands_on_startup(force: bool = False) -> None:
257
350
  """Convenience function to deploy commands during startup.
258
351
 
352
+ This function:
353
+ 1. Removes deprecated commands that have been replaced
354
+ 2. Deploys current command files
355
+
259
356
  Args:
260
357
  force: Force deployment even if files exist
261
358
  """
262
359
  service = CommandDeploymentService()
360
+ logger = get_logger("startup")
361
+
362
+ # Clean up deprecated commands BEFORE deploying new ones
363
+ removed_count = service.remove_deprecated_commands()
364
+ if removed_count > 0:
365
+ logger.info(f"Cleaned up {removed_count} deprecated command(s)")
366
+
367
+ # Deploy current commands
263
368
  result = service.deploy_commands(force=force)
264
369
 
265
370
  if result["deployed"]:
266
- logger = get_logger("startup")
267
371
  logger.info(f"MPM commands deployed: {', '.join(result['deployed'])}")
268
372
 
269
373
  if result["errors"]:
270
- logger = get_logger("startup")
271
374
  for error in result["errors"]:
272
375
  logger.warning(f"Command deployment issue: {error}")
@@ -224,10 +224,11 @@ class SingletonService(SyncBaseService):
224
224
 
225
225
  Ensures only one instance of the service exists with thread-safe initialization.
226
226
  Uses double-checked locking pattern to prevent race conditions.
227
+ Uses RLock (reentrant lock) to support recursive instantiation patterns.
227
228
  """
228
229
 
229
230
  _instances: Dict[type, "SingletonService"] = {}
230
- _lock = threading.Lock()
231
+ _lock = threading.RLock()
231
232
 
232
233
  def __new__(cls, *args, **kwargs):
233
234
  """Ensure only one instance exists with thread-safe initialization."""
@@ -247,7 +248,11 @@ class SingletonService(SyncBaseService):
247
248
  # Slow path - acquire lock and double-check
248
249
  with cls._lock:
249
250
  if cls not in cls._instances:
250
- cls._instances[cls] = cls()
251
+ # Use object.__new__ to bypass __new__ recursion
252
+ instance = object.__new__(cls)
253
+ cls._instances[cls] = instance
254
+ # Call __init__ explicitly after storing instance
255
+ instance.__init__()
251
256
  return cls._instances[cls]
252
257
 
253
258
  @classmethod
@@ -126,22 +126,14 @@ class MCPServicesCheck(BaseDiagnosticCheck):
126
126
  )
127
127
  sub_results.append(fix_result)
128
128
 
129
- # Also ensure configurations are updated for all projects
130
- config_success, config_message = (
131
- mcp_manager.ensure_mcp_services_configured()
132
- )
133
- if (
134
- config_message
135
- and config_message != "All MCP services already configured correctly"
136
- ):
129
+ # Check if MCP services are available (read-only check)
130
+ available, availability_message = mcp_manager.check_mcp_services_available()
131
+ if not available:
132
+ # Services not configured - provide installation instructions
137
133
  config_result = DiagnosticResult(
138
- category="MCP Configuration Update",
139
- status=(
140
- OperationResult.SUCCESS
141
- if config_success
142
- else ValidationSeverity.WARNING
143
- ),
144
- message=config_message,
134
+ category="MCP Service Availability",
135
+ status=ValidationSeverity.WARNING,
136
+ message=availability_message,
145
137
  details={"auto_config_applied": True},
146
138
  )
147
139
  sub_results.append(config_result)
@@ -12,10 +12,10 @@ Design Decisions:
12
12
 
13
13
  Example:
14
14
  >>> service = GitOperationsService()
15
- >>> success = service.create_branch(Path("~/.claude-mpm/cache/agents"), "improve/research-memory")
15
+ >>> success = service.create_branch(Path("~/.claude-mpm/cache/remote-agents"), "improve/research-memory")
16
16
  >>> if success:
17
- ... service.stage_files(Path("~/.claude-mpm/cache/agents"), ["agents/research.md"])
18
- ... service.commit(Path("~/.claude-mpm/cache/agents"), "feat: improve research agent memory handling")
17
+ ... service.stage_files(Path("~/.claude-mpm/cache/remote-agents"), ["agents/research.md"])
18
+ ... service.commit(Path("~/.claude-mpm/cache/remote-agents"), "feat: improve research agent memory handling")
19
19
  """
20
20
 
21
21
  import subprocess
@@ -65,7 +65,7 @@ class GitOperationsService:
65
65
 
66
66
  Example:
67
67
  >>> service = GitOperationsService()
68
- >>> service.is_git_repo(Path("~/.claude-mpm/cache/agents"))
68
+ >>> service.is_git_repo(Path("~/.claude-mpm/cache/remote-agents"))
69
69
  True
70
70
  """
71
71
  try:
@@ -147,7 +147,7 @@ class GitOperationsService:
147
147
  Example:
148
148
  >>> service = GitOperationsService()
149
149
  >>> service.create_and_checkout_branch(
150
- ... Path("~/.claude-mpm/cache/agents"),
150
+ ... Path("~/.claude-mpm/cache/remote-agents"),
151
151
  ... "improve/research-memory",
152
152
  ... "main"
153
153
  ... )
@@ -242,7 +242,7 @@ class GitOperationsService:
242
242
  Example:
243
243
  >>> service = GitOperationsService()
244
244
  >>> service.commit(
245
- ... Path("~/.claude-mpm/cache/agents"),
245
+ ... Path("~/.claude-mpm/cache/remote-agents"),
246
246
  ... "feat(agent): improve research agent memory handling\\n\\n- Add hard limit of 5 files"
247
247
  ... )
248
248
  True
@@ -286,7 +286,7 @@ class GitOperationsService:
286
286
 
287
287
  Example:
288
288
  >>> service = GitOperationsService()
289
- >>> service.push(Path("~/.claude-mpm/cache/agents"), "improve/research-memory")
289
+ >>> service.push(Path("~/.claude-mpm/cache/remote-agents"), "improve/research-memory")
290
290
  True
291
291
  """
292
292
  self._validate_repo(repo_path)
@@ -404,7 +404,7 @@ class GitOperationsService:
404
404
 
405
405
  Example:
406
406
  >>> service = GitOperationsService()
407
- >>> valid, msg = service.validate_repo(Path("~/.claude-mpm/cache/agents"))
407
+ >>> valid, msg = service.validate_repo(Path("~/.claude-mpm/cache/remote-agents"))
408
408
  >>> if not valid:
409
409
  ... print(f"Repository invalid: {msg}")
410
410
  """
@@ -12,7 +12,6 @@ MCP service installations.
12
12
  import json
13
13
  import subprocess
14
14
  import sys
15
- from datetime import datetime, timezone
16
15
  from enum import Enum
17
16
  from pathlib import Path
18
17
  from typing import Dict, Optional, Tuple
@@ -698,175 +697,106 @@ class MCPConfigManager:
698
697
 
699
698
  return config
700
699
 
701
- def ensure_mcp_services_configured(self) -> Tuple[bool, str]:
700
+ def check_mcp_services_available(self) -> Tuple[bool, str]:
702
701
  """
703
- Ensure MCP services are configured correctly in ~/.claude.json on startup.
702
+ Check if required MCP services are available in ~/.claude.json (READ-ONLY).
704
703
 
705
- This method checks ALL projects in ~/.claude.json and ensures each has
706
- the correct, static MCP service configurations. It will:
707
- 1. Add missing services
708
- 2. Fix incorrect configurations
709
- 3. Update all projects, not just the current one
704
+ This method performs a READ-ONLY check of MCP service availability.
705
+ It does NOT modify ~/.claude.json. Users should install and configure
706
+ MCP services themselves via pip, npx, or Claude Desktop.
710
707
 
711
708
  Returns:
712
- Tuple of (success, message)
709
+ Tuple of (all_available: bool, message: str)
713
710
  """
714
- updated = False
715
- fixed_services = []
716
- added_services = []
717
-
718
- # Load existing Claude config or create minimal structure
719
- claude_config = {}
720
- if self.claude_config_path.exists():
721
- try:
722
- with self.claude_config_path.open() as f:
723
- claude_config = json.load(f)
724
- except Exception as e:
725
- self.logger.error(f"Error reading {self.claude_config_path}: {e}")
726
- return False, f"Failed to read Claude config: {e}"
711
+ # Get services Claude MPM expects to use (from ~/.claude-mpm/config/)
712
+ expected_services = self.get_filtered_services()
727
713
 
728
- # Ensure projects structure exists
729
- if "projects" not in claude_config:
730
- claude_config["projects"] = {}
731
- updated = True
714
+ if not expected_services:
715
+ return True, "No MCP services configured in Claude MPM"
732
716
 
733
- # Note: fix_mcp_service_issues() is already called during CLI initialization
734
- # Calling it here would duplicate the service health checks
717
+ # Load Claude config (read-only)
718
+ if not self.claude_config_path.exists():
719
+ return False, f"Claude config not found at {self.claude_config_path}"
735
720
 
736
- # Process ALL projects in the config, not just current one
737
- projects_to_update = list(claude_config.get("projects", {}).keys())
721
+ try:
722
+ with self.claude_config_path.open() as f:
723
+ claude_config = json.load(f)
724
+ except Exception as e:
725
+ return False, f"Failed to read Claude config: {e}"
738
726
 
739
- # Also add the current project if not in list
727
+ # Check current project
740
728
  current_project_key = str(self.project_root)
741
- if current_project_key not in projects_to_update:
742
- projects_to_update.append(current_project_key)
743
- # Initialize new project structure
744
- claude_config["projects"][current_project_key] = {
745
- "allowedTools": [],
746
- "history": [],
747
- "mcpContextUris": [],
748
- "mcpServers": {},
749
- "enabledMcpjsonServers": [],
750
- "disabledMcpjsonServers": [],
751
- "hasTrustDialogAccepted": False,
752
- "projectOnboardingSeenCount": 0,
753
- "hasClaudeMdExternalIncludesApproved": False,
754
- "hasClaudeMdExternalIncludesWarningShown": False,
755
- }
756
- updated = True
757
-
758
- # Update each project's MCP configurations
759
- for project_key in projects_to_update:
760
- project_config = claude_config["projects"][project_key]
761
-
762
- # Ensure mcpServers section exists
763
- if "mcpServers" not in project_config:
764
- project_config["mcpServers"] = {}
765
- updated = True
766
-
767
- # Check and fix each service configuration - now filtered by startup config
768
- services_to_configure = self.get_filtered_services()
769
-
770
- for service_name, correct_config in services_to_configure.items():
771
- # Check if service exists and has correct configuration
772
- existing_config = project_config["mcpServers"].get(service_name)
773
-
774
- # Determine if we need to update
775
- needs_update = False
776
- if not existing_config:
777
- # Service is missing
778
- needs_update = True
779
- added_services.append(f"{service_name} in {Path(project_key).name}")
780
- # Service exists, check if configuration is correct
781
- # Compare command and args (the most critical parts)
782
- elif existing_config.get("command") != correct_config.get(
783
- "command"
784
- ) or existing_config.get("args") != correct_config.get("args"):
785
- needs_update = True
786
- fixed_services.append(f"{service_name} in {Path(project_key).name}")
787
-
788
- # Update configuration if needed
789
- if needs_update:
790
- project_config["mcpServers"][service_name] = correct_config
791
- updated = True
792
- self.logger.debug(
793
- f"Updated MCP service config for {service_name} in project {Path(project_key).name}"
794
- )
729
+ project_config = claude_config.get("projects", {}).get(current_project_key)
795
730
 
796
- # Remove disabled services from configuration
797
- if self.config is not None:
798
- # Import Config here to avoid circular import
799
- from ..core.config import Config
731
+ if not project_config:
732
+ missing = list(expected_services.keys())
733
+ return (
734
+ False,
735
+ f"Current project not configured in Claude. Missing services: {', '.join(missing)}",
736
+ )
800
737
 
801
- if isinstance(self.config, Config):
802
- enabled_services = self.config.get(
803
- "startup.enabled_mcp_services", None
804
- )
805
- if enabled_services is not None:
806
- # Remove services that are not in the enabled list
807
- services_to_remove = []
808
- for service_name in project_config["mcpServers"]:
809
- if service_name not in enabled_services:
810
- services_to_remove.append(service_name)
811
-
812
- for service_name in services_to_remove:
813
- del project_config["mcpServers"][service_name]
814
- updated = True
815
- self.logger.debug(
816
- f"Removed disabled service {service_name} from project {Path(project_key).name}"
817
- )
818
-
819
- # Write updated config if changes were made
820
- if updated:
821
- try:
822
- # Create backup if file exists and is large (> 100KB)
823
- if self.claude_config_path.exists():
824
- file_size = self.claude_config_path.stat().st_size
825
- if file_size > 100000: # 100KB
826
- backup_path = self.claude_config_path.with_suffix(
827
- f".backup.{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}.json"
828
- )
829
- import shutil
738
+ # Check which services are missing
739
+ mcp_servers = project_config.get("mcpServers", {})
740
+ missing_services = [
741
+ name for name in expected_services if name not in mcp_servers
742
+ ]
830
743
 
831
- shutil.copy2(self.claude_config_path, backup_path)
832
- self.logger.debug(f"Created backup: {backup_path}")
744
+ if missing_services:
745
+ msg = (
746
+ f"Missing MCP services: {', '.join(missing_services)}. "
747
+ f"Install via: pip install {' '.join(missing_services)} "
748
+ f"or configure in Claude Desktop"
749
+ )
750
+ return False, msg
833
751
 
834
- # Write updated config
835
- with self.claude_config_path.open("w") as f:
836
- json.dump(claude_config, f, indent=2)
752
+ return (
753
+ True,
754
+ f"All required MCP services available ({len(expected_services)} services)",
755
+ )
837
756
 
838
- messages = []
839
- if added_services:
840
- messages.append(
841
- f"Added MCP services: {', '.join(added_services[:3])}"
842
- )
843
- if fixed_services:
844
- messages.append(
845
- f"Fixed MCP services: {', '.join(fixed_services[:3])}"
846
- )
757
+ def ensure_mcp_services_configured(self) -> Tuple[bool, str]:
758
+ """
759
+ DEPRECATED: Auto-configuring ~/.claude.json is no longer supported.
847
760
 
848
- if messages:
849
- return True, "; ".join(messages)
850
- return True, "All MCP services already configured correctly"
851
- except Exception as e:
852
- self.logger.error(f"Failed to write Claude config: {e}")
853
- return False, f"Failed to write configuration: {e}"
761
+ As of v4.15.0+, MCP services are user-controlled. Users should install
762
+ and configure MCP services themselves via:
763
+ - pip install <service-name>
764
+ - npx @modelcontextprotocol/...
765
+ - Claude Desktop UI
854
766
 
855
- return True, "All MCP services already configured correctly"
767
+ This method now only performs a read-only check and logs a deprecation warning.
768
+ Use check_mcp_services_available() for read-only checks.
769
+
770
+ Returns:
771
+ Tuple of (success, message)
772
+ """
773
+ import warnings
774
+
775
+ warnings.warn(
776
+ "ensure_mcp_services_configured() is deprecated and will be removed in v6.0.0. "
777
+ "MCP services are now user-controlled. Use check_mcp_services_available() instead.",
778
+ DeprecationWarning,
779
+ stacklevel=2,
780
+ )
781
+
782
+ # Delegate to read-only check
783
+ return self.check_mcp_services_available()
856
784
 
857
785
  def update_mcp_config(self, force_pipx: bool = True) -> Tuple[bool, str]:
858
786
  """
859
- Update the MCP configuration in ~/.claude.json.
787
+ DEPRECATED: Check MCP configuration in ~/.claude.json (READ-ONLY).
788
+
789
+ This method no longer modifies ~/.claude.json. Users should install
790
+ and configure MCP services themselves.
860
791
 
861
792
  Args:
862
- force_pipx: If True, only use pipx installations
793
+ force_pipx: Ignored (kept for backward compatibility)
863
794
 
864
795
  Returns:
865
- Tuple of (success, message)
796
+ Tuple of (success, message) from read-only check
866
797
  """
867
- # This method now delegates to ensure_mcp_services_configured
868
- # since we're updating the Claude config directly
869
- return self.ensure_mcp_services_configured()
798
+ # Delegate to read-only check
799
+ return self.check_mcp_services_available()
870
800
 
871
801
  def update_project_mcp_config(self, force_pipx: bool = True) -> Tuple[bool, str]:
872
802
  """
@@ -440,15 +440,18 @@ async def auto_initialize_vector_search():
440
440
  )
441
441
  return
442
442
 
443
- # Update the Claude configuration to include the newly installed service
444
- logger.info("📝 Updating Claude configuration...")
445
- config_success, config_msg = (
446
- config_manager.ensure_mcp_services_configured()
447
- )
448
- if config_success:
449
- logger.info(f"✅ {config_msg}")
443
+ # Verify the newly installed service is available
444
+ logger.info("📝 Verifying installation...")
445
+ available, msg = config_manager.check_mcp_services_available()
446
+ if available:
447
+ logger.info(f"✅ {msg}")
450
448
  else:
451
- logger.warning(f"⚠️ Configuration update issue: {config_msg}")
449
+ logger.warning(
450
+ f"⚠️ Service installed but not configured in Claude: {msg}"
451
+ )
452
+ logger.info(
453
+ "💡 Configure via: Claude Desktop > Settings > Developer > Model Context Protocol"
454
+ )
452
455
  else:
453
456
  logger.warning(
454
457
  f"Failed to install mcp-vector-search: {result.stderr}"
@@ -658,15 +661,18 @@ async def auto_initialize_kuzu_memory():
658
661
  )
659
662
  return
660
663
 
661
- # Update the Claude configuration to include the newly installed service
662
- logger.info("📝 Updating Claude configuration...")
663
- config_success, config_msg = (
664
- config_manager.ensure_mcp_services_configured()
665
- )
666
- if config_success:
667
- logger.info(f"✅ {config_msg}")
664
+ # Verify the newly installed service is available
665
+ logger.info("📝 Verifying installation...")
666
+ available, msg = config_manager.check_mcp_services_available()
667
+ if available:
668
+ logger.info(f"✅ {msg}")
668
669
  else:
669
- logger.warning(f"⚠️ Configuration update issue: {config_msg}")
670
+ logger.warning(
671
+ f"⚠️ Service installed but not configured in Claude: {msg}"
672
+ )
673
+ logger.info(
674
+ "💡 Configure via: Claude Desktop > Settings > Developer > Model Context Protocol"
675
+ )
670
676
  else:
671
677
  logger.warning(f"Failed to install kuzu-memory: {result.stderr}")
672
678
  return
@@ -620,12 +620,15 @@ class MCPServiceVerifier:
620
620
  return True
621
621
 
622
622
  if "claude-mpm configure" in diagnostic.fix_command:
623
- # Trigger configuration update
623
+ # Check if services are available (read-only)
624
624
  from .mcp_config_manager import MCPConfigManager
625
625
 
626
626
  manager = MCPConfigManager()
627
- success, _ = manager.ensure_mcp_services_configured()
628
- return success
627
+ available, message = manager.check_mcp_services_available()
628
+ if not available:
629
+ # Cannot auto-fix - user must install services manually
630
+ self.logger.warning(f"Cannot auto-fix: {message}")
631
+ return available
629
632
 
630
633
  except Exception as e:
631
634
  self.logger.error(f"Auto-fix failed for {service_name}: {e}")