claude-mpm 4.4.5__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.
Files changed (32) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/templates/local_ops_agent.json +273 -0
  3. claude_mpm/cli/__init__.py +21 -0
  4. claude_mpm/cli/commands/mcp_external_commands.py +7 -7
  5. claude_mpm/cli/commands/mcp_install_commands.py +9 -9
  6. claude_mpm/cli/commands/mcp_setup_external.py +6 -6
  7. claude_mpm/cli/commands/verify.py +118 -0
  8. claude_mpm/cli/parsers/base_parser.py +5 -0
  9. claude_mpm/hooks/kuzu_memory_hook.py +4 -2
  10. claude_mpm/services/agents/deployment/agent_deployment.py +10 -6
  11. claude_mpm/services/diagnostics/checks/__init__.py +2 -2
  12. claude_mpm/services/diagnostics/checks/{claude_desktop_check.py → claude_code_check.py} +95 -112
  13. claude_mpm/services/diagnostics/checks/mcp_check.py +6 -6
  14. claude_mpm/services/diagnostics/checks/mcp_services_check.py +29 -6
  15. claude_mpm/services/diagnostics/diagnostic_runner.py +5 -5
  16. claude_mpm/services/diagnostics/doctor_reporter.py +4 -4
  17. claude_mpm/services/mcp_config_manager.py +46 -26
  18. claude_mpm/services/mcp_gateway/core/process_pool.py +11 -8
  19. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +4 -4
  20. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +8 -4
  21. claude_mpm/services/mcp_service_verifier.py +690 -0
  22. claude_mpm/services/project/project_organizer.py +8 -1
  23. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +1 -2
  24. claude_mpm/services/unified/config_strategies/context_strategy.py +1 -3
  25. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +3 -1
  26. claude_mpm/validation/frontmatter_validator.py +1 -1
  27. {claude_mpm-4.4.5.dist-info → claude_mpm-4.4.7.dist-info}/METADATA +20 -5
  28. {claude_mpm-4.4.5.dist-info → claude_mpm-4.4.7.dist-info}/RECORD +32 -29
  29. {claude_mpm-4.4.5.dist-info → claude_mpm-4.4.7.dist-info}/WHEEL +0 -0
  30. {claude_mpm-4.4.5.dist-info → claude_mpm-4.4.7.dist-info}/entry_points.txt +0 -0
  31. {claude_mpm-4.4.5.dist-info → claude_mpm-4.4.7.dist-info}/licenses/LICENSE +0 -0
  32. {claude_mpm-4.4.5.dist-info → claude_mpm-4.4.7.dist-info}/top_level.txt +0 -0
@@ -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"