claude-mpm 4.4.11__py3-none-any.whl → 4.5.0__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/web_qa.json +84 -5
- claude_mpm/cli/__init__.py +27 -1
- claude_mpm/cli/commands/mcp_setup_external.py +1 -3
- claude_mpm/cli/commands/verify.py +13 -12
- claude_mpm/services/diagnostics/checks/claude_code_check.py +4 -4
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +139 -69
- claude_mpm/services/mcp_config_manager.py +189 -89
- claude_mpm/services/mcp_service_verifier.py +84 -46
- {claude_mpm-4.4.11.dist-info → claude_mpm-4.5.0.dist-info}/METADATA +1 -1
- {claude_mpm-4.4.11.dist-info → claude_mpm-4.5.0.dist-info}/RECORD +15 -15
- {claude_mpm-4.4.11.dist-info → claude_mpm-4.5.0.dist-info}/WHEEL +0 -0
- {claude_mpm-4.4.11.dist-info → claude_mpm-4.5.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.4.11.dist-info → claude_mpm-4.5.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.4.11.dist-info → claude_mpm-4.5.0.dist-info}/top_level.txt +0 -0
@@ -43,30 +43,31 @@ class MCPConfigManager:
|
|
43
43
|
|
44
44
|
# Static known-good MCP service configurations
|
45
45
|
# These are the correct, tested configurations that work reliably
|
46
|
+
# Note: Commands will be resolved to full paths dynamically in get_static_service_config()
|
46
47
|
STATIC_MCP_CONFIGS = {
|
47
48
|
"kuzu-memory": {
|
48
49
|
"type": "stdio",
|
49
|
-
"command": "kuzu-memory", #
|
50
|
-
"args": ["mcp", "serve"] # v1.1.0+ uses 'mcp serve' command
|
50
|
+
"command": "kuzu-memory", # Will be resolved to full path
|
51
|
+
"args": ["mcp", "serve"], # v1.1.0+ uses 'mcp serve' command
|
51
52
|
},
|
52
53
|
"mcp-ticketer": {
|
53
54
|
"type": "stdio",
|
54
|
-
"command": "mcp-ticketer", #
|
55
|
-
"args": ["mcp"]
|
55
|
+
"command": "mcp-ticketer", # Will be resolved to full path
|
56
|
+
"args": ["mcp"],
|
56
57
|
},
|
57
58
|
"mcp-browser": {
|
58
59
|
"type": "stdio",
|
59
|
-
"command": "mcp-browser", #
|
60
|
+
"command": "mcp-browser", # Will be resolved to full path
|
60
61
|
"args": ["mcp"],
|
61
|
-
"env": {"MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser")}
|
62
|
+
"env": {"MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser")},
|
62
63
|
},
|
63
64
|
"mcp-vector-search": {
|
64
65
|
"type": "stdio",
|
65
|
-
#
|
66
|
-
"command":
|
66
|
+
# Special handling: needs Python interpreter from pipx venv
|
67
|
+
"command": "python", # Will be resolved to pipx venv Python
|
67
68
|
"args": ["-m", "mcp_vector_search.mcp.server", "{project_root}"],
|
68
|
-
"env": {}
|
69
|
-
}
|
69
|
+
"env": {},
|
70
|
+
},
|
70
71
|
}
|
71
72
|
|
72
73
|
def __init__(self):
|
@@ -264,7 +265,11 @@ class MCPConfigManager:
|
|
264
265
|
test_args = config["args"].copy()
|
265
266
|
# Replace project root placeholder for testing
|
266
267
|
test_args = [
|
267
|
-
|
268
|
+
(
|
269
|
+
arg.replace("{project_root}", str(self.project_root))
|
270
|
+
if "{project_root}" in arg
|
271
|
+
else arg
|
272
|
+
)
|
268
273
|
for arg in test_args
|
269
274
|
]
|
270
275
|
|
@@ -286,13 +291,16 @@ class MCPConfigManager:
|
|
286
291
|
text=True,
|
287
292
|
timeout=5,
|
288
293
|
check=False,
|
289
|
-
env=config.get("env", {})
|
294
|
+
env=config.get("env", {}),
|
290
295
|
)
|
291
296
|
|
292
297
|
# Check if command executed (exit code 0 or 1 for help)
|
293
298
|
if result.returncode in [0, 1]:
|
294
299
|
# Additional check for import errors in stderr
|
295
|
-
if
|
300
|
+
if (
|
301
|
+
"ModuleNotFoundError" in result.stderr
|
302
|
+
or "ImportError" in result.stderr
|
303
|
+
):
|
296
304
|
self.logger.debug(f"Service {service_name} has import errors")
|
297
305
|
return False
|
298
306
|
return True
|
@@ -305,7 +313,9 @@ class MCPConfigManager:
|
|
305
313
|
|
306
314
|
return False
|
307
315
|
|
308
|
-
def get_static_service_config(
|
316
|
+
def get_static_service_config(
|
317
|
+
self, service_name: str, project_path: Optional[str] = None
|
318
|
+
) -> Optional[Dict]:
|
309
319
|
"""
|
310
320
|
Get the static, known-good configuration for an MCP service.
|
311
321
|
|
@@ -326,73 +336,118 @@ class MCPConfigManager:
|
|
326
336
|
if service_name in ["kuzu-memory", "mcp-ticketer", "mcp-browser"]:
|
327
337
|
# Try to find the full path of the binary
|
328
338
|
binary_name = config["command"]
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
339
|
+
|
340
|
+
# First check pipx location
|
341
|
+
pipx_bin = (
|
342
|
+
Path.home()
|
343
|
+
/ ".local"
|
344
|
+
/ "pipx"
|
345
|
+
/ "venvs"
|
346
|
+
/ service_name
|
347
|
+
/ "bin"
|
348
|
+
/ binary_name
|
349
|
+
)
|
350
|
+
if pipx_bin.exists():
|
351
|
+
binary_path = str(pipx_bin)
|
352
|
+
else:
|
353
|
+
# Try which command
|
354
|
+
binary_path = shutil.which(binary_name)
|
355
|
+
|
356
|
+
if not binary_path:
|
357
|
+
# Try common installation locations
|
358
|
+
possible_paths = [
|
359
|
+
Path.home() / ".local" / "bin" / binary_name,
|
360
|
+
Path("/opt/homebrew/bin") / binary_name,
|
361
|
+
Path("/usr/local/bin") / binary_name,
|
362
|
+
]
|
363
|
+
for path in possible_paths:
|
364
|
+
if path.exists():
|
365
|
+
binary_path = str(path)
|
366
|
+
break
|
342
367
|
|
343
368
|
if binary_path:
|
344
369
|
config["command"] = binary_path
|
345
|
-
|
370
|
+
else:
|
371
|
+
# Fall back to pipx run method if binary not found
|
372
|
+
self.logger.debug(
|
373
|
+
f"Could not find {binary_name}, using pipx run fallback"
|
374
|
+
)
|
375
|
+
config["command"] = "pipx"
|
376
|
+
config["args"] = ["run", service_name] + config["args"]
|
346
377
|
|
347
|
-
# Resolve pipx command to full path if needed
|
348
|
-
|
378
|
+
# Resolve pipx command to full path if needed (for fallback configs)
|
379
|
+
if config.get("command") == "pipx":
|
349
380
|
pipx_path = shutil.which("pipx")
|
350
381
|
if not pipx_path:
|
351
382
|
# Try common pipx locations
|
352
|
-
|
353
|
-
"/
|
354
|
-
"/
|
355
|
-
|
356
|
-
]
|
357
|
-
|
358
|
-
|
383
|
+
possible_pipx_paths = [
|
384
|
+
Path.home() / ".local" / "bin" / "pipx",
|
385
|
+
Path("/opt/homebrew/bin/pipx"),
|
386
|
+
Path("/usr/local/bin/pipx"),
|
387
|
+
]
|
388
|
+
for path in possible_pipx_paths:
|
389
|
+
if path.exists():
|
390
|
+
pipx_path = str(path)
|
359
391
|
break
|
360
392
|
if pipx_path:
|
361
393
|
config["command"] = pipx_path
|
362
|
-
else:
|
363
|
-
# Keep as "pipx" and hope it's in PATH when executed
|
364
|
-
config["command"] = "pipx"
|
365
394
|
|
366
395
|
# Handle user-specific paths for mcp-vector-search
|
367
396
|
if service_name == "mcp-vector-search":
|
368
397
|
# Get the correct pipx venv path for the current user
|
369
398
|
home = Path.home()
|
370
|
-
python_path =
|
399
|
+
python_path = (
|
400
|
+
home
|
401
|
+
/ ".local"
|
402
|
+
/ "pipx"
|
403
|
+
/ "venvs"
|
404
|
+
/ "mcp-vector-search"
|
405
|
+
/ "bin"
|
406
|
+
/ "python"
|
407
|
+
)
|
371
408
|
|
372
|
-
# Check if the Python interpreter exists
|
409
|
+
# Check if the Python interpreter exists
|
373
410
|
if python_path.exists():
|
374
411
|
config["command"] = str(python_path)
|
375
412
|
else:
|
376
413
|
# Fallback to pipx run method
|
377
|
-
import shutil
|
378
414
|
pipx_path = shutil.which("pipx")
|
379
415
|
if not pipx_path:
|
380
416
|
# Try common pipx locations
|
381
|
-
|
382
|
-
"/
|
383
|
-
"/
|
384
|
-
|
385
|
-
]
|
386
|
-
|
387
|
-
|
417
|
+
possible_pipx_paths = [
|
418
|
+
Path.home() / ".local" / "bin" / "pipx",
|
419
|
+
Path("/opt/homebrew/bin/pipx"),
|
420
|
+
Path("/usr/local/bin/pipx"),
|
421
|
+
]
|
422
|
+
for path in possible_pipx_paths:
|
423
|
+
if path.exists():
|
424
|
+
pipx_path = str(path)
|
388
425
|
break
|
389
|
-
|
390
|
-
|
426
|
+
|
427
|
+
if pipx_path:
|
428
|
+
config["command"] = pipx_path
|
429
|
+
else:
|
430
|
+
config["command"] = "pipx" # Hope it's in PATH
|
431
|
+
|
432
|
+
# Use pipx run with the spec argument
|
433
|
+
config["args"] = [
|
434
|
+
"run",
|
435
|
+
"--spec",
|
436
|
+
"mcp-vector-search",
|
437
|
+
"python",
|
438
|
+
"-m",
|
439
|
+
"mcp_vector_search.mcp.server",
|
440
|
+
"{project_root}",
|
441
|
+
]
|
391
442
|
|
392
443
|
# Use provided project path or current project
|
393
444
|
project_root = project_path if project_path else str(self.project_root)
|
394
445
|
config["args"] = [
|
395
|
-
|
446
|
+
(
|
447
|
+
arg.replace("{project_root}", project_root)
|
448
|
+
if "{project_root}" in arg
|
449
|
+
else arg
|
450
|
+
)
|
396
451
|
for arg in config["args"]
|
397
452
|
]
|
398
453
|
|
@@ -416,10 +471,13 @@ class MCPConfigManager:
|
|
416
471
|
if static_config:
|
417
472
|
# Validate that the static config actually works
|
418
473
|
if self.test_service_command(service_name, static_config):
|
419
|
-
self.logger.debug(
|
474
|
+
self.logger.debug(
|
475
|
+
f"Static config for {service_name} validated successfully"
|
476
|
+
)
|
420
477
|
return static_config
|
421
|
-
|
422
|
-
|
478
|
+
self.logger.warning(
|
479
|
+
f"Static config for {service_name} failed validation, trying fallback"
|
480
|
+
)
|
423
481
|
|
424
482
|
# Fall back to detection-based configuration for unknown services
|
425
483
|
import shutil
|
@@ -547,7 +605,9 @@ class MCPConfigManager:
|
|
547
605
|
|
548
606
|
# Standard version detection - look for "mcp serve" command (v1.1.0+)
|
549
607
|
# This is the correct format for kuzu-memory v1.1.0 and later
|
550
|
-
if "mcp serve" in help_output or (
|
608
|
+
if "mcp serve" in help_output or (
|
609
|
+
"mcp" in help_output and "serve" in help_output
|
610
|
+
):
|
551
611
|
# Standard v1.1.0+ version with mcp serve command
|
552
612
|
kuzu_args = ["mcp", "serve"]
|
553
613
|
# Legacy version detection - only "serve" without "mcp"
|
@@ -656,10 +716,14 @@ class MCPConfigManager:
|
|
656
716
|
# Check and fix each service configuration
|
657
717
|
for service_name in self.PIPX_SERVICES:
|
658
718
|
# Get the correct static configuration with project-specific paths
|
659
|
-
correct_config = self.get_static_service_config(
|
719
|
+
correct_config = self.get_static_service_config(
|
720
|
+
service_name, project_key
|
721
|
+
)
|
660
722
|
|
661
723
|
if not correct_config:
|
662
|
-
self.logger.warning(
|
724
|
+
self.logger.warning(
|
725
|
+
f"No static config available for {service_name}"
|
726
|
+
)
|
663
727
|
continue
|
664
728
|
|
665
729
|
# Check if service exists and has correct configuration
|
@@ -671,13 +735,15 @@ class MCPConfigManager:
|
|
671
735
|
# Service is missing
|
672
736
|
needs_update = True
|
673
737
|
added_services.append(f"{service_name} in {Path(project_key).name}")
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
738
|
+
# Service exists, check if configuration is correct
|
739
|
+
# Compare command and args (the most critical parts)
|
740
|
+
elif existing_config.get("command") != correct_config.get(
|
741
|
+
"command"
|
742
|
+
) or existing_config.get("args") != correct_config.get("args"):
|
743
|
+
needs_update = True
|
744
|
+
fixed_services.append(
|
745
|
+
f"{service_name} in {Path(project_key).name}"
|
746
|
+
)
|
681
747
|
|
682
748
|
# Update configuration if needed
|
683
749
|
if needs_update:
|
@@ -698,6 +764,7 @@ class MCPConfigManager:
|
|
698
764
|
f".backup.{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}.json"
|
699
765
|
)
|
700
766
|
import shutil
|
767
|
+
|
701
768
|
shutil.copy2(self.claude_config_path, backup_path)
|
702
769
|
self.logger.debug(f"Created backup: {backup_path}")
|
703
770
|
|
@@ -707,9 +774,13 @@ class MCPConfigManager:
|
|
707
774
|
|
708
775
|
messages = []
|
709
776
|
if added_services:
|
710
|
-
messages.append(
|
777
|
+
messages.append(
|
778
|
+
f"Added MCP services: {', '.join(added_services[:3])}"
|
779
|
+
)
|
711
780
|
if fixed_services:
|
712
|
-
messages.append(
|
781
|
+
messages.append(
|
782
|
+
f"Fixed MCP services: {', '.join(fixed_services[:3])}"
|
783
|
+
)
|
713
784
|
|
714
785
|
if messages:
|
715
786
|
return True, "; ".join(messages)
|
@@ -977,11 +1048,12 @@ class MCPConfigManager:
|
|
977
1048
|
)
|
978
1049
|
|
979
1050
|
if inject_result.returncode == 0:
|
980
|
-
self.logger.info(
|
1051
|
+
self.logger.info(
|
1052
|
+
"✅ Successfully injected gql dependency into mcp-ticketer"
|
1053
|
+
)
|
981
1054
|
return True
|
982
|
-
|
983
|
-
|
984
|
-
return False
|
1055
|
+
self.logger.warning(f"Failed to inject gql: {inject_result.stderr}")
|
1056
|
+
return False
|
985
1057
|
|
986
1058
|
return False
|
987
1059
|
|
@@ -1032,7 +1104,9 @@ class MCPConfigManager:
|
|
1032
1104
|
|
1033
1105
|
elif issue_type == "import_error":
|
1034
1106
|
# Reinstall to fix corrupted installation
|
1035
|
-
self.logger.info(
|
1107
|
+
self.logger.info(
|
1108
|
+
f" Reinstalling {service_name} to fix import errors..."
|
1109
|
+
)
|
1036
1110
|
success = self._reinstall_service(service_name)
|
1037
1111
|
if success:
|
1038
1112
|
# Special handling for mcp-ticketer - inject missing gql dependency
|
@@ -1048,13 +1122,17 @@ class MCPConfigManager:
|
|
1048
1122
|
if self._check_and_fix_mcp_ticketer_dependencies():
|
1049
1123
|
fixed_services.append(f"{service_name} (dependency fixed)")
|
1050
1124
|
else:
|
1051
|
-
failed_services.append(
|
1125
|
+
failed_services.append(
|
1126
|
+
f"{service_name} (dependency fix failed)"
|
1127
|
+
)
|
1052
1128
|
else:
|
1053
1129
|
failed_services.append(f"{service_name} (unknown dependency issue)")
|
1054
1130
|
|
1055
1131
|
elif issue_type == "path_issue":
|
1056
1132
|
# Path issues are handled by config updates
|
1057
|
-
self.logger.info(
|
1133
|
+
self.logger.info(
|
1134
|
+
f" Path issue for {service_name} will be fixed by config update"
|
1135
|
+
)
|
1058
1136
|
fixed_services.append(f"{service_name} (config updated)")
|
1059
1137
|
|
1060
1138
|
# Build result message
|
@@ -1098,7 +1176,7 @@ class MCPConfigManager:
|
|
1098
1176
|
capture_output=True,
|
1099
1177
|
text=True,
|
1100
1178
|
timeout=10,
|
1101
|
-
check=False
|
1179
|
+
check=False,
|
1102
1180
|
)
|
1103
1181
|
|
1104
1182
|
# Check for specific error patterns
|
@@ -1107,11 +1185,17 @@ class MCPConfigManager:
|
|
1107
1185
|
combined_output = stderr_lower + stdout_lower
|
1108
1186
|
|
1109
1187
|
# Not installed
|
1110
|
-
if
|
1188
|
+
if (
|
1189
|
+
"no apps associated" in combined_output
|
1190
|
+
or "not found" in combined_output
|
1191
|
+
):
|
1111
1192
|
return "not_installed"
|
1112
1193
|
|
1113
1194
|
# Import errors (like mcp-ticketer's corrupted state)
|
1114
|
-
if
|
1195
|
+
if (
|
1196
|
+
"modulenotfounderror" in combined_output
|
1197
|
+
or "importerror" in combined_output
|
1198
|
+
):
|
1115
1199
|
# Check if it's specifically the gql dependency for mcp-ticketer
|
1116
1200
|
if service_name == "mcp-ticketer" and "gql" in combined_output:
|
1117
1201
|
return "missing_dependency"
|
@@ -1122,7 +1206,11 @@ class MCPConfigManager:
|
|
1122
1206
|
return "path_issue"
|
1123
1207
|
|
1124
1208
|
# If help text appears, service is working
|
1125
|
-
if
|
1209
|
+
if (
|
1210
|
+
"usage:" in combined_output
|
1211
|
+
or "help" in combined_output
|
1212
|
+
or result.returncode in [0, 1]
|
1213
|
+
):
|
1126
1214
|
return None # Service is working
|
1127
1215
|
|
1128
1216
|
# Unknown issue
|
@@ -1157,7 +1245,7 @@ class MCPConfigManager:
|
|
1157
1245
|
capture_output=True,
|
1158
1246
|
text=True,
|
1159
1247
|
timeout=30,
|
1160
|
-
check=False
|
1248
|
+
check=False,
|
1161
1249
|
)
|
1162
1250
|
|
1163
1251
|
# Don't check return code - uninstall might fail if partially corrupted
|
@@ -1170,7 +1258,7 @@ class MCPConfigManager:
|
|
1170
1258
|
capture_output=True,
|
1171
1259
|
text=True,
|
1172
1260
|
timeout=120,
|
1173
|
-
check=False
|
1261
|
+
check=False,
|
1174
1262
|
)
|
1175
1263
|
|
1176
1264
|
if install_result.returncode == 0:
|
@@ -1179,12 +1267,14 @@ class MCPConfigManager:
|
|
1179
1267
|
if issue is None:
|
1180
1268
|
self.logger.info(f"✅ Successfully reinstalled {service_name}")
|
1181
1269
|
return True
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
else:
|
1186
|
-
self.logger.error(f"Failed to reinstall {service_name}: {install_result.stderr}")
|
1270
|
+
self.logger.warning(
|
1271
|
+
f"Reinstalled {service_name} but still has issue: {issue}"
|
1272
|
+
)
|
1187
1273
|
return False
|
1274
|
+
self.logger.error(
|
1275
|
+
f"Failed to reinstall {service_name}: {install_result.stderr}"
|
1276
|
+
)
|
1277
|
+
return False
|
1188
1278
|
|
1189
1279
|
except Exception as e:
|
1190
1280
|
self.logger.error(f"Error reinstalling {service_name}: {e}")
|
@@ -1266,7 +1356,9 @@ class MCPConfigManager:
|
|
1266
1356
|
|
1267
1357
|
return False
|
1268
1358
|
|
1269
|
-
def _get_fallback_config(
|
1359
|
+
def _get_fallback_config(
|
1360
|
+
self, service_name: str, project_path: str
|
1361
|
+
) -> Optional[Dict]:
|
1270
1362
|
"""
|
1271
1363
|
Get a fallback configuration for a service if the primary config fails.
|
1272
1364
|
|
@@ -1282,8 +1374,16 @@ class MCPConfigManager:
|
|
1282
1374
|
return {
|
1283
1375
|
"type": "stdio",
|
1284
1376
|
"command": "pipx",
|
1285
|
-
"args": [
|
1286
|
-
|
1377
|
+
"args": [
|
1378
|
+
"run",
|
1379
|
+
"--spec",
|
1380
|
+
"mcp-vector-search",
|
1381
|
+
"python",
|
1382
|
+
"-m",
|
1383
|
+
"mcp_vector_search.mcp.server",
|
1384
|
+
project_path,
|
1385
|
+
],
|
1386
|
+
"env": {},
|
1287
1387
|
}
|
1288
1388
|
|
1289
1389
|
# For other services, try pipx run
|