claude-mpm 4.4.6__py3-none-any.whl → 4.4.7__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/templates/local_ops_agent.json +273 -0
- claude_mpm/cli/__init__.py +21 -0
- claude_mpm/cli/commands/verify.py +118 -0
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +10 -6
- claude_mpm/services/mcp_service_verifier.py +690 -0
- {claude_mpm-4.4.6.dist-info → claude_mpm-4.4.7.dist-info}/METADATA +13 -1
- {claude_mpm-4.4.6.dist-info → claude_mpm-4.4.7.dist-info}/RECORD +13 -10
- {claude_mpm-4.4.6.dist-info → claude_mpm-4.4.7.dist-info}/WHEEL +0 -0
- {claude_mpm-4.4.6.dist-info → claude_mpm-4.4.7.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.4.6.dist-info → claude_mpm-4.4.7.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.4.6.dist-info → claude_mpm-4.4.7.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
4.4.
|
1
|
+
4.4.7
|
@@ -0,0 +1,273 @@
|
|
1
|
+
{
|
2
|
+
"name": "local-ops",
|
3
|
+
"display_name": "Local Operations Agent",
|
4
|
+
"description": "Specialized agent for managing local development deployments with authority over PM2, Docker, and native processes",
|
5
|
+
"version": "1.0.0",
|
6
|
+
"author": "Claude MPM",
|
7
|
+
"authority": {
|
8
|
+
"level": "deployment_manager",
|
9
|
+
"domains": [
|
10
|
+
"local_deployments",
|
11
|
+
"process_management",
|
12
|
+
"port_allocation",
|
13
|
+
"resource_monitoring",
|
14
|
+
"log_management"
|
15
|
+
]
|
16
|
+
},
|
17
|
+
"capabilities": {
|
18
|
+
"frameworks": {
|
19
|
+
"javascript": ["next.js", "react", "vue", "angular", "svelte", "nuxt", "gatsby", "vite"],
|
20
|
+
"python": ["django", "flask", "fastapi", "streamlit", "gradio"],
|
21
|
+
"ruby": ["rails", "sinatra"],
|
22
|
+
"php": ["laravel", "symfony"],
|
23
|
+
"static": ["hugo", "jekyll", "eleventy"]
|
24
|
+
},
|
25
|
+
"deployment_methods": {
|
26
|
+
"pm2": {
|
27
|
+
"description": "Node.js process manager for production deployments",
|
28
|
+
"commands": ["start", "stop", "restart", "status", "logs", "monit"],
|
29
|
+
"features": ["auto-restart", "clustering", "log-rotation", "monitoring"]
|
30
|
+
},
|
31
|
+
"docker": {
|
32
|
+
"description": "Container-based deployment for isolation",
|
33
|
+
"commands": ["build", "run", "stop", "logs", "exec"],
|
34
|
+
"features": ["isolation", "reproducibility", "multi-service"]
|
35
|
+
},
|
36
|
+
"native": {
|
37
|
+
"description": "Direct process management for simple cases",
|
38
|
+
"commands": ["start", "stop", "status"],
|
39
|
+
"features": ["lightweight", "direct-control"]
|
40
|
+
},
|
41
|
+
"systemd": {
|
42
|
+
"description": "Linux system service management",
|
43
|
+
"commands": ["start", "stop", "enable", "status"],
|
44
|
+
"features": ["boot-persistence", "system-integration"]
|
45
|
+
}
|
46
|
+
},
|
47
|
+
"monitoring": {
|
48
|
+
"health_checks": ["http", "tcp", "process"],
|
49
|
+
"metrics": ["cpu", "memory", "response_time", "error_rate"],
|
50
|
+
"alerts": ["crash", "high_memory", "port_conflict", "unhealthy"]
|
51
|
+
}
|
52
|
+
},
|
53
|
+
"metadata": {
|
54
|
+
"category": "operations",
|
55
|
+
"tags": ["deployment", "devops", "local", "process-management", "monitoring"],
|
56
|
+
"requirements": {
|
57
|
+
"optional": ["pm2", "docker", "nginx"],
|
58
|
+
"auto_install": ["pm2"]
|
59
|
+
}
|
60
|
+
},
|
61
|
+
"configuration": {
|
62
|
+
"default_port_range": [3000, 9999],
|
63
|
+
"deployment_directory": ".claude-mpm/deployments",
|
64
|
+
"log_directory": ".claude-mpm/logs",
|
65
|
+
"state_file": ".claude-mpm/deployment-state.json",
|
66
|
+
"health_check_interval": 30,
|
67
|
+
"auto_restart_attempts": 3,
|
68
|
+
"cleanup_on_exit": false
|
69
|
+
},
|
70
|
+
"commands": {
|
71
|
+
"deploy": {
|
72
|
+
"description": "Deploy application with optimal method",
|
73
|
+
"examples": [
|
74
|
+
"Deploy this Next.js app in production mode",
|
75
|
+
"Start the development server with hot reload",
|
76
|
+
"Deploy with PM2 for stability"
|
77
|
+
],
|
78
|
+
"workflow": [
|
79
|
+
"detect_framework",
|
80
|
+
"check_existing_deployments",
|
81
|
+
"allocate_port",
|
82
|
+
"build_if_needed",
|
83
|
+
"start_process",
|
84
|
+
"monitor_health",
|
85
|
+
"report_status"
|
86
|
+
]
|
87
|
+
},
|
88
|
+
"status": {
|
89
|
+
"description": "Check deployment status",
|
90
|
+
"provides": ["process_info", "port_mapping", "health_status", "resource_usage"]
|
91
|
+
},
|
92
|
+
"logs": {
|
93
|
+
"description": "Stream or fetch deployment logs",
|
94
|
+
"options": ["tail", "follow", "filter", "since"]
|
95
|
+
},
|
96
|
+
"stop": {
|
97
|
+
"description": "Gracefully stop deployments",
|
98
|
+
"options": ["force", "timeout", "cleanup"]
|
99
|
+
},
|
100
|
+
"scale": {
|
101
|
+
"description": "Scale deployments (PM2 cluster mode)",
|
102
|
+
"options": ["instances", "auto"]
|
103
|
+
}
|
104
|
+
},
|
105
|
+
"detection_patterns": {
|
106
|
+
"nextjs": {
|
107
|
+
"files": ["next.config.js", "next.config.mjs", "next.config.ts"],
|
108
|
+
"package_json": ["next"],
|
109
|
+
"commands": {
|
110
|
+
"dev": "next dev",
|
111
|
+
"build": "next build",
|
112
|
+
"start": "next start",
|
113
|
+
"export": "next export"
|
114
|
+
}
|
115
|
+
},
|
116
|
+
"react": {
|
117
|
+
"files": ["react-scripts", "vite.config.js", "webpack.config.js"],
|
118
|
+
"package_json": ["react", "react-dom"],
|
119
|
+
"commands": {
|
120
|
+
"dev": "npm start",
|
121
|
+
"build": "npm run build",
|
122
|
+
"serve": "serve -s build"
|
123
|
+
}
|
124
|
+
},
|
125
|
+
"vue": {
|
126
|
+
"files": ["vue.config.js", "vite.config.js"],
|
127
|
+
"package_json": ["vue", "@vue/cli-service"],
|
128
|
+
"commands": {
|
129
|
+
"dev": "npm run serve",
|
130
|
+
"build": "npm run build",
|
131
|
+
"preview": "npm run preview"
|
132
|
+
}
|
133
|
+
},
|
134
|
+
"python_django": {
|
135
|
+
"files": ["manage.py", "wsgi.py"],
|
136
|
+
"requirements": ["django"],
|
137
|
+
"commands": {
|
138
|
+
"dev": "python manage.py runserver",
|
139
|
+
"prod": "gunicorn wsgi:application"
|
140
|
+
}
|
141
|
+
},
|
142
|
+
"python_flask": {
|
143
|
+
"files": ["app.py", "wsgi.py"],
|
144
|
+
"requirements": ["flask"],
|
145
|
+
"commands": {
|
146
|
+
"dev": "flask run",
|
147
|
+
"prod": "gunicorn app:app"
|
148
|
+
}
|
149
|
+
}
|
150
|
+
},
|
151
|
+
"deployment_strategies": {
|
152
|
+
"production": {
|
153
|
+
"nextjs": {
|
154
|
+
"method": "pm2",
|
155
|
+
"steps": [
|
156
|
+
"npm install --production",
|
157
|
+
"npm run build",
|
158
|
+
"pm2 start npm --name '{app_name}' -- start"
|
159
|
+
],
|
160
|
+
"health_check": "http://localhost:{port}",
|
161
|
+
"environment": {
|
162
|
+
"NODE_ENV": "production"
|
163
|
+
}
|
164
|
+
},
|
165
|
+
"react": {
|
166
|
+
"method": "static",
|
167
|
+
"steps": [
|
168
|
+
"npm install",
|
169
|
+
"npm run build",
|
170
|
+
"pm2 serve build {port} --name '{app_name}'"
|
171
|
+
],
|
172
|
+
"health_check": "http://localhost:{port}"
|
173
|
+
},
|
174
|
+
"python": {
|
175
|
+
"method": "pm2",
|
176
|
+
"steps": [
|
177
|
+
"pip install -r requirements.txt",
|
178
|
+
"pm2 start gunicorn --name '{app_name}' -- app:app --bind 0.0.0.0:{port}"
|
179
|
+
],
|
180
|
+
"health_check": "http://localhost:{port}/health"
|
181
|
+
}
|
182
|
+
},
|
183
|
+
"development": {
|
184
|
+
"nextjs": {
|
185
|
+
"method": "pm2",
|
186
|
+
"steps": [
|
187
|
+
"npm install",
|
188
|
+
"pm2 start npm --name '{app_name}-dev' -- run dev"
|
189
|
+
],
|
190
|
+
"environment": {
|
191
|
+
"NODE_ENV": "development"
|
192
|
+
}
|
193
|
+
},
|
194
|
+
"react": {
|
195
|
+
"method": "pm2",
|
196
|
+
"steps": [
|
197
|
+
"npm install",
|
198
|
+
"pm2 start npm --name '{app_name}-dev' -- start"
|
199
|
+
]
|
200
|
+
}
|
201
|
+
},
|
202
|
+
"docker": {
|
203
|
+
"default": {
|
204
|
+
"method": "docker",
|
205
|
+
"steps": [
|
206
|
+
"docker build -t {app_name} .",
|
207
|
+
"docker run -d -p {port}:{container_port} --name {app_name} {app_name}"
|
208
|
+
],
|
209
|
+
"health_check": "docker exec {app_name} echo 'OK'"
|
210
|
+
}
|
211
|
+
}
|
212
|
+
},
|
213
|
+
"error_recovery": {
|
214
|
+
"port_conflict": {
|
215
|
+
"detection": "EADDRINUSE",
|
216
|
+
"action": "allocate_next_available_port"
|
217
|
+
},
|
218
|
+
"build_failure": {
|
219
|
+
"detection": "npm ERR!|ERROR|Failed",
|
220
|
+
"action": "report_error_and_suggest_fixes"
|
221
|
+
},
|
222
|
+
"crash_loop": {
|
223
|
+
"detection": "restart_count > 5",
|
224
|
+
"action": "stop_and_investigate_logs"
|
225
|
+
},
|
226
|
+
"out_of_memory": {
|
227
|
+
"detection": "JavaScript heap out of memory",
|
228
|
+
"action": "increase_memory_limit"
|
229
|
+
}
|
230
|
+
},
|
231
|
+
"security": {
|
232
|
+
"port_exposure": "localhost_only",
|
233
|
+
"process_isolation": "user_level",
|
234
|
+
"log_sanitization": true,
|
235
|
+
"secrets_handling": "environment_variables"
|
236
|
+
},
|
237
|
+
"integration": {
|
238
|
+
"hooks": {
|
239
|
+
"pre_deploy": "validate_requirements",
|
240
|
+
"post_deploy": "notify_status",
|
241
|
+
"pre_stop": "graceful_shutdown",
|
242
|
+
"on_crash": "auto_restart_with_backoff"
|
243
|
+
},
|
244
|
+
"monitoring": {
|
245
|
+
"export_metrics": true,
|
246
|
+
"prometheus_endpoint": "/metrics",
|
247
|
+
"health_endpoint": "/health"
|
248
|
+
}
|
249
|
+
},
|
250
|
+
"tools": [
|
251
|
+
"Bash",
|
252
|
+
"Read",
|
253
|
+
"Write",
|
254
|
+
"Edit"
|
255
|
+
],
|
256
|
+
"examples": [
|
257
|
+
{
|
258
|
+
"user": "Deploy my Next.js app",
|
259
|
+
"response": "I'll deploy your Next.js application using PM2 for stability. Let me detect your configuration and set it up...",
|
260
|
+
"actions": ["detect_framework", "build_production", "deploy_with_pm2", "monitor_health"]
|
261
|
+
},
|
262
|
+
{
|
263
|
+
"user": "Show me the status of all deployments",
|
264
|
+
"response": "Here's the status of all active deployments...",
|
265
|
+
"actions": ["list_pm2_processes", "check_docker_containers", "aggregate_status"]
|
266
|
+
},
|
267
|
+
{
|
268
|
+
"user": "The app keeps crashing, help me debug",
|
269
|
+
"response": "Let me check the logs and identify the issue...",
|
270
|
+
"actions": ["fetch_error_logs", "analyze_crash_pattern", "suggest_fixes"]
|
271
|
+
}
|
272
|
+
]
|
273
|
+
}
|
claude_mpm/cli/__init__.py
CHANGED
@@ -234,6 +234,19 @@ def _verify_mcp_gateway_startup():
|
|
234
234
|
DESIGN DECISION: This is non-blocking - failures are logged but don't prevent
|
235
235
|
startup to ensure claude-mpm remains functional even if MCP gateway has issues.
|
236
236
|
"""
|
237
|
+
# Quick verification of MCP services installation
|
238
|
+
try:
|
239
|
+
from ..services.mcp_service_verifier import verify_mcp_services_on_startup
|
240
|
+
from ..core.logger import get_logger
|
241
|
+
|
242
|
+
logger = get_logger("mcp_verify")
|
243
|
+
all_ok, message = verify_mcp_services_on_startup()
|
244
|
+
if not all_ok:
|
245
|
+
logger.warning(message)
|
246
|
+
except Exception:
|
247
|
+
# Non-critical - continue with startup
|
248
|
+
pass
|
249
|
+
|
237
250
|
try:
|
238
251
|
import asyncio
|
239
252
|
import time
|
@@ -449,6 +462,14 @@ def _execute_command(command: str, args) -> int:
|
|
449
462
|
# Convert CommandResult to exit code
|
450
463
|
return result.exit_code if result else 0
|
451
464
|
|
465
|
+
# Handle verify command with lazy import
|
466
|
+
if command == "verify":
|
467
|
+
# Lazy import to avoid loading unless needed
|
468
|
+
from .commands.verify import handle_verify
|
469
|
+
|
470
|
+
result = handle_verify(args)
|
471
|
+
return result if result is not None else 0
|
472
|
+
|
452
473
|
# Map stable commands to their implementations
|
453
474
|
command_map = {
|
454
475
|
CLICommands.RUN.value: run_session,
|
@@ -0,0 +1,118 @@
|
|
1
|
+
"""
|
2
|
+
Verify command for MCP service health checks.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import argparse
|
6
|
+
import sys
|
7
|
+
|
8
|
+
from ...core.logger import get_logger
|
9
|
+
from ...services.mcp_service_verifier import MCPServiceVerifier, ServiceStatus
|
10
|
+
|
11
|
+
|
12
|
+
def add_parser(subparsers) -> None:
|
13
|
+
"""Add the verify command parser."""
|
14
|
+
parser = subparsers.add_parser(
|
15
|
+
"verify",
|
16
|
+
help="Verify MCP services installation and configuration",
|
17
|
+
description="Performs comprehensive health checks on MCP services",
|
18
|
+
)
|
19
|
+
|
20
|
+
parser.add_argument(
|
21
|
+
"--fix",
|
22
|
+
action="store_true",
|
23
|
+
help="Attempt to automatically fix detected issues",
|
24
|
+
)
|
25
|
+
|
26
|
+
parser.add_argument(
|
27
|
+
"--service",
|
28
|
+
type=str,
|
29
|
+
help="Verify a specific service only",
|
30
|
+
choices=["mcp-vector-search", "mcp-browser", "mcp-ticketer", "kuzu-memory"],
|
31
|
+
)
|
32
|
+
|
33
|
+
parser.add_argument(
|
34
|
+
"--json",
|
35
|
+
action="store_true",
|
36
|
+
help="Output results in JSON format",
|
37
|
+
)
|
38
|
+
|
39
|
+
parser.set_defaults(func=handle_verify)
|
40
|
+
|
41
|
+
|
42
|
+
def handle_verify(args: argparse.Namespace) -> int:
|
43
|
+
"""
|
44
|
+
Handle the verify command.
|
45
|
+
|
46
|
+
Args:
|
47
|
+
args: Parsed command-line arguments
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
Exit code (0 for success, non-zero for issues found)
|
51
|
+
"""
|
52
|
+
logger = get_logger(__name__)
|
53
|
+
verifier = MCPServiceVerifier()
|
54
|
+
|
55
|
+
try:
|
56
|
+
# Run verification
|
57
|
+
if args.service:
|
58
|
+
# Verify single service
|
59
|
+
logger.info(f"Verifying {args.service}...")
|
60
|
+
diagnostic = verifier._verify_service(args.service)
|
61
|
+
diagnostics = {args.service: diagnostic}
|
62
|
+
|
63
|
+
# Auto-fix if requested
|
64
|
+
if args.fix and diagnostic.fix_command and diagnostic.status != ServiceStatus.WORKING:
|
65
|
+
logger.info(f"Attempting to fix {args.service}...")
|
66
|
+
if verifier._attempt_auto_fix(args.service, diagnostic):
|
67
|
+
# Re-verify after fix
|
68
|
+
diagnostic = verifier._verify_service(args.service)
|
69
|
+
diagnostics = {args.service: diagnostic}
|
70
|
+
else:
|
71
|
+
# Verify all services
|
72
|
+
logger.info("Verifying all MCP services...")
|
73
|
+
diagnostics = verifier.verify_all_services(auto_fix=args.fix)
|
74
|
+
|
75
|
+
# Output results
|
76
|
+
if args.json:
|
77
|
+
import json
|
78
|
+
|
79
|
+
# Convert to JSON-serializable format
|
80
|
+
json_output = {}
|
81
|
+
for name, diag in diagnostics.items():
|
82
|
+
json_output[name] = {
|
83
|
+
"status": diag.status.value,
|
84
|
+
"message": diag.message,
|
85
|
+
"installed_path": diag.installed_path,
|
86
|
+
"configured_command": diag.configured_command,
|
87
|
+
"fix_command": diag.fix_command,
|
88
|
+
"details": diag.details,
|
89
|
+
}
|
90
|
+
print(json.dumps(json_output, indent=2))
|
91
|
+
else:
|
92
|
+
verifier.print_diagnostics(diagnostics)
|
93
|
+
|
94
|
+
# Determine exit code
|
95
|
+
all_working = all(
|
96
|
+
d.status == ServiceStatus.WORKING
|
97
|
+
for d in diagnostics.values()
|
98
|
+
)
|
99
|
+
|
100
|
+
if all_working:
|
101
|
+
logger.info("✅ All verified services are fully operational")
|
102
|
+
return 0
|
103
|
+
else:
|
104
|
+
issues_count = sum(
|
105
|
+
1 for d in diagnostics.values()
|
106
|
+
if d.status != ServiceStatus.WORKING
|
107
|
+
)
|
108
|
+
logger.warning(f"⚠️ {issues_count} service(s) have issues")
|
109
|
+
return 1
|
110
|
+
|
111
|
+
except Exception as e:
|
112
|
+
logger.error(f"Verification failed: {e}")
|
113
|
+
if args.json:
|
114
|
+
import json
|
115
|
+
print(json.dumps({"error": str(e)}, indent=2))
|
116
|
+
else:
|
117
|
+
print(f"\n❌ Verification failed: {e}")
|
118
|
+
return 2
|
@@ -422,6 +422,11 @@ def create_parser(
|
|
422
422
|
from ..commands.doctor import add_doctor_parser
|
423
423
|
|
424
424
|
add_doctor_parser(subparsers)
|
425
|
+
|
426
|
+
# Add verify command for MCP service verification
|
427
|
+
from ..commands.verify import add_parser as add_verify_parser
|
428
|
+
|
429
|
+
add_verify_parser(subparsers)
|
425
430
|
except ImportError:
|
426
431
|
# Commands module may not be available during testing or refactoring
|
427
432
|
pass
|
@@ -369,12 +369,18 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
|
|
369
369
|
else "single"
|
370
370
|
)
|
371
371
|
|
372
|
+
# When using multi-source deployment, we've already determined which
|
373
|
+
# agents need updates. Don't re-check versions in single_agent_deployer.
|
374
|
+
# This prevents the issue where multi-source says "deploying 9 agents"
|
375
|
+
# but then all get skipped due to redundant version checks.
|
376
|
+
skip_version_check = use_multi_source and not force_rebuild
|
377
|
+
|
372
378
|
self.single_agent_deployer.deploy_single_agent(
|
373
379
|
template_file=template_file_path,
|
374
380
|
agents_dir=agents_dir,
|
375
381
|
base_agent_data=base_agent_data,
|
376
382
|
base_agent_version=base_agent_version,
|
377
|
-
force_rebuild=force_rebuild,
|
383
|
+
force_rebuild=force_rebuild or skip_version_check,
|
378
384
|
deployment_mode=deployment_mode,
|
379
385
|
results=results,
|
380
386
|
source_info=source_info,
|
@@ -817,11 +823,9 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
|
|
817
823
|
|
818
824
|
agents_to_deploy = filtered_agents
|
819
825
|
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
)
|
824
|
-
else:
|
826
|
+
# Don't log this redundant message - we already logged the upgrades above
|
827
|
+
# The "Deploying X agent upgrade(s)" message is sufficient
|
828
|
+
if not agents_to_deploy:
|
825
829
|
self.logger.debug(
|
826
830
|
f"All {len(comparison_results.get('up_to_date', []))} agents are up to date"
|
827
831
|
)
|
@@ -0,0 +1,690 @@
|
|
1
|
+
"""
|
2
|
+
MCP Service Verifier
|
3
|
+
====================
|
4
|
+
|
5
|
+
Comprehensive verification system for MCP services that checks installation,
|
6
|
+
configuration, and runtime functionality. Provides detailed diagnostics and
|
7
|
+
automated fixes for common issues.
|
8
|
+
"""
|
9
|
+
|
10
|
+
import json
|
11
|
+
import os
|
12
|
+
import shutil
|
13
|
+
import subprocess
|
14
|
+
import sys
|
15
|
+
import time
|
16
|
+
from dataclasses import dataclass
|
17
|
+
from enum import Enum
|
18
|
+
from pathlib import Path
|
19
|
+
from typing import Dict, List, Optional, Tuple
|
20
|
+
|
21
|
+
from ..core.logger import get_logger
|
22
|
+
|
23
|
+
|
24
|
+
class ServiceStatus(Enum):
|
25
|
+
"""MCP service health status levels."""
|
26
|
+
|
27
|
+
WORKING = "✅" # Fully operational
|
28
|
+
MISCONFIGURED = "⚠️" # Installed but configuration issues
|
29
|
+
NOT_INSTALLED = "❌" # Not installed at all
|
30
|
+
PERMISSION_DENIED = "🔒" # Permissions issue
|
31
|
+
VERSION_MISMATCH = "🔄" # Needs upgrade
|
32
|
+
UNKNOWN = "❓" # Unknown status
|
33
|
+
|
34
|
+
|
35
|
+
@dataclass
|
36
|
+
class ServiceDiagnostic:
|
37
|
+
"""Detailed diagnostic information for a service."""
|
38
|
+
|
39
|
+
name: str
|
40
|
+
status: ServiceStatus
|
41
|
+
message: str
|
42
|
+
installed_path: Optional[str] = None
|
43
|
+
configured_command: Optional[str] = None
|
44
|
+
fix_command: Optional[str] = None
|
45
|
+
details: Optional[Dict] = None
|
46
|
+
|
47
|
+
|
48
|
+
class MCPServiceVerifier:
|
49
|
+
"""
|
50
|
+
Comprehensive MCP service verification and auto-fix system.
|
51
|
+
|
52
|
+
This verifier performs deep health checks on MCP services including:
|
53
|
+
- Installation verification (pipx, uvx, system)
|
54
|
+
- Configuration validation in ~/.claude.json
|
55
|
+
- Permission checks on executables
|
56
|
+
- Command format verification
|
57
|
+
- Runtime functionality testing
|
58
|
+
- Auto-fix capabilities for common issues
|
59
|
+
"""
|
60
|
+
|
61
|
+
# Known MCP services and their requirements
|
62
|
+
SERVICE_REQUIREMENTS = {
|
63
|
+
"mcp-vector-search": {
|
64
|
+
"pipx_package": "mcp-vector-search",
|
65
|
+
"test_args": ["--version"],
|
66
|
+
"required_args": ["-m", "mcp_vector_search.mcp.server"],
|
67
|
+
"needs_project_path": True,
|
68
|
+
},
|
69
|
+
"mcp-browser": {
|
70
|
+
"pipx_package": "mcp-browser",
|
71
|
+
"test_args": ["--version"],
|
72
|
+
"required_args": ["mcp"],
|
73
|
+
"env_vars": {"MCP_BROWSER_HOME": "~/.mcp-browser"},
|
74
|
+
},
|
75
|
+
"mcp-ticketer": {
|
76
|
+
"pipx_package": "mcp-ticketer",
|
77
|
+
"test_args": ["--version"],
|
78
|
+
"required_args": ["mcp"],
|
79
|
+
},
|
80
|
+
"kuzu-memory": {
|
81
|
+
"pipx_package": "kuzu-memory",
|
82
|
+
"test_args": ["--help"], # kuzu-memory uses --help not --version
|
83
|
+
"required_args": ["mcp", "serve"], # Modern format
|
84
|
+
"min_version": "1.1.0", # Minimum version for MCP support
|
85
|
+
"version_check_pattern": ["mcp", "serve", "claude"], # Pattern to check in help
|
86
|
+
},
|
87
|
+
}
|
88
|
+
|
89
|
+
def __init__(self):
|
90
|
+
"""Initialize the MCP service verifier."""
|
91
|
+
self.logger = get_logger(__name__)
|
92
|
+
self.project_root = Path.cwd()
|
93
|
+
self.claude_config_path = Path.home() / ".claude.json"
|
94
|
+
self.diagnostics: Dict[str, ServiceDiagnostic] = {}
|
95
|
+
|
96
|
+
def verify_all_services(self, auto_fix: bool = False) -> Dict[str, ServiceDiagnostic]:
|
97
|
+
"""
|
98
|
+
Perform comprehensive verification of all MCP services.
|
99
|
+
|
100
|
+
Args:
|
101
|
+
auto_fix: Whether to attempt automatic fixes for issues
|
102
|
+
|
103
|
+
Returns:
|
104
|
+
Dictionary mapping service names to diagnostic results
|
105
|
+
"""
|
106
|
+
self.logger.info("Starting MCP service verification...")
|
107
|
+
|
108
|
+
for service_name in self.SERVICE_REQUIREMENTS:
|
109
|
+
diagnostic = self._verify_service(service_name)
|
110
|
+
self.diagnostics[service_name] = diagnostic
|
111
|
+
|
112
|
+
# Attempt auto-fix if requested and fixable
|
113
|
+
if auto_fix and diagnostic.fix_command and diagnostic.status != ServiceStatus.WORKING:
|
114
|
+
self._attempt_auto_fix(service_name, diagnostic)
|
115
|
+
# Re-verify after fix
|
116
|
+
self.diagnostics[service_name] = self._verify_service(service_name)
|
117
|
+
|
118
|
+
return self.diagnostics
|
119
|
+
|
120
|
+
def _verify_service(self, service_name: str) -> ServiceDiagnostic:
|
121
|
+
"""
|
122
|
+
Perform deep verification of a single MCP service.
|
123
|
+
|
124
|
+
Args:
|
125
|
+
service_name: Name of the service to verify
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
Diagnostic result for the service
|
129
|
+
"""
|
130
|
+
requirements = self.SERVICE_REQUIREMENTS[service_name]
|
131
|
+
|
132
|
+
# Step 1: Check if service is installed
|
133
|
+
installed_path = self._find_service_installation(service_name)
|
134
|
+
|
135
|
+
if not installed_path:
|
136
|
+
return ServiceDiagnostic(
|
137
|
+
name=service_name,
|
138
|
+
status=ServiceStatus.NOT_INSTALLED,
|
139
|
+
message=f"{service_name} is not installed",
|
140
|
+
fix_command=f"pipx install {requirements['pipx_package']}"
|
141
|
+
)
|
142
|
+
|
143
|
+
# Step 2: Check executable permissions
|
144
|
+
if not self._check_permissions(installed_path):
|
145
|
+
return ServiceDiagnostic(
|
146
|
+
name=service_name,
|
147
|
+
status=ServiceStatus.PERMISSION_DENIED,
|
148
|
+
message=f"Permission denied for {service_name}",
|
149
|
+
installed_path=installed_path,
|
150
|
+
fix_command=f"chmod +x {installed_path}"
|
151
|
+
)
|
152
|
+
|
153
|
+
# Step 3: Test basic functionality
|
154
|
+
if not self._test_service_functionality(service_name, installed_path):
|
155
|
+
# Check if it's a version issue for kuzu-memory
|
156
|
+
if service_name == "kuzu-memory":
|
157
|
+
version_info = self._check_kuzu_version(installed_path)
|
158
|
+
if not version_info["has_mcp_support"]:
|
159
|
+
return ServiceDiagnostic(
|
160
|
+
name=service_name,
|
161
|
+
status=ServiceStatus.VERSION_MISMATCH,
|
162
|
+
message=f"kuzu-memory needs upgrade to v1.1.0+ for MCP support",
|
163
|
+
installed_path=installed_path,
|
164
|
+
fix_command="pipx upgrade kuzu-memory",
|
165
|
+
details=version_info
|
166
|
+
)
|
167
|
+
|
168
|
+
return ServiceDiagnostic(
|
169
|
+
name=service_name,
|
170
|
+
status=ServiceStatus.MISCONFIGURED,
|
171
|
+
message=f"{service_name} installed but not functioning",
|
172
|
+
installed_path=installed_path,
|
173
|
+
fix_command=f"pipx reinstall {requirements['pipx_package']}"
|
174
|
+
)
|
175
|
+
|
176
|
+
# Step 4: Verify configuration in ~/.claude.json
|
177
|
+
config_status = self._verify_configuration(service_name, installed_path)
|
178
|
+
|
179
|
+
if not config_status["configured"]:
|
180
|
+
return ServiceDiagnostic(
|
181
|
+
name=service_name,
|
182
|
+
status=ServiceStatus.MISCONFIGURED,
|
183
|
+
message=f"{service_name} not configured in ~/.claude.json",
|
184
|
+
installed_path=installed_path,
|
185
|
+
configured_command=None,
|
186
|
+
fix_command="Run 'claude-mpm configure' to update configuration"
|
187
|
+
)
|
188
|
+
|
189
|
+
if not config_status["correct"]:
|
190
|
+
return ServiceDiagnostic(
|
191
|
+
name=service_name,
|
192
|
+
status=ServiceStatus.MISCONFIGURED,
|
193
|
+
message=f"{service_name} configuration needs update",
|
194
|
+
installed_path=installed_path,
|
195
|
+
configured_command=config_status.get("command"),
|
196
|
+
fix_command="Run 'claude-mpm configure' to fix configuration",
|
197
|
+
details={"config_issue": config_status.get("issue")}
|
198
|
+
)
|
199
|
+
|
200
|
+
# Step 5: Test actual MCP command execution
|
201
|
+
if not self._test_mcp_command(service_name, config_status.get("command"), config_status.get("args", [])):
|
202
|
+
return ServiceDiagnostic(
|
203
|
+
name=service_name,
|
204
|
+
status=ServiceStatus.MISCONFIGURED,
|
205
|
+
message=f"{service_name} command format issue",
|
206
|
+
installed_path=installed_path,
|
207
|
+
configured_command=config_status.get("command"),
|
208
|
+
fix_command="Run 'claude-mpm configure' to update command format",
|
209
|
+
details={"command": config_status.get("command"), "args": config_status.get("args")}
|
210
|
+
)
|
211
|
+
|
212
|
+
# All checks passed!
|
213
|
+
return ServiceDiagnostic(
|
214
|
+
name=service_name,
|
215
|
+
status=ServiceStatus.WORKING,
|
216
|
+
message=f"{service_name} is fully operational",
|
217
|
+
installed_path=installed_path,
|
218
|
+
configured_command=config_status.get("command")
|
219
|
+
)
|
220
|
+
|
221
|
+
def _find_service_installation(self, service_name: str) -> Optional[str]:
|
222
|
+
"""
|
223
|
+
Find where a service is installed.
|
224
|
+
|
225
|
+
Checks in order:
|
226
|
+
1. pipx installation
|
227
|
+
2. uvx installation
|
228
|
+
3. System PATH
|
229
|
+
4. User pip installation
|
230
|
+
|
231
|
+
Args:
|
232
|
+
service_name: Name of the service
|
233
|
+
|
234
|
+
Returns:
|
235
|
+
Path to the service executable or None
|
236
|
+
"""
|
237
|
+
# Check pipx
|
238
|
+
pipx_path = Path.home() / ".local" / "pipx" / "venvs" / service_name / "bin" / service_name
|
239
|
+
if pipx_path.exists():
|
240
|
+
return str(pipx_path)
|
241
|
+
|
242
|
+
# Special case for mcp-vector-search (uses Python interpreter)
|
243
|
+
if service_name == "mcp-vector-search":
|
244
|
+
pipx_python = pipx_path.parent / "python"
|
245
|
+
if pipx_python.exists():
|
246
|
+
return str(pipx_python)
|
247
|
+
|
248
|
+
# Check system PATH
|
249
|
+
system_path = shutil.which(service_name)
|
250
|
+
if system_path:
|
251
|
+
return system_path
|
252
|
+
|
253
|
+
# Check user pip installation
|
254
|
+
user_bin = Path.home() / ".local" / "bin" / service_name
|
255
|
+
if user_bin.exists():
|
256
|
+
return str(user_bin)
|
257
|
+
|
258
|
+
return None
|
259
|
+
|
260
|
+
def _check_permissions(self, path: str) -> bool:
|
261
|
+
"""
|
262
|
+
Check if a file has execute permissions.
|
263
|
+
|
264
|
+
Args:
|
265
|
+
path: Path to the executable
|
266
|
+
|
267
|
+
Returns:
|
268
|
+
True if executable, False otherwise
|
269
|
+
"""
|
270
|
+
try:
|
271
|
+
return os.access(path, os.X_OK)
|
272
|
+
except Exception as e:
|
273
|
+
self.logger.debug(f"Permission check failed for {path}: {e}")
|
274
|
+
return False
|
275
|
+
|
276
|
+
def _test_service_functionality(self, service_name: str, path: str) -> bool:
|
277
|
+
"""
|
278
|
+
Test if a service can execute basic commands.
|
279
|
+
|
280
|
+
Args:
|
281
|
+
service_name: Name of the service
|
282
|
+
path: Path to the executable
|
283
|
+
|
284
|
+
Returns:
|
285
|
+
True if service is functional, False otherwise
|
286
|
+
"""
|
287
|
+
requirements = self.SERVICE_REQUIREMENTS[service_name]
|
288
|
+
test_args = requirements.get("test_args", ["--help"])
|
289
|
+
|
290
|
+
try:
|
291
|
+
# First try direct execution
|
292
|
+
result = subprocess.run(
|
293
|
+
[path] + test_args,
|
294
|
+
capture_output=True,
|
295
|
+
text=True,
|
296
|
+
timeout=10,
|
297
|
+
check=False
|
298
|
+
)
|
299
|
+
|
300
|
+
output = (result.stdout + result.stderr).lower()
|
301
|
+
|
302
|
+
# Check for success indicators
|
303
|
+
if result.returncode == 0:
|
304
|
+
return True
|
305
|
+
|
306
|
+
# Some tools return non-zero but still work
|
307
|
+
if any(word in output for word in ["version", "usage", "help", service_name.lower()]):
|
308
|
+
# Make sure it's not an error
|
309
|
+
if not any(error in output for error in ["error", "not found", "traceback", "no module"]):
|
310
|
+
return True
|
311
|
+
|
312
|
+
# Try pipx run as fallback
|
313
|
+
if shutil.which("pipx"):
|
314
|
+
result = subprocess.run(
|
315
|
+
["pipx", "run", service_name] + test_args,
|
316
|
+
capture_output=True,
|
317
|
+
text=True,
|
318
|
+
timeout=10,
|
319
|
+
check=False
|
320
|
+
)
|
321
|
+
if result.returncode == 0 or "version" in result.stdout.lower():
|
322
|
+
return True
|
323
|
+
|
324
|
+
except subprocess.TimeoutExpired:
|
325
|
+
self.logger.warning(f"Service {service_name} timed out during functionality test")
|
326
|
+
except Exception as e:
|
327
|
+
self.logger.debug(f"Functionality test failed for {service_name}: {e}")
|
328
|
+
|
329
|
+
return False
|
330
|
+
|
331
|
+
def _check_kuzu_version(self, path: str) -> Dict:
|
332
|
+
"""
|
333
|
+
Check kuzu-memory version and MCP support.
|
334
|
+
|
335
|
+
Args:
|
336
|
+
path: Path to kuzu-memory executable
|
337
|
+
|
338
|
+
Returns:
|
339
|
+
Dictionary with version information
|
340
|
+
"""
|
341
|
+
version_info = {
|
342
|
+
"has_mcp_support": False,
|
343
|
+
"version": "unknown",
|
344
|
+
"command_format": None
|
345
|
+
}
|
346
|
+
|
347
|
+
try:
|
348
|
+
# Check help output for MCP support
|
349
|
+
result = subprocess.run(
|
350
|
+
[path, "--help"],
|
351
|
+
capture_output=True,
|
352
|
+
text=True,
|
353
|
+
timeout=10,
|
354
|
+
check=False
|
355
|
+
)
|
356
|
+
|
357
|
+
help_text = (result.stdout + result.stderr).lower()
|
358
|
+
|
359
|
+
# Check for modern "mcp serve" command
|
360
|
+
if "mcp serve" in help_text or ("mcp" in help_text and "serve" in help_text):
|
361
|
+
version_info["has_mcp_support"] = True
|
362
|
+
version_info["command_format"] = "mcp serve"
|
363
|
+
# Check for legacy "serve" only
|
364
|
+
elif "serve" in help_text and "mcp" not in help_text:
|
365
|
+
version_info["has_mcp_support"] = False
|
366
|
+
version_info["command_format"] = "serve"
|
367
|
+
|
368
|
+
# Try to extract version
|
369
|
+
version_result = subprocess.run(
|
370
|
+
[path, "--version"],
|
371
|
+
capture_output=True,
|
372
|
+
text=True,
|
373
|
+
timeout=5,
|
374
|
+
check=False
|
375
|
+
)
|
376
|
+
if version_result.returncode == 0:
|
377
|
+
version_info["version"] = version_result.stdout.strip()
|
378
|
+
|
379
|
+
except Exception as e:
|
380
|
+
self.logger.debug(f"Failed to check kuzu-memory version: {e}")
|
381
|
+
|
382
|
+
return version_info
|
383
|
+
|
384
|
+
def _verify_configuration(self, service_name: str, installed_path: str) -> Dict:
|
385
|
+
"""
|
386
|
+
Verify service configuration in ~/.claude.json.
|
387
|
+
|
388
|
+
Args:
|
389
|
+
service_name: Name of the service
|
390
|
+
installed_path: Path where service is installed
|
391
|
+
|
392
|
+
Returns:
|
393
|
+
Dictionary with configuration status
|
394
|
+
"""
|
395
|
+
project_key = str(self.project_root)
|
396
|
+
|
397
|
+
if not self.claude_config_path.exists():
|
398
|
+
return {"configured": False, "correct": False}
|
399
|
+
|
400
|
+
try:
|
401
|
+
with open(self.claude_config_path) as f:
|
402
|
+
config = json.load(f)
|
403
|
+
|
404
|
+
# Check if project is configured
|
405
|
+
if "projects" not in config or project_key not in config["projects"]:
|
406
|
+
return {"configured": False, "correct": False}
|
407
|
+
|
408
|
+
project_config = config["projects"][project_key]
|
409
|
+
mcp_servers = project_config.get("mcpServers", {})
|
410
|
+
|
411
|
+
# Check if service is configured
|
412
|
+
if service_name not in mcp_servers:
|
413
|
+
return {"configured": False, "correct": False}
|
414
|
+
|
415
|
+
service_config = mcp_servers[service_name]
|
416
|
+
command = service_config.get("command", "")
|
417
|
+
args = service_config.get("args", [])
|
418
|
+
|
419
|
+
# Validate command configuration
|
420
|
+
requirements = self.SERVICE_REQUIREMENTS[service_name]
|
421
|
+
required_args = requirements.get("required_args", [])
|
422
|
+
|
423
|
+
# Check if using pipx run or direct execution
|
424
|
+
if command == "pipx" and args and args[0] == "run":
|
425
|
+
# pipx run format
|
426
|
+
if service_name not in args:
|
427
|
+
return {
|
428
|
+
"configured": True,
|
429
|
+
"correct": False,
|
430
|
+
"command": command,
|
431
|
+
"args": args,
|
432
|
+
"issue": "Service name missing in pipx run command"
|
433
|
+
}
|
434
|
+
# Check required args are present
|
435
|
+
for req_arg in required_args:
|
436
|
+
if req_arg not in args[2:]: # Skip "run" and service name
|
437
|
+
return {
|
438
|
+
"configured": True,
|
439
|
+
"correct": False,
|
440
|
+
"command": command,
|
441
|
+
"args": args,
|
442
|
+
"issue": f"Missing required argument: {req_arg}"
|
443
|
+
}
|
444
|
+
elif command == "uvx" and args and args[0] == service_name:
|
445
|
+
# uvx format - similar validation
|
446
|
+
for req_arg in required_args:
|
447
|
+
if req_arg not in args[1:]:
|
448
|
+
return {
|
449
|
+
"configured": True,
|
450
|
+
"correct": False,
|
451
|
+
"command": command,
|
452
|
+
"args": args,
|
453
|
+
"issue": f"Missing required argument: {req_arg}"
|
454
|
+
}
|
455
|
+
else:
|
456
|
+
# Direct execution - command should be a valid path
|
457
|
+
if not Path(command).exists() and command != installed_path:
|
458
|
+
# Allow for relative paths that might resolve differently
|
459
|
+
if not shutil.which(command):
|
460
|
+
return {
|
461
|
+
"configured": True,
|
462
|
+
"correct": False,
|
463
|
+
"command": command,
|
464
|
+
"args": args,
|
465
|
+
"issue": f"Command path does not exist: {command}"
|
466
|
+
}
|
467
|
+
|
468
|
+
# Check required args
|
469
|
+
for req_arg in required_args:
|
470
|
+
if req_arg not in args:
|
471
|
+
return {
|
472
|
+
"configured": True,
|
473
|
+
"correct": False,
|
474
|
+
"command": command,
|
475
|
+
"args": args,
|
476
|
+
"issue": f"Missing required argument: {req_arg}"
|
477
|
+
}
|
478
|
+
|
479
|
+
# Special validation for kuzu-memory command format
|
480
|
+
if service_name == "kuzu-memory":
|
481
|
+
# Should use "mcp serve" format for modern versions
|
482
|
+
if args and "serve" in args and "mcp" not in args:
|
483
|
+
return {
|
484
|
+
"configured": True,
|
485
|
+
"correct": False,
|
486
|
+
"command": command,
|
487
|
+
"args": args,
|
488
|
+
"issue": "Using legacy 'serve' format, should use 'mcp serve'"
|
489
|
+
}
|
490
|
+
|
491
|
+
return {
|
492
|
+
"configured": True,
|
493
|
+
"correct": True,
|
494
|
+
"command": command,
|
495
|
+
"args": args
|
496
|
+
}
|
497
|
+
|
498
|
+
except Exception as e:
|
499
|
+
self.logger.error(f"Failed to verify configuration: {e}")
|
500
|
+
return {"configured": False, "correct": False, "error": str(e)}
|
501
|
+
|
502
|
+
def _test_mcp_command(self, service_name: str, command: str, args: List[str]) -> bool:
|
503
|
+
"""
|
504
|
+
Test if the configured MCP command actually works.
|
505
|
+
|
506
|
+
Args:
|
507
|
+
service_name: Name of the service
|
508
|
+
command: Configured command
|
509
|
+
args: Configured arguments
|
510
|
+
|
511
|
+
Returns:
|
512
|
+
True if command executes successfully
|
513
|
+
"""
|
514
|
+
if not command:
|
515
|
+
return False
|
516
|
+
|
517
|
+
try:
|
518
|
+
# Build test command - add --help to test without side effects
|
519
|
+
test_cmd = [command] + args[:2] if args else [command] # Include base args
|
520
|
+
test_cmd.append("--help")
|
521
|
+
|
522
|
+
result = subprocess.run(
|
523
|
+
test_cmd,
|
524
|
+
capture_output=True,
|
525
|
+
text=True,
|
526
|
+
timeout=10,
|
527
|
+
check=False,
|
528
|
+
cwd=str(self.project_root) # Run in project context
|
529
|
+
)
|
530
|
+
|
531
|
+
# Check for success or expected output
|
532
|
+
output = (result.stdout + result.stderr).lower()
|
533
|
+
if result.returncode == 0:
|
534
|
+
return True
|
535
|
+
|
536
|
+
# Check for expected patterns
|
537
|
+
if service_name == "kuzu-memory" and "mcp" in output and "serve" in output:
|
538
|
+
return True
|
539
|
+
if service_name in output or "usage" in output or "help" in output:
|
540
|
+
if not any(error in output for error in ["error", "not found", "traceback"]):
|
541
|
+
return True
|
542
|
+
|
543
|
+
except subprocess.TimeoutExpired:
|
544
|
+
self.logger.warning(f"Command test timed out for {service_name}")
|
545
|
+
except Exception as e:
|
546
|
+
self.logger.debug(f"Command test failed for {service_name}: {e}")
|
547
|
+
|
548
|
+
return False
|
549
|
+
|
550
|
+
def _attempt_auto_fix(self, service_name: str, diagnostic: ServiceDiagnostic) -> bool:
|
551
|
+
"""
|
552
|
+
Attempt to automatically fix a service issue.
|
553
|
+
|
554
|
+
Args:
|
555
|
+
service_name: Name of the service
|
556
|
+
diagnostic: Current diagnostic information
|
557
|
+
|
558
|
+
Returns:
|
559
|
+
True if fix was successful
|
560
|
+
"""
|
561
|
+
if not diagnostic.fix_command:
|
562
|
+
return False
|
563
|
+
|
564
|
+
self.logger.info(f"Attempting auto-fix for {service_name}: {diagnostic.fix_command}")
|
565
|
+
|
566
|
+
try:
|
567
|
+
# Handle different types of fix commands
|
568
|
+
if diagnostic.fix_command.startswith("pipx "):
|
569
|
+
# Execute pipx command
|
570
|
+
cmd_parts = diagnostic.fix_command.split()
|
571
|
+
result = subprocess.run(
|
572
|
+
cmd_parts,
|
573
|
+
capture_output=True,
|
574
|
+
text=True,
|
575
|
+
timeout=120,
|
576
|
+
check=False
|
577
|
+
)
|
578
|
+
return result.returncode == 0
|
579
|
+
|
580
|
+
elif diagnostic.fix_command.startswith("chmod "):
|
581
|
+
# Fix permissions
|
582
|
+
path = diagnostic.fix_command.replace("chmod +x ", "")
|
583
|
+
os.chmod(path, 0o755)
|
584
|
+
return True
|
585
|
+
|
586
|
+
elif "claude-mpm configure" in diagnostic.fix_command:
|
587
|
+
# Trigger configuration update
|
588
|
+
from .mcp_config_manager import MCPConfigManager
|
589
|
+
manager = MCPConfigManager()
|
590
|
+
success, _ = manager.ensure_mcp_services_configured()
|
591
|
+
return success
|
592
|
+
|
593
|
+
except Exception as e:
|
594
|
+
self.logger.error(f"Auto-fix failed for {service_name}: {e}")
|
595
|
+
|
596
|
+
return False
|
597
|
+
|
598
|
+
def print_diagnostics(self, diagnostics: Optional[Dict[str, ServiceDiagnostic]] = None) -> None:
|
599
|
+
"""
|
600
|
+
Print formatted diagnostic results to console.
|
601
|
+
|
602
|
+
Args:
|
603
|
+
diagnostics: Diagnostic results to print (uses self.diagnostics if None)
|
604
|
+
"""
|
605
|
+
if diagnostics is None:
|
606
|
+
diagnostics = self.diagnostics
|
607
|
+
|
608
|
+
if not diagnostics:
|
609
|
+
print("\n📋 No services verified yet")
|
610
|
+
return
|
611
|
+
|
612
|
+
print("\n" + "=" * 60)
|
613
|
+
print("📋 MCP Service Verification Report")
|
614
|
+
print("=" * 60)
|
615
|
+
|
616
|
+
# Group by status
|
617
|
+
working = []
|
618
|
+
issues = []
|
619
|
+
|
620
|
+
for name, diag in diagnostics.items():
|
621
|
+
if diag.status == ServiceStatus.WORKING:
|
622
|
+
working.append(diag)
|
623
|
+
else:
|
624
|
+
issues.append(diag)
|
625
|
+
|
626
|
+
# Print working services
|
627
|
+
if working:
|
628
|
+
print("\n✅ Fully Operational Services:")
|
629
|
+
for diag in working:
|
630
|
+
print(f" • {diag.name}: {diag.message}")
|
631
|
+
if diag.configured_command:
|
632
|
+
print(f" Command: {diag.configured_command}")
|
633
|
+
|
634
|
+
# Print services with issues
|
635
|
+
if issues:
|
636
|
+
print("\n⚠️ Services Requiring Attention:")
|
637
|
+
for diag in issues:
|
638
|
+
print(f"\n {diag.status.value} {diag.name}:")
|
639
|
+
print(f" Issue: {diag.message}")
|
640
|
+
if diag.installed_path:
|
641
|
+
print(f" Path: {diag.installed_path}")
|
642
|
+
if diag.fix_command:
|
643
|
+
print(f" Fix: {diag.fix_command}")
|
644
|
+
if diag.details:
|
645
|
+
print(f" Details: {json.dumps(diag.details, indent=6)}")
|
646
|
+
|
647
|
+
# Summary
|
648
|
+
print("\n" + "=" * 60)
|
649
|
+
print(f"Summary: {len(working)}/{len(diagnostics)} services operational")
|
650
|
+
|
651
|
+
if issues:
|
652
|
+
print("\n💡 Quick Fix Commands:")
|
653
|
+
seen_fixes = set()
|
654
|
+
for diag in issues:
|
655
|
+
if diag.fix_command and diag.fix_command not in seen_fixes:
|
656
|
+
print(f" {diag.fix_command}")
|
657
|
+
seen_fixes.add(diag.fix_command)
|
658
|
+
|
659
|
+
print("\nOr run: claude-mpm verify --fix")
|
660
|
+
|
661
|
+
print("=" * 60 + "\n")
|
662
|
+
|
663
|
+
|
664
|
+
def verify_mcp_services_on_startup() -> Tuple[bool, str]:
|
665
|
+
"""
|
666
|
+
Quick verification check for MCP services during startup.
|
667
|
+
|
668
|
+
This is a lightweight check that runs during CLI initialization
|
669
|
+
to warn users of potential issues without blocking startup.
|
670
|
+
|
671
|
+
Returns:
|
672
|
+
Tuple of (all_working, summary_message)
|
673
|
+
"""
|
674
|
+
verifier = MCPServiceVerifier()
|
675
|
+
logger = get_logger(__name__)
|
676
|
+
|
677
|
+
# Do quick checks only (don't block startup)
|
678
|
+
issues = []
|
679
|
+
for service_name in MCPServiceVerifier.SERVICE_REQUIREMENTS:
|
680
|
+
path = verifier._find_service_installation(service_name)
|
681
|
+
if not path:
|
682
|
+
issues.append(f"{service_name} not installed")
|
683
|
+
elif not verifier._check_permissions(path):
|
684
|
+
issues.append(f"{service_name} permission issue")
|
685
|
+
|
686
|
+
if issues:
|
687
|
+
message = f"MCP service issues detected: {', '.join(issues)}. Run 'claude-mpm verify' for details."
|
688
|
+
return False, message
|
689
|
+
|
690
|
+
return True, "All MCP services appear operational"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: claude-mpm
|
3
|
-
Version: 4.4.
|
3
|
+
Version: 4.4.7
|
4
4
|
Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
|
5
5
|
Author-email: Bob Matsuoka <bob@matsuoka.com>
|
6
6
|
Maintainer: Claude MPM Team
|
@@ -230,6 +230,18 @@ claude-mpm doctor --checks installation configuration agents mcp
|
|
230
230
|
# Check MCP service status specifically
|
231
231
|
claude-mpm doctor --checks mcp --verbose
|
232
232
|
|
233
|
+
# Verify MCP services installation and configuration
|
234
|
+
claude-mpm verify
|
235
|
+
|
236
|
+
# Auto-fix MCP service issues
|
237
|
+
claude-mpm verify --fix
|
238
|
+
|
239
|
+
# Verify specific service
|
240
|
+
claude-mpm verify --service kuzu-memory
|
241
|
+
|
242
|
+
# Get JSON output for automation
|
243
|
+
claude-mpm verify --json
|
244
|
+
|
233
245
|
# Manage memory for large conversation histories
|
234
246
|
claude-mpm cleanup-memory
|
235
247
|
```
|
@@ -1,5 +1,5 @@
|
|
1
1
|
claude_mpm/BUILD_NUMBER,sha256=toytnNjkIKPgQaGwDqQdC1rpNTAdSEc6Vja50d7Ovug,4
|
2
|
-
claude_mpm/VERSION,sha256=
|
2
|
+
claude_mpm/VERSION,sha256=R8T1NAm61Ztbc_VglNJtvUE8VEkNwCNRHjh4rXfFP_M,6
|
3
3
|
claude_mpm/__init__.py,sha256=lyTZAYGH4DTaFGLRNWJKk5Q5oTjzN5I6AXmfVX-Jff0,1512
|
4
4
|
claude_mpm/__main__.py,sha256=Ro5UBWBoQaSAIoSqWAr7zkbLyvi4sSy28WShqAhKJG0,723
|
5
5
|
claude_mpm/constants.py,sha256=cChN3myrAcF3jC-6DvHnBFTEnwlDk-TAsIXPvUZr_yw,5953
|
@@ -38,6 +38,7 @@ claude_mpm/agents/templates/documentation.json,sha256=Hp9GADw0Wttmxe2pxTCg40DT1A
|
|
38
38
|
claude_mpm/agents/templates/engineer.json,sha256=tAWKqt35OyL0RUQgTSeX9nVtcjF94ND-WbflXStAXHA,8222
|
39
39
|
claude_mpm/agents/templates/gcp_ops_agent.json,sha256=v2exIGCzPLBDu5M4CCxZ1k3EQfrV0jc6T9C2ecPjScY,10812
|
40
40
|
claude_mpm/agents/templates/imagemagick.json,sha256=6Zy4W_q6BYkJxd_KqDT2B3GBkwhn7VKT9ZVj-O_tj0Q,17747
|
41
|
+
claude_mpm/agents/templates/local_ops_agent.json,sha256=50i0Ic2zkxR19VRw8kAadkdR3-sq03SASj1e8TgLIZo,8203
|
41
42
|
claude_mpm/agents/templates/memory_manager.json,sha256=b6bTMLFShY4-884pgjc6wsfLICcVx17ZrRj7y-NH61w,12613
|
42
43
|
claude_mpm/agents/templates/nextjs_engineer.json,sha256=Y2NUxJ3D6FFkDdk83kTqbJ70IZKAVJZkePbyEX_hESo,18786
|
43
44
|
claude_mpm/agents/templates/ops.json,sha256=zp1uBfxdp7Atja0oLcNr8TAv-CQuK4mAnbfnLQMFIF4,10904
|
@@ -60,7 +61,7 @@ claude_mpm/agents/templates/.claude-mpm/memories/README.md,sha256=vEiG7cPjHRZfwX
|
|
60
61
|
claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md,sha256=KMZSJrQi-wHOwfl2C0m3A4PpC4QuBtDolAtVybGahKc,77
|
61
62
|
claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md,sha256=UBm4BycXtdaa-_l1VCh0alTGGOUSsnCbpKwbFuI-mUY,2219
|
62
63
|
claude_mpm/agents/templates/logs/prompts/agent_engineer_20250901_010124_142.md,sha256=oPvFSYFnmJ4TkbTe4AZnNHWaJMJ-xqZP2WM6scUKQKo,13089
|
63
|
-
claude_mpm/cli/__init__.py,sha256=
|
64
|
+
claude_mpm/cli/__init__.py,sha256=TTtZNHx70miCIk-6ekKZiawOA9a0q1-HmVDecljZ3ZA,19317
|
64
65
|
claude_mpm/cli/__main__.py,sha256=WnVGBwe10InxuZjJRFdwuMF6Gh16aXox6zFgxr0sRXk,847
|
65
66
|
claude_mpm/cli/parser.py,sha256=Vqx9n-6Xo1uNhXR4rThmgWpZXTr0nOtkgDf3oMS9b0g,5855
|
66
67
|
claude_mpm/cli/startup_logging.py,sha256=xtgAmTirhpk2B-dIS9YKcesPprXTukfu24gJSmGt8y8,29357
|
@@ -98,6 +99,7 @@ claude_mpm/cli/commands/run.py,sha256=_MpX-Eh4OVaUuS_cX8UZFGPbtMwZq0kFfstI6V4VA3
|
|
98
99
|
claude_mpm/cli/commands/search.py,sha256=_0qbUnop8v758MHsB0fAop8FVxwygD59tec_-iN7pLE,9806
|
99
100
|
claude_mpm/cli/commands/tickets.py,sha256=kl2dklTBnG3Y4jUUJ_PcEVsTx4CtVJfkGWboWBx_mQM,21234
|
100
101
|
claude_mpm/cli/commands/uninstall.py,sha256=KGlVG6veEs1efLVjrZ3wSty7e1zVR9wpt-VXQA1RzWw,5945
|
102
|
+
claude_mpm/cli/commands/verify.py,sha256=KhtykUj5hodz0vUQSMCFxe3rGQdqerMVH6J8WlCIWX4,3694
|
101
103
|
claude_mpm/cli/interactive/__init__.py,sha256=vQqUCgPFvLYA1Vkq-5pnY7Ow3A-IgdM0SByfNL1ZLTk,433
|
102
104
|
claude_mpm/cli/interactive/agent_wizard.py,sha256=PMSG6F3Jngb0Gm4nBA6X2PK9Iuuj-AuJd35-g4iE4tE,36202
|
103
105
|
claude_mpm/cli/parsers/__init__.py,sha256=f0Fm1DDXorlVOZPLxUpjC-GIvLh01G-FZOK7TEV1L3I,1005
|
@@ -105,7 +107,7 @@ claude_mpm/cli/parsers/agent_manager_parser.py,sha256=TQEIm638ELM4X_AAGcn6WrJxlt
|
|
105
107
|
claude_mpm/cli/parsers/agents_parser.py,sha256=R-9ESNXdjqr5iCEIcmbb2EPvcswW9UfJzMj5bAkgI4U,9042
|
106
108
|
claude_mpm/cli/parsers/analyze_code_parser.py,sha256=cpJSMFbc3mqB4qrMBIEZiikzPekC2IQX-cjt9U2fHW4,5356
|
107
109
|
claude_mpm/cli/parsers/analyze_parser.py,sha256=E00Ao0zwzbJPchs_AJt-aoQ7LQEtJPXRCNQ6Piivb4o,3908
|
108
|
-
claude_mpm/cli/parsers/base_parser.py,sha256=
|
110
|
+
claude_mpm/cli/parsers/base_parser.py,sha256=Wejn4lAo-rMUQVP13PAehE6NoqmH2AD50HLKDf3QAKM,14169
|
109
111
|
claude_mpm/cli/parsers/config_parser.py,sha256=wp6NbV8_p9txP28MXFcQrri0JDIfGFM7u4aJbYJXcYQ,2699
|
110
112
|
claude_mpm/cli/parsers/configure_parser.py,sha256=xE77C9yMGGmXFuYmSrWBAeKl1xxBaYqrlI9NDSvrjmk,4244
|
111
113
|
claude_mpm/cli/parsers/dashboard_parser.py,sha256=JBCM6v_iZhADr_Fwtk_d3up9AOod1avMab-vkNE61gE,3460
|
@@ -410,6 +412,7 @@ claude_mpm/services/exceptions.py,sha256=5lVZETr_6-xk0ItH7BTfYUiX5RlckS1e8ah_Ual
|
|
410
412
|
claude_mpm/services/hook_installer_service.py,sha256=z3kKeriEY1Y9bFesuGlHBxhCtc0Wzd3Zv02k2_rEyGo,19727
|
411
413
|
claude_mpm/services/hook_service.py,sha256=rZnMn_4qxX5g9KAn0IQdoG50WmySNfsTmfG0XHuRHXk,15737
|
412
414
|
claude_mpm/services/mcp_config_manager.py,sha256=bMV3vkDahRjH94Ht91sUg1Ha8H1Sz63Q1AM9SddfvBY,28957
|
415
|
+
claude_mpm/services/mcp_service_verifier.py,sha256=ngiegCngX18AFehfyJdvqQAvscoIBvFN_DeOoGTjxj0,25164
|
413
416
|
claude_mpm/services/memory_hook_service.py,sha256=pRlTClkRcw30Jhwbha4BC8IMdzKZxF8aWqf52JlntgY,11600
|
414
417
|
claude_mpm/services/monitor_build_service.py,sha256=8gWR9CaqgXdG6-OjOFXGpk28GCcJTlHhojkUYnMCebI,12160
|
415
418
|
claude_mpm/services/port_manager.py,sha256=CYqLh8Ss_-aoYEXV3G6uZkGexpsRK_XTBL0bV4P3tSI,22838
|
@@ -431,7 +434,7 @@ claude_mpm/services/agents/deployment/__init__.py,sha256=iprW-Ww3LBtWu9pVFjAX45_
|
|
431
434
|
claude_mpm/services/agents/deployment/agent_config_provider.py,sha256=gtQcbGr0GspZYplbeRkGXd3BAXl3_pWhKSsbfiSVTww,15234
|
432
435
|
claude_mpm/services/agents/deployment/agent_configuration_manager.py,sha256=5nf69hfo7lUa3_55sDcU5WfpzBtOZCMSTPYGr2dlIdY,12173
|
433
436
|
claude_mpm/services/agents/deployment/agent_definition_factory.py,sha256=DaJ__b3-ADinfd-qNnW6LKWfclOaZbkBBcfuHgqlUic,2697
|
434
|
-
claude_mpm/services/agents/deployment/agent_deployment.py,sha256=
|
437
|
+
claude_mpm/services/agents/deployment/agent_deployment.py,sha256=wcicglXOv4tiJsDUSUJyEgJJryPmXbgGyK7kuiflq7Y,36860
|
435
438
|
claude_mpm/services/agents/deployment/agent_discovery_service.py,sha256=-Iw_gIA6yTdt2eHGDLVJtz9wIQiSUH20WXTapqxd8_Q,18551
|
436
439
|
claude_mpm/services/agents/deployment/agent_environment_manager.py,sha256=kjMiLyZYHs-s9pVdtj4av4hJW0gNXnD7lZz2CJpxmZI,9863
|
437
440
|
claude_mpm/services/agents/deployment/agent_filesystem_manager.py,sha256=F58zMgJYISbi8eqcVlEq6Nd5Q4oLg4Zj4wll4gwRL64,12965
|
@@ -770,9 +773,9 @@ claude_mpm/utils/subprocess_utils.py,sha256=D0izRT8anjiUb_JG72zlJR_JAw1cDkb7kalN
|
|
770
773
|
claude_mpm/validation/__init__.py,sha256=YZhwE3mhit-lslvRLuwfX82xJ_k4haZeKmh4IWaVwtk,156
|
771
774
|
claude_mpm/validation/agent_validator.py,sha256=Nm2WmcbCb0EwOG4nFcikc3wVdiiAfjGBBI3YoR6ainQ,20915
|
772
775
|
claude_mpm/validation/frontmatter_validator.py,sha256=IDBOCBweO6umydSnUJjBh81sKk3cy9hRFYm61DCiXbI,7020
|
773
|
-
claude_mpm-4.4.
|
774
|
-
claude_mpm-4.4.
|
775
|
-
claude_mpm-4.4.
|
776
|
-
claude_mpm-4.4.
|
777
|
-
claude_mpm-4.4.
|
778
|
-
claude_mpm-4.4.
|
776
|
+
claude_mpm-4.4.7.dist-info/licenses/LICENSE,sha256=lpaivOlPuBZW1ds05uQLJJswy8Rp_HMNieJEbFlqvLk,1072
|
777
|
+
claude_mpm-4.4.7.dist-info/METADATA,sha256=dxh7pWMT-QL9jjSQ4oI4tYrYfjuYfJAMucrSK30Jn70,17517
|
778
|
+
claude_mpm-4.4.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
779
|
+
claude_mpm-4.4.7.dist-info/entry_points.txt,sha256=FDPZgz8JOvD-6iuXY2l9Zbo9zYVRuE4uz4Qr0vLeGOk,471
|
780
|
+
claude_mpm-4.4.7.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
|
781
|
+
claude_mpm-4.4.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|