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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/OUTPUT_STYLE.md +48 -3
- claude_mpm/agents/templates/project_organizer.json +3 -3
- claude_mpm/cli/commands/auto_configure.py +7 -9
- claude_mpm/cli/commands/local_deploy.py +3 -2
- claude_mpm/cli/commands/mpm_init.py +4 -4
- claude_mpm/cli/commands/mpm_init_handler.py +8 -3
- claude_mpm/core/base_service.py +13 -12
- claude_mpm/core/enums.py +36 -1
- claude_mpm/services/agents/auto_config_manager.py +9 -10
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +6 -4
- claude_mpm/services/agents/registry/modification_tracker.py +5 -2
- claude_mpm/services/command_handler_service.py +11 -5
- claude_mpm/services/core/interfaces/process.py +6 -6
- claude_mpm/services/core/models/__init__.py +0 -2
- claude_mpm/services/core/models/agent_config.py +12 -28
- claude_mpm/services/core/models/process.py +19 -42
- claude_mpm/services/diagnostics/__init__.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
- claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
- claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
- claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
- claude_mpm/services/diagnostics/checks/installation_check.py +28 -28
- claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
- claude_mpm/services/diagnostics/checks/mcp_check.py +31 -31
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +36 -31
- claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
- claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
- claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
- claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
- claude_mpm/services/diagnostics/models.py +19 -24
- claude_mpm/services/local_ops/__init__.py +3 -3
- claude_mpm/services/local_ops/process_manager.py +12 -12
- claude_mpm/services/local_ops/state_manager.py +6 -5
- claude_mpm/services/local_ops/unified_manager.py +2 -2
- claude_mpm/services/mcp_config_manager.py +7 -2
- claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
- claude_mpm/services/mcp_gateway/core/base.py +18 -31
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +71 -24
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +23 -22
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +2 -2
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +2 -2
- claude_mpm/services/unified/deployment_strategies/local.py +3 -3
- claude_mpm/services/unified/deployment_strategies/utils.py +5 -5
- claude_mpm/services/unified/deployment_strategies/vercel.py +6 -6
- claude_mpm/services/unified/unified_analyzer.py +8 -5
- claude_mpm/services/unified/unified_deployment.py +2 -2
- {claude_mpm-4.14.9.dist-info → claude_mpm-4.15.1.dist-info}/METADATA +1 -1
- {claude_mpm-4.14.9.dist-info → claude_mpm-4.15.1.dist-info}/RECORD +59 -59
- {claude_mpm-4.14.9.dist-info → claude_mpm-4.15.1.dist-info}/WHEEL +0 -0
- {claude_mpm-4.14.9.dist-info → claude_mpm-4.15.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.14.9.dist-info → claude_mpm-4.15.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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:
|
|
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 (
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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 ==
|
|
82
|
+
if result.status == OperationResult.SUCCESS:
|
|
88
83
|
self.ok_count += 1
|
|
89
|
-
elif result.status ==
|
|
84
|
+
elif result.status == ValidationSeverity.WARNING:
|
|
90
85
|
self.warning_count += 1
|
|
91
|
-
elif result.status ==
|
|
86
|
+
elif result.status == ValidationSeverity.ERROR:
|
|
92
87
|
self.error_count += 1
|
|
93
|
-
elif result.status ==
|
|
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) ->
|
|
97
|
+
def overall_status(self) -> Union[OperationResult, ValidationSeverity]:
|
|
103
98
|
"""Get overall system status."""
|
|
104
99
|
if self.error_count > 0:
|
|
105
|
-
return
|
|
100
|
+
return ValidationSeverity.ERROR
|
|
106
101
|
if self.warning_count > 0:
|
|
107
|
-
return
|
|
108
|
-
return
|
|
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=
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
373
|
+
status = ServiceState.RUNNING
|
|
374
374
|
else:
|
|
375
|
-
status =
|
|
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=
|
|
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[
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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[
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
-
|
|
80
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 !=
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 [
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ==
|
|
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
|
|
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
|
-
|
|
10
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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.
|
|
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
|
|
526
|
-
for external MCP services. The actual registration of these services
|
|
527
|
-
|
|
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
|
|
530
|
-
|
|
531
|
-
|
|
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
|
|
544
|
-
|
|
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
|
|
548
|
-
#
|
|
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
|
-
|
|
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:
|