claude-mpm 4.14.9__py3-none-any.whl → 4.15.1__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (59) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/OUTPUT_STYLE.md +48 -3
  3. claude_mpm/agents/templates/project_organizer.json +3 -3
  4. claude_mpm/cli/commands/auto_configure.py +7 -9
  5. claude_mpm/cli/commands/local_deploy.py +3 -2
  6. claude_mpm/cli/commands/mpm_init.py +4 -4
  7. claude_mpm/cli/commands/mpm_init_handler.py +8 -3
  8. claude_mpm/core/base_service.py +13 -12
  9. claude_mpm/core/enums.py +36 -1
  10. claude_mpm/services/agents/auto_config_manager.py +9 -10
  11. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
  12. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
  13. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
  14. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
  15. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
  16. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +6 -4
  17. claude_mpm/services/agents/registry/modification_tracker.py +5 -2
  18. claude_mpm/services/command_handler_service.py +11 -5
  19. claude_mpm/services/core/interfaces/process.py +6 -6
  20. claude_mpm/services/core/models/__init__.py +0 -2
  21. claude_mpm/services/core/models/agent_config.py +12 -28
  22. claude_mpm/services/core/models/process.py +19 -42
  23. claude_mpm/services/diagnostics/__init__.py +2 -2
  24. claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
  25. claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
  26. claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
  27. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
  28. claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
  29. claude_mpm/services/diagnostics/checks/installation_check.py +28 -28
  30. claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
  31. claude_mpm/services/diagnostics/checks/mcp_check.py +31 -31
  32. claude_mpm/services/diagnostics/checks/mcp_services_check.py +36 -31
  33. claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
  34. claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
  35. claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
  36. claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
  37. claude_mpm/services/diagnostics/models.py +19 -24
  38. claude_mpm/services/local_ops/__init__.py +3 -3
  39. claude_mpm/services/local_ops/process_manager.py +12 -12
  40. claude_mpm/services/local_ops/state_manager.py +6 -5
  41. claude_mpm/services/local_ops/unified_manager.py +2 -2
  42. claude_mpm/services/mcp_config_manager.py +7 -2
  43. claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
  44. claude_mpm/services/mcp_gateway/core/base.py +18 -31
  45. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +71 -24
  46. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +23 -22
  47. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +2 -2
  48. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +2 -2
  49. claude_mpm/services/unified/deployment_strategies/local.py +3 -3
  50. claude_mpm/services/unified/deployment_strategies/utils.py +5 -5
  51. claude_mpm/services/unified/deployment_strategies/vercel.py +6 -6
  52. claude_mpm/services/unified/unified_analyzer.py +8 -5
  53. claude_mpm/services/unified/unified_deployment.py +2 -2
  54. {claude_mpm-4.14.9.dist-info → claude_mpm-4.15.1.dist-info}/METADATA +1 -1
  55. {claude_mpm-4.14.9.dist-info → claude_mpm-4.15.1.dist-info}/RECORD +59 -59
  56. {claude_mpm-4.14.9.dist-info → claude_mpm-4.15.1.dist-info}/WHEEL +0 -0
  57. {claude_mpm-4.14.9.dist-info → claude_mpm-4.15.1.dist-info}/entry_points.txt +0 -0
  58. {claude_mpm-4.14.9.dist-info → claude_mpm-4.15.1.dist-info}/licenses/LICENSE +0 -0
  59. {claude_mpm-4.14.9.dist-info → claude_mpm-4.15.1.dist-info}/top_level.txt +0 -0
@@ -6,17 +6,9 @@ consistency across all checks and reporting.
6
6
  """
7
7
 
8
8
  from dataclasses import dataclass, field
9
- from enum import Enum
10
- from typing import Any, Dict, List, Optional
9
+ from typing import Any, Dict, List, Optional, Union
11
10
 
12
-
13
- class DiagnosticStatus(Enum):
14
- """Status levels for diagnostic results."""
15
-
16
- OK = "ok"
17
- WARNING = "warning"
18
- ERROR = "error"
19
- SKIPPED = "skipped"
11
+ from ...core.enums import OperationResult, ValidationSeverity
20
12
 
21
13
 
22
14
  @dataclass
@@ -25,10 +17,13 @@ class DiagnosticResult:
25
17
 
26
18
  WHY: Standardized result format ensures consistent reporting
27
19
  and makes it easy to aggregate and display results.
20
+
21
+ Note: status uses Union[OperationResult, ValidationSeverity] to support both
22
+ operation results (SUCCESS, SKIPPED) and validation results (WARNING, ERROR).
28
23
  """
29
24
 
30
25
  category: str # e.g., "Installation", "Agents", "MCP Server"
31
- status: DiagnosticStatus
26
+ status: Union[OperationResult, ValidationSeverity]
32
27
  message: str
33
28
  details: Dict[str, Any] = field(default_factory=dict)
34
29
  fix_command: Optional[str] = None
@@ -50,16 +45,16 @@ class DiagnosticResult:
50
45
  @property
51
46
  def has_issues(self) -> bool:
52
47
  """Check if this result indicates any issues."""
53
- return self.status in (DiagnosticStatus.WARNING, DiagnosticStatus.ERROR)
48
+ return self.status in (ValidationSeverity.WARNING, ValidationSeverity.ERROR)
54
49
 
55
50
  @property
56
51
  def severity_level(self) -> int:
57
52
  """Get numeric severity level for sorting."""
58
53
  severity_map = {
59
- DiagnosticStatus.OK: 0,
60
- DiagnosticStatus.SKIPPED: 1,
61
- DiagnosticStatus.WARNING: 2,
62
- DiagnosticStatus.ERROR: 3,
54
+ OperationResult.SUCCESS: 0,
55
+ OperationResult.SKIPPED: 1,
56
+ ValidationSeverity.WARNING: 2,
57
+ ValidationSeverity.ERROR: 3,
63
58
  }
64
59
  return severity_map.get(self.status, 0)
65
60
 
@@ -84,13 +79,13 @@ class DiagnosticSummary:
84
79
  self.results.append(result)
85
80
  self.total_checks += 1
86
81
 
87
- if result.status == DiagnosticStatus.OK:
82
+ if result.status == OperationResult.SUCCESS:
88
83
  self.ok_count += 1
89
- elif result.status == DiagnosticStatus.WARNING:
84
+ elif result.status == ValidationSeverity.WARNING:
90
85
  self.warning_count += 1
91
- elif result.status == DiagnosticStatus.ERROR:
86
+ elif result.status == ValidationSeverity.ERROR:
92
87
  self.error_count += 1
93
- elif result.status == DiagnosticStatus.SKIPPED:
88
+ elif result.status == OperationResult.SKIPPED:
94
89
  self.skipped_count += 1
95
90
 
96
91
  @property
@@ -99,13 +94,13 @@ class DiagnosticSummary:
99
94
  return self.warning_count > 0 or self.error_count > 0
100
95
 
101
96
  @property
102
- def overall_status(self) -> DiagnosticStatus:
97
+ def overall_status(self) -> Union[OperationResult, ValidationSeverity]:
103
98
  """Get overall system status."""
104
99
  if self.error_count > 0:
105
- return DiagnosticStatus.ERROR
100
+ return ValidationSeverity.ERROR
106
101
  if self.warning_count > 0:
107
- return DiagnosticStatus.WARNING
108
- return DiagnosticStatus.OK
102
+ return ValidationSeverity.WARNING
103
+ return OperationResult.SUCCESS
109
104
 
110
105
  def to_dict(self) -> Dict[str, Any]:
111
106
  """Convert to dictionary for JSON serialization."""
@@ -32,9 +32,9 @@ USAGE:
32
32
  DeploymentStateManager,
33
33
  HealthCheckManager,
34
34
  StartConfig,
35
- ProcessStatus,
36
35
  HealthStatus,
37
36
  )
37
+ from claude_mpm.core.enums import ServiceState
38
38
 
39
39
  # Initialize managers
40
40
  state_manager = DeploymentStateManager(".claude-mpm/deployment-state.json")
@@ -54,6 +54,8 @@ USAGE:
54
54
 
55
55
  # Start background monitoring
56
56
  health_manager.start_monitoring()
57
+
58
+ Note: ProcessStatus has been consolidated into ServiceState (core.enums) as of Phase 3A Batch 24.
57
59
  """
58
60
 
59
61
  # Re-export data models and interfaces for convenience
@@ -84,7 +86,6 @@ from claude_mpm.services.core.models.process import (
84
86
  PROTECTED_PORT_RANGES,
85
87
  DeploymentState,
86
88
  ProcessInfo,
87
- ProcessStatus,
88
89
  StartConfig,
89
90
  is_port_protected,
90
91
  )
@@ -147,7 +148,6 @@ __all__ = [
147
148
  "ProcessInfo",
148
149
  "ProcessSpawnError",
149
150
  # Data models - Process
150
- "ProcessStatus",
151
151
  "ResourceMonitor",
152
152
  "ResourceUsage",
153
153
  "RestartAttempt",
@@ -42,6 +42,7 @@ from typing import List, Optional
42
42
 
43
43
  import psutil
44
44
 
45
+ from claude_mpm.core.enums import ServiceState
45
46
  from claude_mpm.services.core.base import SyncBaseService
46
47
  from claude_mpm.services.core.interfaces.process import (
47
48
  IDeploymentStateManager,
@@ -50,7 +51,6 @@ from claude_mpm.services.core.interfaces.process import (
50
51
  from claude_mpm.services.core.models.process import (
51
52
  DeploymentState,
52
53
  ProcessInfo,
53
- ProcessStatus,
54
54
  StartConfig,
55
55
  is_port_protected,
56
56
  )
@@ -203,7 +203,7 @@ class LocalProcessManager(SyncBaseService, ILocalProcessManager):
203
203
  environment=config.environment,
204
204
  port=allocated_port,
205
205
  started_at=datetime.now(tz=timezone.utc),
206
- status=ProcessStatus.RUNNING,
206
+ status=ServiceState.RUNNING,
207
207
  metadata=config.metadata,
208
208
  )
209
209
 
@@ -248,13 +248,13 @@ class LocalProcessManager(SyncBaseService, ILocalProcessManager):
248
248
  # Process already dead, just update state
249
249
  self.log_info(f"Process {deployment.process_id} already dead")
250
250
  self.state_manager.update_deployment_status(
251
- deployment_id, ProcessStatus.STOPPED
251
+ deployment_id, ServiceState.STOPPED
252
252
  )
253
253
  return True
254
254
 
255
255
  self.log_info(f"Stopping process {deployment.process_id} for {deployment_id}")
256
256
  self.state_manager.update_deployment_status(
257
- deployment_id, ProcessStatus.STOPPING
257
+ deployment_id, ServiceState.STOPPING
258
258
  )
259
259
 
260
260
  try:
@@ -262,7 +262,7 @@ class LocalProcessManager(SyncBaseService, ILocalProcessManager):
262
262
  # Force kill immediately
263
263
  self._kill_process_group(process)
264
264
  self.state_manager.update_deployment_status(
265
- deployment_id, ProcessStatus.STOPPED
265
+ deployment_id, ServiceState.STOPPED
266
266
  )
267
267
  return True
268
268
 
@@ -275,7 +275,7 @@ class LocalProcessManager(SyncBaseService, ILocalProcessManager):
275
275
  if not process.is_running():
276
276
  self.log_info(f"Process {deployment.process_id} stopped gracefully")
277
277
  self.state_manager.update_deployment_status(
278
- deployment_id, ProcessStatus.STOPPED
278
+ deployment_id, ServiceState.STOPPED
279
279
  )
280
280
  return True
281
281
  time.sleep(0.1)
@@ -286,14 +286,14 @@ class LocalProcessManager(SyncBaseService, ILocalProcessManager):
286
286
  )
287
287
  self._kill_process_group(process)
288
288
  self.state_manager.update_deployment_status(
289
- deployment_id, ProcessStatus.STOPPED
289
+ deployment_id, ServiceState.STOPPED
290
290
  )
291
291
  return True
292
292
 
293
293
  except psutil.NoSuchProcess:
294
294
  # Process died during shutdown
295
295
  self.state_manager.update_deployment_status(
296
- deployment_id, ProcessStatus.STOPPED
296
+ deployment_id, ServiceState.STOPPED
297
297
  )
298
298
  return True
299
299
 
@@ -370,9 +370,9 @@ class LocalProcessManager(SyncBaseService, ILocalProcessManager):
370
370
 
371
371
  # Determine status
372
372
  if process.is_running():
373
- status = ProcessStatus.RUNNING
373
+ status = ServiceState.RUNNING
374
374
  else:
375
- status = ProcessStatus.STOPPED
375
+ status = ServiceState.STOPPED
376
376
 
377
377
  return ProcessInfo(
378
378
  deployment_id=deployment_id,
@@ -389,13 +389,13 @@ class LocalProcessManager(SyncBaseService, ILocalProcessManager):
389
389
  return ProcessInfo(
390
390
  deployment_id=deployment_id,
391
391
  process_id=deployment.process_id,
392
- status=ProcessStatus.CRASHED,
392
+ status=ServiceState.ERROR, # CRASHED semantically maps to ERROR state
393
393
  port=deployment.port,
394
394
  error_message="Process no longer exists",
395
395
  )
396
396
 
397
397
  def list_processes(
398
- self, status_filter: Optional[ProcessStatus] = None
398
+ self, status_filter: Optional[ServiceState] = None
399
399
  ) -> List[ProcessInfo]:
400
400
  """
401
401
  List all managed processes.
@@ -30,9 +30,10 @@ from typing import Dict, List, Optional
30
30
  import psutil
31
31
  from filelock import FileLock
32
32
 
33
+ from claude_mpm.core.enums import ServiceState
33
34
  from claude_mpm.services.core.base import SyncBaseService
34
35
  from claude_mpm.services.core.interfaces.process import IDeploymentStateManager
35
- from claude_mpm.services.core.models.process import DeploymentState, ProcessStatus
36
+ from claude_mpm.services.core.models.process import DeploymentState
36
37
 
37
38
 
38
39
  class StateCorruptionError(Exception):
@@ -206,12 +207,12 @@ class DeploymentStateManager(SyncBaseService, IDeploymentStateManager):
206
207
  states = self.load_state()
207
208
  return list(states.values())
208
209
 
209
- def get_deployments_by_status(self, status: ProcessStatus) -> List[DeploymentState]:
210
+ def get_deployments_by_status(self, status: ServiceState) -> List[DeploymentState]:
210
211
  """
211
212
  Get all deployments with a specific status.
212
213
 
213
214
  Args:
214
- status: ProcessStatus to filter by
215
+ status: ServiceState to filter by
215
216
 
216
217
  Returns:
217
218
  List of matching DeploymentState objects
@@ -295,14 +296,14 @@ class DeploymentStateManager(SyncBaseService, IDeploymentStateManager):
295
296
  return False
296
297
 
297
298
  def update_deployment_status(
298
- self, deployment_id: str, status: ProcessStatus
299
+ self, deployment_id: str, status: ServiceState
299
300
  ) -> bool:
300
301
  """
301
302
  Update the status of a deployment.
302
303
 
303
304
  Args:
304
305
  deployment_id: Unique deployment identifier
305
- status: New ProcessStatus
306
+ status: New ServiceState
306
307
 
307
308
  Returns:
308
309
  True if updated, False if deployment not found
@@ -45,12 +45,12 @@ from typing import Any, Dict, List, Optional
45
45
 
46
46
  import yaml
47
47
 
48
+ from claude_mpm.core.enums import ServiceState
48
49
  from claude_mpm.services.core.base import SyncBaseService
49
50
  from claude_mpm.services.core.models.health import DeploymentHealth
50
51
  from claude_mpm.services.core.models.process import (
51
52
  DeploymentState,
52
53
  ProcessInfo,
53
- ProcessStatus,
54
54
  StartConfig,
55
55
  )
56
56
  from claude_mpm.services.core.models.restart import RestartConfig, RestartHistory
@@ -384,7 +384,7 @@ class UnifiedLocalOpsManager(SyncBaseService):
384
384
 
385
385
  def list_deployments(
386
386
  self,
387
- status_filter: Optional[ProcessStatus] = None,
387
+ status_filter: Optional[ServiceState] = None,
388
388
  ) -> List[DeploymentState]:
389
389
  """
390
390
  List all deployments.
@@ -1145,11 +1145,16 @@ class MCPConfigManager:
1145
1145
 
1146
1146
  # Check each service for issues
1147
1147
  for service_name in self.PIPX_SERVICES:
1148
- self.logger.info(f"🔍 Checking {service_name} for issues...")
1148
+ # Check if service is enabled in config
1149
+ if not self.should_enable_service(service_name):
1150
+ self.logger.debug(f"Skipping {service_name} (disabled in config)")
1151
+ continue
1152
+
1153
+ self.logger.debug(f"🔍 Checking {service_name} for issues...")
1149
1154
  issue_type = self._detect_service_issue(service_name)
1150
1155
  if issue_type:
1151
1156
  services_to_fix.append((service_name, issue_type))
1152
- self.logger.info(f" ⚠️ Found issue with {service_name}: {issue_type}")
1157
+ self.logger.debug(f" ⚠️ Found issue with {service_name}: {issue_type}")
1153
1158
  else:
1154
1159
  self.logger.debug(f" ✅ {service_name} is functioning correctly")
1155
1160
 
@@ -5,7 +5,7 @@ MCP Gateway Core Module
5
5
  Core interfaces and base classes for the MCP Gateway service.
6
6
  """
7
7
 
8
- from .base import BaseMCPService, MCPServiceState
8
+ from .base import BaseMCPService
9
9
  from .exceptions import (
10
10
  MCPCommunicationError,
11
11
  MCPConfigurationError,
@@ -38,7 +38,6 @@ __all__ = [
38
38
  # Exceptions
39
39
  "MCPException",
40
40
  "MCPServerError",
41
- "MCPServiceState",
42
41
  "MCPToolNotFoundError",
43
42
  "MCPValidationError",
44
43
  ]
@@ -8,25 +8,12 @@ Part of ISS-0034: Infrastructure Setup - MCP Gateway Project Foundation
8
8
  """
9
9
 
10
10
  import asyncio
11
- from enum import Enum
12
11
  from typing import Any, Dict, Optional
13
12
 
13
+ from claude_mpm.core.enums import ServiceState
14
14
  from claude_mpm.services.core.base import BaseService
15
15
 
16
16
 
17
- class MCPServiceState(Enum):
18
- """MCP service lifecycle states."""
19
-
20
- UNINITIALIZED = "uninitialized"
21
- INITIALIZING = "initializing"
22
- INITIALIZED = "initialized"
23
- STARTING = "starting"
24
- RUNNING = "running"
25
- STOPPING = "stopping"
26
- STOPPED = "stopped"
27
- ERROR = "error"
28
-
29
-
30
17
  class BaseMCPService(BaseService):
31
18
  """
32
19
  Base class for all MCP Gateway services.
@@ -53,7 +40,7 @@ class BaseMCPService(BaseService):
53
40
  config: Service-specific configuration
54
41
  """
55
42
  super().__init__(service_name or "MCPService", config)
56
- self._state = MCPServiceState.UNINITIALIZED
43
+ self._state = ServiceState.UNINITIALIZED
57
44
  self._health_status = {
58
45
  "healthy": False,
59
46
  "state": self._state.value,
@@ -76,13 +63,13 @@ class BaseMCPService(BaseService):
76
63
  """
77
64
  async with self._state_lock:
78
65
  if self._state not in [
79
- MCPServiceState.UNINITIALIZED,
80
- MCPServiceState.STOPPED,
66
+ ServiceState.UNINITIALIZED,
67
+ ServiceState.STOPPED,
81
68
  ]:
82
69
  self.log_warning(f"Cannot initialize from state {self._state.value}")
83
70
  return False
84
71
 
85
- self._state = MCPServiceState.INITIALIZING
72
+ self._state = ServiceState.INITIALIZING
86
73
  self.log_info("Initializing MCP service")
87
74
 
88
75
  try:
@@ -91,13 +78,13 @@ class BaseMCPService(BaseService):
91
78
 
92
79
  async with self._state_lock:
93
80
  if success:
94
- self._state = MCPServiceState.INITIALIZED
81
+ self._state = ServiceState.INITIALIZED
95
82
  self._initialized = True
96
83
  self._health_status["healthy"] = True
97
84
  self._health_status["state"] = self._state.value
98
85
  self.log_info("MCP service initialized successfully")
99
86
  else:
100
- self._state = MCPServiceState.ERROR
87
+ self._state = ServiceState.ERROR
101
88
  self._health_status["healthy"] = False
102
89
  self._health_status["state"] = self._state.value
103
90
  self.log_error("MCP service initialization failed")
@@ -106,7 +93,7 @@ class BaseMCPService(BaseService):
106
93
 
107
94
  except Exception as e:
108
95
  async with self._state_lock:
109
- self._state = MCPServiceState.ERROR
96
+ self._state = ServiceState.ERROR
110
97
  self._health_status["healthy"] = False
111
98
  self._health_status["state"] = self._state.value
112
99
  self._health_status["details"]["error"] = str(e)
@@ -134,11 +121,11 @@ class BaseMCPService(BaseService):
134
121
  True if startup successful
135
122
  """
136
123
  async with self._state_lock:
137
- if self._state != MCPServiceState.INITIALIZED:
124
+ if self._state != ServiceState.INITIALIZED:
138
125
  self.log_warning(f"Cannot start from state {self._state.value}")
139
126
  return False
140
127
 
141
- self._state = MCPServiceState.STARTING
128
+ self._state = ServiceState.STARTING
142
129
  self.log_info("Starting MCP service")
143
130
 
144
131
  try:
@@ -146,12 +133,12 @@ class BaseMCPService(BaseService):
146
133
 
147
134
  async with self._state_lock:
148
135
  if success:
149
- self._state = MCPServiceState.RUNNING
136
+ self._state = ServiceState.RUNNING
150
137
  self._health_status["healthy"] = True
151
138
  self._health_status["state"] = self._state.value
152
139
  self.log_info("MCP service started successfully")
153
140
  else:
154
- self._state = MCPServiceState.ERROR
141
+ self._state = ServiceState.ERROR
155
142
  self._health_status["healthy"] = False
156
143
  self._health_status["state"] = self._state.value
157
144
  self.log_error("MCP service startup failed")
@@ -160,7 +147,7 @@ class BaseMCPService(BaseService):
160
147
 
161
148
  except Exception as e:
162
149
  async with self._state_lock:
163
- self._state = MCPServiceState.ERROR
150
+ self._state = ServiceState.ERROR
164
151
  self._health_status["healthy"] = False
165
152
  self._health_status["state"] = self._state.value
166
153
  self._health_status["details"]["error"] = str(e)
@@ -188,18 +175,18 @@ class BaseMCPService(BaseService):
188
175
  Subclasses should override _do_shutdown() for custom shutdown logic.
189
176
  """
190
177
  async with self._state_lock:
191
- if self._state in [MCPServiceState.STOPPED, MCPServiceState.STOPPING]:
178
+ if self._state in [ServiceState.STOPPED, ServiceState.STOPPING]:
192
179
  self.log_warning(f"Already in state {self._state.value}")
193
180
  return
194
181
 
195
- self._state = MCPServiceState.STOPPING
182
+ self._state = ServiceState.STOPPING
196
183
  self.log_info("Shutting down MCP service")
197
184
 
198
185
  try:
199
186
  await self._do_shutdown()
200
187
 
201
188
  async with self._state_lock:
202
- self._state = MCPServiceState.STOPPED
189
+ self._state = ServiceState.STOPPED
203
190
  self._shutdown = True
204
191
  self._health_status["healthy"] = False
205
192
  self._health_status["state"] = self._state.value
@@ -207,7 +194,7 @@ class BaseMCPService(BaseService):
207
194
 
208
195
  except Exception as e:
209
196
  async with self._state_lock:
210
- self._state = MCPServiceState.ERROR
197
+ self._state = ServiceState.ERROR
211
198
  self._health_status["healthy"] = False
212
199
  self._health_status["state"] = self._state.value
213
200
  self._health_status["details"]["error"] = str(e)
@@ -232,7 +219,7 @@ class BaseMCPService(BaseService):
232
219
  self.log_info("Restarting MCP service")
233
220
 
234
221
  # Shutdown if running
235
- if self._state == MCPServiceState.RUNNING:
222
+ if self._state == ServiceState.RUNNING:
236
223
  await self.shutdown()
237
224
 
238
225
  # Re-initialize
@@ -2,12 +2,22 @@
2
2
  External MCP Services Integration
3
3
  ==================================
4
4
 
5
- Manages installation and basic setup of external MCP services like mcp-vector-search
5
+ Manages detection and setup of external MCP services like mcp-vector-search
6
6
  and mcp-browser. These services run as separate MCP servers in Claude Code,
7
7
  not as part of the Claude MPM MCP Gateway.
8
8
 
9
- Note: As of the latest architecture, external services are registered as separate
10
- MCP servers in Claude Code configuration, not as tools within the gateway.
9
+ IMPORTANT: External services are NOT auto-installed. Users must manually install
10
+ them using pipx or pip. This gives users explicit control over which optional
11
+ services they want to enable.
12
+
13
+ Installation:
14
+ pipx install mcp-vector-search
15
+ pipx install mcp-browser
16
+ pipx install kuzu-memory
17
+ pipx install mcp-ticketer
18
+
19
+ Note: External services are registered as separate MCP servers in Claude Code
20
+ configuration, not as tools within the gateway.
11
21
  """
12
22
 
13
23
  import json
@@ -20,7 +30,12 @@ from claude_mpm.services.mcp_gateway.tools.base_adapter import BaseToolAdapter
20
30
 
21
31
 
22
32
  class ExternalMCPService(BaseToolAdapter):
23
- """Base class for external MCP service integration."""
33
+ """Base class for external MCP service integration.
34
+
35
+ External services are detected if already installed but are NOT
36
+ automatically installed. Users must install them manually using
37
+ pipx or pip to enable these optional features.
38
+ """
24
39
 
25
40
  def __init__(self, service_name: str, package_name: str):
26
41
  """
@@ -65,27 +80,37 @@ class ExternalMCPService(BaseToolAdapter):
65
80
  )
66
81
 
67
82
  async def initialize(
68
- self, auto_install: bool = True, interactive: bool = True
83
+ self, auto_install: bool = False, interactive: bool = False
69
84
  ) -> bool:
70
85
  """Initialize the external service.
71
86
 
87
+ NOTE: Auto-installation is disabled by default (v4.9.0+). Users must
88
+ manually install external services using pipx or pip.
89
+
72
90
  Args:
73
- auto_install: Whether to automatically install if not found
74
- interactive: Whether to prompt user for installation preferences
91
+ auto_install: Whether to automatically install if not found (default: False)
92
+ Deprecated - will be removed in future versions
93
+ interactive: Whether to prompt user for installation preferences (default: False)
94
+ Only used if auto_install=True
75
95
  """
76
96
  try:
77
97
  # Check if package is installed
78
98
  self._is_installed = await self._check_installation()
79
99
 
80
100
  if not self._is_installed and auto_install:
81
- self.logger.info(
82
- f"{self.package_name} not installed - attempting installation"
101
+ # This path is deprecated but kept for backward compatibility
102
+ self.logger.warning(
103
+ f"Auto-installation is deprecated. Please install {self.package_name} manually: "
104
+ f"pipx install {self.package_name}"
83
105
  )
84
106
  await self._install_package(interactive=interactive)
85
107
  self._is_installed = await self._check_installation()
86
108
 
87
109
  if not self._is_installed:
88
- self.logger.warning(f"{self.package_name} is not available")
110
+ self.logger.debug(
111
+ f"{self.package_name} is not available. "
112
+ f"Install manually with: pipx install {self.package_name}"
113
+ )
89
114
  return False
90
115
 
91
116
  self.logger.info(f"{self.package_name} is available")
@@ -522,13 +547,20 @@ class MCPBrowserService(ExternalMCPService):
522
547
  class ExternalMCPServiceManager:
523
548
  """Manager for external MCP services.
524
549
 
525
- This manager is responsible for checking and installing Python packages
526
- for external MCP services. The actual registration of these services
527
- happens in Claude Code configuration as separate MCP servers.
550
+ This manager is responsible for detecting (but NOT installing) Python packages
551
+ for external MCP services. The actual registration of these services happens
552
+ in Claude Code configuration as separate MCP servers.
553
+
554
+ IMPORTANT: As of v4.9.0, this manager NO LONGER auto-installs missing services.
555
+ Users must manually install external services using pipx or pip:
556
+ - pipx install mcp-vector-search
557
+ - pipx install mcp-browser
558
+ - pipx install kuzu-memory
559
+ - pipx install mcp-ticketer
528
560
 
529
- Note: This class is maintained for backward compatibility and package
530
- management. The actual tool registration is handled by separate MCP
531
- server instances in Claude Code.
561
+ Note: This class is maintained for backward compatibility and service detection.
562
+ The actual tool registration is handled by separate MCP server instances in
563
+ Claude Code.
532
564
  """
533
565
 
534
566
  def __init__(self):
@@ -539,20 +571,34 @@ class ExternalMCPServiceManager:
539
571
  async def initialize_services(self) -> List[ExternalMCPService]:
540
572
  """Initialize all external MCP services.
541
573
 
542
- This method checks if external service packages are installed
543
- and attempts to install them if missing. It does NOT register
544
- them as tools in the gateway - they run as separate MCP servers.
574
+ This method checks if external service packages are already installed
575
+ and registers them if available. It does NOT auto-install missing services.
576
+
577
+ External MCP services (mcp-vector-search, mcp-browser, kuzu-memory, mcp-ticketer)
578
+ must be manually installed by users. This gives users explicit control over
579
+ which services they want to use.
580
+
581
+ Installation instructions:
582
+ - mcp-vector-search: pipx install mcp-vector-search
583
+ - mcp-browser: pipx install mcp-browser
584
+ - kuzu-memory: pipx install kuzu-memory
585
+ - mcp-ticketer: pipx install mcp-ticketer
586
+
587
+ Services run as separate MCP servers in Claude Code, not as tools within
588
+ the gateway.
545
589
  """
546
590
  # Create service instances
547
- # Note: kuzu-memory is configured via MCPConfigManager and runs as a separate MCP server
548
- # It doesn't need to be included here since it's already set up through the MCP config
591
+ # Note: kuzu-memory and mcp-ticketer are configured via MCPConfigManager
592
+ # and run as separate MCP servers. They don't need to be included here
593
+ # since they're already set up through the MCP config.
549
594
  services = [MCPVectorSearchService(), MCPBrowserService()]
550
595
 
551
- # Initialize each service
596
+ # Initialize each service (check if installed, but DO NOT auto-install)
552
597
  initialized_services = []
553
598
  for service in services:
554
599
  try:
555
- if await service.initialize():
600
+ # Pass auto_install=False to prevent automatic installation
601
+ if await service.initialize(auto_install=False, interactive=False):
556
602
  initialized_services.append(service)
557
603
  if self.logger:
558
604
  self.logger.info(
@@ -560,7 +606,8 @@ class ExternalMCPServiceManager:
560
606
  )
561
607
  elif self.logger:
562
608
  self.logger.debug(
563
- f"Service not available (optional): {service.service_name}"
609
+ f"Service not available (optional): {service.service_name}. "
610
+ f"Install manually with: pipx install {service.package_name}"
564
611
  )
565
612
  except Exception as e:
566
613
  if self.logger: