claude-mpm 4.4.7__py3-none-any.whl → 4.4.9__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/services/diagnostics/checks/mcp_services_check.py +494 -6
- claude_mpm/services/diagnostics/diagnostic_runner.py +3 -0
- claude_mpm/services/mcp_config_manager.py +187 -42
- {claude_mpm-4.4.7.dist-info → claude_mpm-4.4.9.dist-info}/METADATA +1 -1
- {claude_mpm-4.4.7.dist-info → claude_mpm-4.4.9.dist-info}/RECORD +10 -10
- {claude_mpm-4.4.7.dist-info → claude_mpm-4.4.9.dist-info}/WHEEL +0 -0
- {claude_mpm-4.4.7.dist-info → claude_mpm-4.4.9.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.4.7.dist-info → claude_mpm-4.4.9.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.4.7.dist-info → claude_mpm-4.4.9.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
4.4.
|
1
|
+
4.4.9
|
@@ -5,11 +5,14 @@ WHY: Verify that MCP services (mcp-vector-search, mcp-browser, mcp-ticketer, kuz
|
|
5
5
|
are properly installed and accessible for enhanced Claude Code capabilities.
|
6
6
|
"""
|
7
7
|
|
8
|
+
import asyncio
|
8
9
|
import json
|
9
10
|
import subprocess
|
11
|
+
import time
|
10
12
|
from pathlib import Path
|
11
13
|
from typing import Dict, List, Optional, Tuple
|
12
14
|
|
15
|
+
from claude_mpm.core.logger import get_logger
|
13
16
|
from ..models import DiagnosticResult, DiagnosticStatus
|
14
17
|
from .base_check import BaseDiagnosticCheck
|
15
18
|
|
@@ -17,6 +20,11 @@ from .base_check import BaseDiagnosticCheck
|
|
17
20
|
class MCPServicesCheck(BaseDiagnosticCheck):
|
18
21
|
"""Check MCP external services installation and health."""
|
19
22
|
|
23
|
+
def __init__(self, verbose: bool = False):
|
24
|
+
"""Initialize the MCP services check."""
|
25
|
+
super().__init__(verbose)
|
26
|
+
self.logger = get_logger(self.__class__.__name__)
|
27
|
+
|
20
28
|
# Define MCP services to check
|
21
29
|
MCP_SERVICES = {
|
22
30
|
"mcp-vector-search": {
|
@@ -29,6 +37,8 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
29
37
|
"check_health": True,
|
30
38
|
"health_command": ["mcp-vector-search", "--version"],
|
31
39
|
"pipx_run_command": ["pipx", "run", "mcp-vector-search", "--version"],
|
40
|
+
"mcp_command": ["python", "-m", "mcp_vector_search.mcp.server"], # Command to run as MCP server
|
41
|
+
"pipx_mcp_command": ["pipx", "run", "--spec", "mcp-vector-search", "python", "-m", "mcp_vector_search.mcp.server"],
|
32
42
|
},
|
33
43
|
"mcp-browser": {
|
34
44
|
"package": "mcp-browser",
|
@@ -37,6 +47,7 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
37
47
|
"check_health": True,
|
38
48
|
"health_command": ["mcp-browser", "--version"],
|
39
49
|
"pipx_run_command": ["pipx", "run", "mcp-browser", "--version"],
|
50
|
+
"mcp_command": ["mcp-browser", "mcp"], # Command to run as MCP server
|
40
51
|
},
|
41
52
|
"mcp-ticketer": {
|
42
53
|
"package": "mcp-ticketer",
|
@@ -45,6 +56,7 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
45
56
|
"check_health": True,
|
46
57
|
"health_command": ["mcp-ticketer", "--version"],
|
47
58
|
"pipx_run_command": ["pipx", "run", "mcp-ticketer", "--version"],
|
59
|
+
"mcp_command": ["mcp-ticketer", "mcp"], # Command to run as MCP server
|
48
60
|
},
|
49
61
|
"kuzu-memory": {
|
50
62
|
"package": "kuzu-memory",
|
@@ -53,6 +65,7 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
53
65
|
"check_health": True, # v1.1.0+ has version command
|
54
66
|
"health_command": ["kuzu-memory", "--version"],
|
55
67
|
"pipx_run_command": ["pipx", "run", "kuzu-memory", "--version"],
|
68
|
+
"mcp_command": ["kuzu-memory", "mcp", "serve"], # v1.1.0+ uses 'mcp serve' args
|
56
69
|
},
|
57
70
|
}
|
58
71
|
|
@@ -71,15 +84,29 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
71
84
|
sub_results = []
|
72
85
|
services_status = {}
|
73
86
|
|
87
|
+
# Check for kuzu-memory configuration issues and offer auto-fix
|
88
|
+
kuzu_config_result = self._check_and_fix_kuzu_memory_config()
|
89
|
+
if kuzu_config_result:
|
90
|
+
sub_results.append(kuzu_config_result)
|
91
|
+
|
74
92
|
# Check each MCP service
|
75
93
|
for service_name, service_config in self.MCP_SERVICES.items():
|
76
94
|
service_result = self._check_service(service_name, service_config)
|
77
95
|
sub_results.append(service_result)
|
96
|
+
|
97
|
+
# Extract connection test info if available
|
98
|
+
connection_test = service_result.details.get("connection_test", {})
|
99
|
+
|
78
100
|
services_status[service_name] = {
|
79
101
|
"status": service_result.status.value,
|
80
102
|
"installed": service_result.details.get("installed", False),
|
81
103
|
"accessible": service_result.details.get("accessible", False),
|
82
104
|
"version": service_result.details.get("version"),
|
105
|
+
"connection_tested": bool(connection_test),
|
106
|
+
"connected": connection_test.get("connected", False),
|
107
|
+
"response_time_ms": connection_test.get("response_time_ms"),
|
108
|
+
"tools_discovered": connection_test.get("tools_discovered", 0),
|
109
|
+
"connection_error": connection_test.get("error"),
|
83
110
|
}
|
84
111
|
|
85
112
|
# Check MCP gateway configuration for services
|
@@ -91,12 +118,18 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
91
118
|
accessible_count = sum(
|
92
119
|
1 for s in services_status.values() if s["accessible"]
|
93
120
|
)
|
121
|
+
connected_count = sum(1 for s in services_status.values() if s["connected"])
|
94
122
|
total_services = len(self.MCP_SERVICES)
|
95
123
|
|
124
|
+
# Calculate total tools discovered
|
125
|
+
total_tools = sum(s.get("tools_discovered", 0) for s in services_status.values())
|
126
|
+
|
96
127
|
details["services"] = services_status
|
97
128
|
details["installed_count"] = installed_count
|
98
129
|
details["accessible_count"] = accessible_count
|
130
|
+
details["connected_count"] = connected_count
|
99
131
|
details["total_services"] = total_services
|
132
|
+
details["total_tools_discovered"] = total_tools
|
100
133
|
details["gateway_configured"] = gateway_result.status == DiagnosticStatus.OK
|
101
134
|
|
102
135
|
# Determine overall status
|
@@ -109,6 +142,12 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
109
142
|
elif installed_count == 0:
|
110
143
|
status = DiagnosticStatus.WARNING
|
111
144
|
message = "No MCP services installed"
|
145
|
+
elif connected_count == total_services:
|
146
|
+
status = DiagnosticStatus.OK
|
147
|
+
message = f"All {total_services} MCP services connected ({total_tools} tools available)"
|
148
|
+
elif connected_count > 0:
|
149
|
+
status = DiagnosticStatus.WARNING
|
150
|
+
message = f"{connected_count}/{total_services} MCP services connected, {installed_count} installed"
|
112
151
|
elif accessible_count < installed_count:
|
113
152
|
status = DiagnosticStatus.WARNING
|
114
153
|
message = f"{installed_count}/{total_services} services installed, {accessible_count} accessible"
|
@@ -116,8 +155,8 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
116
155
|
status = DiagnosticStatus.WARNING
|
117
156
|
message = f"{installed_count}/{total_services} MCP services installed"
|
118
157
|
else:
|
119
|
-
status = DiagnosticStatus.
|
120
|
-
message = f"All {total_services} MCP services installed
|
158
|
+
status = DiagnosticStatus.WARNING
|
159
|
+
message = f"All {total_services} MCP services installed but connections not tested"
|
121
160
|
|
122
161
|
return DiagnosticResult(
|
123
162
|
category=self.category,
|
@@ -135,6 +174,185 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
135
174
|
details={"error": str(e)},
|
136
175
|
)
|
137
176
|
|
177
|
+
async def _test_mcp_connection(
|
178
|
+
self, service_name: str, command: List[str]
|
179
|
+
) -> Dict:
|
180
|
+
"""Test MCP server connection by sending JSON-RPC requests."""
|
181
|
+
result = {
|
182
|
+
"connected": False,
|
183
|
+
"response_time": None,
|
184
|
+
"tools_count": 0,
|
185
|
+
"tools": [],
|
186
|
+
"error": None,
|
187
|
+
}
|
188
|
+
|
189
|
+
process = None
|
190
|
+
try:
|
191
|
+
# Start the MCP server process
|
192
|
+
start_time = time.time()
|
193
|
+
process = await asyncio.create_subprocess_exec(
|
194
|
+
*command,
|
195
|
+
stdin=asyncio.subprocess.PIPE,
|
196
|
+
stdout=asyncio.subprocess.PIPE,
|
197
|
+
stderr=asyncio.subprocess.PIPE,
|
198
|
+
)
|
199
|
+
|
200
|
+
# Give the server a moment to initialize
|
201
|
+
await asyncio.sleep(0.1)
|
202
|
+
|
203
|
+
# Prepare initialize request
|
204
|
+
init_request = {
|
205
|
+
"jsonrpc": "2.0",
|
206
|
+
"method": "initialize",
|
207
|
+
"params": {
|
208
|
+
"protocolVersion": "2024-11-05",
|
209
|
+
"capabilities": {},
|
210
|
+
"clientInfo": {
|
211
|
+
"name": "mpm-doctor",
|
212
|
+
"version": "1.0.0"
|
213
|
+
}
|
214
|
+
},
|
215
|
+
"id": 1
|
216
|
+
}
|
217
|
+
|
218
|
+
# Send initialize request
|
219
|
+
request_line = json.dumps(init_request) + "\n"
|
220
|
+
process.stdin.write(request_line.encode())
|
221
|
+
await process.stdin.drain()
|
222
|
+
|
223
|
+
# Read response with timeout
|
224
|
+
try:
|
225
|
+
response_line = await asyncio.wait_for(
|
226
|
+
process.stdout.readline(), timeout=5.0
|
227
|
+
)
|
228
|
+
response_time = time.time() - start_time
|
229
|
+
|
230
|
+
if response_line:
|
231
|
+
# Some MCP servers may output non-JSON before the actual response
|
232
|
+
# Try to find JSON in the response
|
233
|
+
response_text = response_line.decode().strip()
|
234
|
+
|
235
|
+
# Skip empty lines or non-JSON lines
|
236
|
+
while response_text and not response_text.startswith('{'):
|
237
|
+
# Try to read the next line
|
238
|
+
try:
|
239
|
+
response_line = await asyncio.wait_for(
|
240
|
+
process.stdout.readline(), timeout=1.0
|
241
|
+
)
|
242
|
+
if response_line:
|
243
|
+
response_text = response_line.decode().strip()
|
244
|
+
else:
|
245
|
+
break
|
246
|
+
except asyncio.TimeoutError:
|
247
|
+
break
|
248
|
+
|
249
|
+
if not response_text or not response_text.startswith('{'):
|
250
|
+
result["error"] = "No valid JSON response received"
|
251
|
+
return result
|
252
|
+
|
253
|
+
response = json.loads(response_text)
|
254
|
+
|
255
|
+
# Check for valid JSON-RPC response
|
256
|
+
if "result" in response and response.get("id") == 1:
|
257
|
+
result["connected"] = True
|
258
|
+
result["response_time"] = round(response_time * 1000, 2) # ms
|
259
|
+
|
260
|
+
# Send tools/list request
|
261
|
+
tools_request = {
|
262
|
+
"jsonrpc": "2.0",
|
263
|
+
"method": "tools/list",
|
264
|
+
"params": {},
|
265
|
+
"id": 2
|
266
|
+
}
|
267
|
+
|
268
|
+
request_line = json.dumps(tools_request) + "\n"
|
269
|
+
process.stdin.write(request_line.encode())
|
270
|
+
await process.stdin.drain()
|
271
|
+
|
272
|
+
# Read tools response
|
273
|
+
try:
|
274
|
+
tools_response_line = await asyncio.wait_for(
|
275
|
+
process.stdout.readline(), timeout=3.0
|
276
|
+
)
|
277
|
+
|
278
|
+
if tools_response_line:
|
279
|
+
tools_response = json.loads(tools_response_line.decode())
|
280
|
+
if "result" in tools_response:
|
281
|
+
tools = tools_response["result"].get("tools", [])
|
282
|
+
result["tools_count"] = len(tools)
|
283
|
+
# Store first 5 tool names for display
|
284
|
+
result["tools"] = [
|
285
|
+
tool.get("name", "unknown")
|
286
|
+
for tool in tools[:5]
|
287
|
+
]
|
288
|
+
except asyncio.TimeoutError:
|
289
|
+
# Connection successful but tools query timed out
|
290
|
+
pass
|
291
|
+
except (json.JSONDecodeError, KeyError):
|
292
|
+
# Connection successful but tools response invalid
|
293
|
+
pass
|
294
|
+
|
295
|
+
elif "error" in response:
|
296
|
+
result["error"] = f"MCP error: {response['error'].get('message', 'Unknown error')}"
|
297
|
+
else:
|
298
|
+
result["error"] = "Invalid JSON-RPC response format"
|
299
|
+
|
300
|
+
except asyncio.TimeoutError:
|
301
|
+
# Try to get any error output from stderr
|
302
|
+
stderr_output = ""
|
303
|
+
if process and process.stderr:
|
304
|
+
try:
|
305
|
+
stderr_data = await asyncio.wait_for(
|
306
|
+
process.stderr.read(1000), timeout=0.5
|
307
|
+
)
|
308
|
+
if stderr_data:
|
309
|
+
stderr_output = stderr_data.decode('utf-8', errors='ignore')[:200]
|
310
|
+
except:
|
311
|
+
pass
|
312
|
+
|
313
|
+
if stderr_output:
|
314
|
+
result["error"] = f"Connection timeout (5s). Server output: {stderr_output}"
|
315
|
+
else:
|
316
|
+
result["error"] = "Connection timeout (5s)"
|
317
|
+
|
318
|
+
except json.JSONDecodeError as e:
|
319
|
+
# Try to get stderr for more context
|
320
|
+
stderr_output = ""
|
321
|
+
if process and process.stderr:
|
322
|
+
try:
|
323
|
+
stderr_data = await asyncio.wait_for(
|
324
|
+
process.stderr.read(1000), timeout=0.5
|
325
|
+
)
|
326
|
+
if stderr_data:
|
327
|
+
stderr_output = stderr_data.decode('utf-8', errors='ignore')[:200]
|
328
|
+
except:
|
329
|
+
pass
|
330
|
+
|
331
|
+
if stderr_output:
|
332
|
+
result["error"] = f"Invalid JSON response: {str(e)}. Server error: {stderr_output}"
|
333
|
+
else:
|
334
|
+
result["error"] = f"Invalid JSON response: {str(e)}"
|
335
|
+
|
336
|
+
except FileNotFoundError:
|
337
|
+
result["error"] = f"Command not found: {command[0]}"
|
338
|
+
except PermissionError:
|
339
|
+
result["error"] = f"Permission denied: {command[0]}"
|
340
|
+
except Exception as e:
|
341
|
+
result["error"] = f"Connection failed: {str(e)}"
|
342
|
+
finally:
|
343
|
+
# Clean up process
|
344
|
+
if process:
|
345
|
+
try:
|
346
|
+
process.terminate()
|
347
|
+
await asyncio.wait_for(process.wait(), timeout=2.0)
|
348
|
+
except asyncio.TimeoutError:
|
349
|
+
process.kill()
|
350
|
+
await process.wait()
|
351
|
+
except Exception:
|
352
|
+
pass
|
353
|
+
|
354
|
+
return result
|
355
|
+
|
138
356
|
def _check_service(self, service_name: str, config: Dict) -> DiagnosticResult:
|
139
357
|
"""Check a specific MCP service."""
|
140
358
|
details = {"service": service_name}
|
@@ -145,6 +363,12 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
145
363
|
if pipx_path:
|
146
364
|
details["pipx_path"] = pipx_path
|
147
365
|
|
366
|
+
# Special check for mcp-ticketer: ensure gql dependency
|
367
|
+
if service_name == "mcp-ticketer" and pipx_installed:
|
368
|
+
gql_fixed = self._ensure_mcp_ticketer_gql_dependency()
|
369
|
+
if gql_fixed:
|
370
|
+
details["gql_dependency_fixed"] = True
|
371
|
+
|
148
372
|
# Check if accessible in PATH
|
149
373
|
accessible, command_path = self._check_command_accessible(config["command"])
|
150
374
|
details["accessible"] = accessible
|
@@ -186,6 +410,64 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
186
410
|
details["version"] = version
|
187
411
|
break
|
188
412
|
|
413
|
+
# Test MCP connection if installed (accessible or pipx) and has mcp_command
|
414
|
+
if (accessible or pipx_installed) and "mcp_command" in config:
|
415
|
+
# Determine which command to use for MCP connection test
|
416
|
+
mcp_command = None
|
417
|
+
if pipx_installed and not accessible:
|
418
|
+
# Service is installed via pipx but not in PATH
|
419
|
+
if "pipx_mcp_command" in config:
|
420
|
+
# Use special pipx MCP command if available (e.g., for mcp-vector-search)
|
421
|
+
mcp_command = config["pipx_mcp_command"]
|
422
|
+
else:
|
423
|
+
# Build pipx run command based on package
|
424
|
+
base_cmd = config["mcp_command"]
|
425
|
+
if len(base_cmd) > 0 and base_cmd[0] == config["package"]:
|
426
|
+
# Simple case where first command is the package name
|
427
|
+
mcp_command = ["pipx", "run", config["package"]] + base_cmd[1:]
|
428
|
+
else:
|
429
|
+
# Complex case - just try running the package with mcp arg
|
430
|
+
mcp_command = ["pipx", "run", config["package"], "mcp"]
|
431
|
+
elif details.get("accessible_via_pipx_run"):
|
432
|
+
# Use pipx run for the MCP command
|
433
|
+
if "pipx_mcp_command" in config:
|
434
|
+
# Use special pipx MCP command if available (e.g., for mcp-vector-search)
|
435
|
+
mcp_command = config["pipx_mcp_command"]
|
436
|
+
else:
|
437
|
+
# Build pipx run command
|
438
|
+
base_cmd = config["mcp_command"]
|
439
|
+
if service_name == "kuzu-memory":
|
440
|
+
# Special case for kuzu-memory with args
|
441
|
+
mcp_command = ["pipx", "run", base_cmd[0]] + base_cmd[1:]
|
442
|
+
else:
|
443
|
+
mcp_command = ["pipx", "run"] + base_cmd
|
444
|
+
else:
|
445
|
+
mcp_command = config["mcp_command"]
|
446
|
+
|
447
|
+
if mcp_command:
|
448
|
+
# Run async connection test
|
449
|
+
try:
|
450
|
+
loop = asyncio.new_event_loop()
|
451
|
+
asyncio.set_event_loop(loop)
|
452
|
+
connection_result = loop.run_until_complete(
|
453
|
+
self._test_mcp_connection(service_name, mcp_command)
|
454
|
+
)
|
455
|
+
loop.close()
|
456
|
+
|
457
|
+
# Add connection test results to details
|
458
|
+
details["connection_test"] = {
|
459
|
+
"connected": connection_result["connected"],
|
460
|
+
"response_time_ms": connection_result["response_time"],
|
461
|
+
"tools_discovered": connection_result["tools_count"],
|
462
|
+
"tools_sample": connection_result["tools"],
|
463
|
+
"error": connection_result["error"]
|
464
|
+
}
|
465
|
+
except Exception as e:
|
466
|
+
details["connection_test"] = {
|
467
|
+
"connected": False,
|
468
|
+
"error": f"Test failed: {str(e)}"
|
469
|
+
}
|
470
|
+
|
189
471
|
# Determine status
|
190
472
|
if not (pipx_installed or accessible):
|
191
473
|
return DiagnosticResult(
|
@@ -200,10 +482,19 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
200
482
|
if pipx_installed and not accessible:
|
201
483
|
# Check if pipx run works
|
202
484
|
if details.get("pipx_run_available"):
|
485
|
+
# Include connection test info if available
|
486
|
+
connection_info = details.get("connection_test", {})
|
487
|
+
if connection_info.get("connected"):
|
488
|
+
message = f"Installed via pipx, connection OK ({connection_info.get('tools_discovered', 0)} tools)"
|
489
|
+
elif connection_info.get("error"):
|
490
|
+
message = f"Installed via pipx, connection failed: {connection_info['error']}"
|
491
|
+
else:
|
492
|
+
message = "Installed via pipx (use 'pipx run' to execute)"
|
493
|
+
|
203
494
|
return DiagnosticResult(
|
204
495
|
category=f"MCP Service: {service_name}",
|
205
|
-
status=DiagnosticStatus.OK,
|
206
|
-
message=
|
496
|
+
status=DiagnosticStatus.OK if connection_info.get("connected") else DiagnosticStatus.WARNING,
|
497
|
+
message=message,
|
207
498
|
details=details,
|
208
499
|
)
|
209
500
|
return DiagnosticResult(
|
@@ -215,10 +506,26 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
215
506
|
fix_description="Ensure pipx bin directory is in PATH",
|
216
507
|
)
|
217
508
|
|
509
|
+
# Service is accessible - check connection test results
|
510
|
+
connection_info = details.get("connection_test", {})
|
511
|
+
if connection_info:
|
512
|
+
if connection_info.get("connected"):
|
513
|
+
response_time = connection_info.get("response_time_ms")
|
514
|
+
tools_count = connection_info.get("tools_discovered", 0)
|
515
|
+
message = f"Installed, accessible, connection OK ({tools_count} tools, {response_time}ms)"
|
516
|
+
status = DiagnosticStatus.OK
|
517
|
+
else:
|
518
|
+
error = connection_info.get("error", "Unknown error")
|
519
|
+
message = f"Installed but connection failed: {error}"
|
520
|
+
status = DiagnosticStatus.WARNING
|
521
|
+
else:
|
522
|
+
message = "Installed and accessible"
|
523
|
+
status = DiagnosticStatus.OK
|
524
|
+
|
218
525
|
return DiagnosticResult(
|
219
526
|
category=f"MCP Service: {service_name}",
|
220
|
-
status=
|
221
|
-
message=
|
527
|
+
status=status,
|
528
|
+
message=message,
|
222
529
|
details=details,
|
223
530
|
)
|
224
531
|
|
@@ -391,6 +698,149 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
391
698
|
|
392
699
|
return None
|
393
700
|
|
701
|
+
def _check_and_fix_kuzu_memory_config(self) -> Optional[DiagnosticResult]:
|
702
|
+
"""Check for incorrect kuzu-memory configuration in .claude.json and offer auto-fix."""
|
703
|
+
claude_config_path = Path.home() / ".claude.json"
|
704
|
+
|
705
|
+
if not claude_config_path.exists():
|
706
|
+
return None
|
707
|
+
|
708
|
+
try:
|
709
|
+
with open(claude_config_path) as f:
|
710
|
+
config = json.load(f)
|
711
|
+
|
712
|
+
mcp_servers = config.get("mcpServers", {})
|
713
|
+
kuzu_config = mcp_servers.get("kuzu-memory")
|
714
|
+
|
715
|
+
if not kuzu_config:
|
716
|
+
return None
|
717
|
+
|
718
|
+
# Check if kuzu-memory has incorrect args
|
719
|
+
args = kuzu_config.get("args", [])
|
720
|
+
needs_fix = False
|
721
|
+
fix_reason = ""
|
722
|
+
# The correct args for kuzu-memory v1.1.0+ are ["mcp", "serve"]
|
723
|
+
correct_args = ["mcp", "serve"]
|
724
|
+
|
725
|
+
# Check for any configuration that is NOT the correct one
|
726
|
+
if args != correct_args:
|
727
|
+
needs_fix = True
|
728
|
+
# Identify the specific issue
|
729
|
+
if args == ["claude", "mcp-server"]:
|
730
|
+
fix_reason = "Outdated 'claude mcp-server' format (pre-v1.1.0)"
|
731
|
+
elif args == ["serve"]:
|
732
|
+
fix_reason = "Legacy 'serve' format"
|
733
|
+
elif args == ["mcp-server"]:
|
734
|
+
fix_reason = "Incorrect 'mcp-server' format"
|
735
|
+
elif args == []:
|
736
|
+
fix_reason = "Empty args list"
|
737
|
+
else:
|
738
|
+
fix_reason = f"Incorrect args format: {args}"
|
739
|
+
|
740
|
+
if needs_fix:
|
741
|
+
# Log the issue for debugging
|
742
|
+
self.logger.warning(
|
743
|
+
f"Found incorrect kuzu-memory configuration: {fix_reason}. "
|
744
|
+
f"Current args: {args}, should be: {correct_args}"
|
745
|
+
)
|
746
|
+
|
747
|
+
# Auto-fix the configuration
|
748
|
+
fixed = self._fix_kuzu_memory_args(claude_config_path, config, correct_args)
|
749
|
+
|
750
|
+
if fixed:
|
751
|
+
return DiagnosticResult(
|
752
|
+
category="kuzu-memory Configuration Fix",
|
753
|
+
status=DiagnosticStatus.OK,
|
754
|
+
message=f"Fixed kuzu-memory configuration to use correct args",
|
755
|
+
details={
|
756
|
+
"old_args": args,
|
757
|
+
"new_args": correct_args,
|
758
|
+
"reason": fix_reason,
|
759
|
+
"auto_fixed": True,
|
760
|
+
},
|
761
|
+
)
|
762
|
+
else:
|
763
|
+
return DiagnosticResult(
|
764
|
+
category="kuzu-memory Configuration",
|
765
|
+
status=DiagnosticStatus.WARNING,
|
766
|
+
message="kuzu-memory has incorrect configuration",
|
767
|
+
details={
|
768
|
+
"current_args": args,
|
769
|
+
"correct_args": correct_args,
|
770
|
+
"reason": fix_reason,
|
771
|
+
"auto_fix_failed": True,
|
772
|
+
},
|
773
|
+
fix_command="claude-mpm configure --mcp --fix-kuzu",
|
774
|
+
fix_description="Fix kuzu-memory configuration manually",
|
775
|
+
)
|
776
|
+
|
777
|
+
# Configuration is correct - args match ["mcp", "serve"]
|
778
|
+
return None
|
779
|
+
|
780
|
+
except (json.JSONDecodeError, Exception) as e:
|
781
|
+
self.logger.debug(f"Could not check kuzu-memory config: {e}")
|
782
|
+
return None
|
783
|
+
|
784
|
+
def _fix_kuzu_memory_args(self, config_path: Path, config: Dict, new_args: List[str]) -> bool:
|
785
|
+
"""Fix kuzu-memory args in the configuration."""
|
786
|
+
try:
|
787
|
+
# Save old args before updating
|
788
|
+
old_args = config["mcpServers"]["kuzu-memory"].get("args", [])
|
789
|
+
|
790
|
+
# Log the exact change we're about to make
|
791
|
+
self.logger.debug(
|
792
|
+
f"Fixing kuzu-memory args: old={old_args}, new={new_args}"
|
793
|
+
)
|
794
|
+
|
795
|
+
# Create backup
|
796
|
+
backup_path = config_path.with_suffix(".json.backup")
|
797
|
+
with open(backup_path, "w") as f:
|
798
|
+
json.dump(config, f, indent=2)
|
799
|
+
|
800
|
+
# Update the configuration - ensure we're setting the exact new_args
|
801
|
+
config["mcpServers"]["kuzu-memory"]["args"] = new_args
|
802
|
+
|
803
|
+
# Verify the update in memory before writing
|
804
|
+
if config["mcpServers"]["kuzu-memory"]["args"] != new_args:
|
805
|
+
self.logger.error(
|
806
|
+
f"Failed to update args in memory! "
|
807
|
+
f"Expected {new_args}, got {config['mcpServers']['kuzu-memory']['args']}"
|
808
|
+
)
|
809
|
+
return False
|
810
|
+
|
811
|
+
# Write updated configuration
|
812
|
+
with open(config_path, "w") as f:
|
813
|
+
json.dump(config, f, indent=2)
|
814
|
+
|
815
|
+
# Verify the file was written correctly
|
816
|
+
with open(config_path) as f:
|
817
|
+
verify_config = json.load(f)
|
818
|
+
verify_args = verify_config.get("mcpServers", {}).get("kuzu-memory", {}).get("args", [])
|
819
|
+
|
820
|
+
if verify_args != new_args:
|
821
|
+
self.logger.error(
|
822
|
+
f"Configuration write verification failed! "
|
823
|
+
f"Expected {new_args}, got {verify_args}"
|
824
|
+
)
|
825
|
+
# Restore backup
|
826
|
+
with open(backup_path) as bf:
|
827
|
+
backup_config = json.load(bf)
|
828
|
+
with open(config_path, "w") as f:
|
829
|
+
json.dump(backup_config, f, indent=2)
|
830
|
+
return False
|
831
|
+
|
832
|
+
self.logger.info(
|
833
|
+
f"✅ Fixed kuzu-memory configuration in {config_path}\n"
|
834
|
+
f" Changed args from {old_args} to {new_args}\n"
|
835
|
+
f" Backup saved to {backup_path}"
|
836
|
+
)
|
837
|
+
|
838
|
+
return True
|
839
|
+
|
840
|
+
except Exception as e:
|
841
|
+
self.logger.error(f"Failed to fix kuzu-memory configuration: {e}")
|
842
|
+
return False
|
843
|
+
|
394
844
|
def _check_gateway_configuration(self) -> DiagnosticResult:
|
395
845
|
"""Check if MCP services are configured in the gateway."""
|
396
846
|
try:
|
@@ -472,3 +922,41 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
472
922
|
message=f"Could not check configuration: {e!s}",
|
473
923
|
details={"error": str(e)},
|
474
924
|
)
|
925
|
+
|
926
|
+
def _ensure_mcp_ticketer_gql_dependency(self) -> bool:
|
927
|
+
"""Ensure mcp-ticketer has the gql dependency injected."""
|
928
|
+
try:
|
929
|
+
# First check if mcp-ticketer can import gql
|
930
|
+
result = subprocess.run(
|
931
|
+
["pipx", "run", "--spec", "mcp-ticketer", "python", "-c", "import gql"],
|
932
|
+
capture_output=True,
|
933
|
+
text=True,
|
934
|
+
timeout=5,
|
935
|
+
check=False,
|
936
|
+
)
|
937
|
+
|
938
|
+
# If import fails, inject the dependency
|
939
|
+
if result.returncode != 0:
|
940
|
+
self.logger.info("🔧 mcp-ticketer missing gql dependency, fixing...")
|
941
|
+
|
942
|
+
inject_result = subprocess.run(
|
943
|
+
["pipx", "inject", "mcp-ticketer", "gql"],
|
944
|
+
capture_output=True,
|
945
|
+
text=True,
|
946
|
+
timeout=30,
|
947
|
+
check=False,
|
948
|
+
)
|
949
|
+
|
950
|
+
if inject_result.returncode == 0:
|
951
|
+
self.logger.info("✅ Successfully injected gql dependency into mcp-ticketer")
|
952
|
+
return True
|
953
|
+
else:
|
954
|
+
self.logger.warning(f"Failed to inject gql dependency: {inject_result.stderr}")
|
955
|
+
return False
|
956
|
+
|
957
|
+
# Dependency already present
|
958
|
+
return False
|
959
|
+
|
960
|
+
except Exception as e:
|
961
|
+
self.logger.debug(f"Could not check/fix mcp-ticketer gql dependency: {e}")
|
962
|
+
return False
|
@@ -214,6 +214,9 @@ class DiagnosticRunner:
|
|
214
214
|
"agents": AgentCheck,
|
215
215
|
"agent": AgentCheck,
|
216
216
|
"mcp": MCPCheck,
|
217
|
+
"mcp_services": MCPServicesCheck,
|
218
|
+
"mcp-services": MCPServicesCheck,
|
219
|
+
"external": MCPServicesCheck,
|
217
220
|
"monitor": MonitorCheck,
|
218
221
|
"monitoring": MonitorCheck,
|
219
222
|
"common": CommonIssuesCheck,
|
@@ -41,6 +41,33 @@ class MCPConfigManager:
|
|
41
41
|
"kuzu-memory",
|
42
42
|
}
|
43
43
|
|
44
|
+
# Static known-good MCP service configurations
|
45
|
+
# These are the correct, tested configurations that work reliably
|
46
|
+
STATIC_MCP_CONFIGS = {
|
47
|
+
"kuzu-memory": {
|
48
|
+
"type": "stdio",
|
49
|
+
"command": "pipx",
|
50
|
+
"args": ["run", "kuzu-memory", "mcp", "serve"]
|
51
|
+
},
|
52
|
+
"mcp-ticketer": {
|
53
|
+
"type": "stdio",
|
54
|
+
"command": "pipx",
|
55
|
+
"args": ["run", "mcp-ticketer", "mcp"]
|
56
|
+
},
|
57
|
+
"mcp-browser": {
|
58
|
+
"type": "stdio",
|
59
|
+
"command": "pipx",
|
60
|
+
"args": ["run", "mcp-browser", "mcp"],
|
61
|
+
"env": {"MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser")}
|
62
|
+
},
|
63
|
+
"mcp-vector-search": {
|
64
|
+
"type": "stdio",
|
65
|
+
"command": "pipx",
|
66
|
+
"args": ["run", "mcp-vector-search", "-m", "mcp_vector_search.mcp.server", "{project_root}"],
|
67
|
+
"env": {}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
44
71
|
def __init__(self):
|
45
72
|
"""Initialize the MCP configuration manager."""
|
46
73
|
self.logger = get_logger(__name__)
|
@@ -195,11 +222,36 @@ class MCPConfigManager:
|
|
195
222
|
|
196
223
|
return None
|
197
224
|
|
225
|
+
def get_static_service_config(self, service_name: str) -> Optional[Dict]:
|
226
|
+
"""
|
227
|
+
Get the static, known-good configuration for an MCP service.
|
228
|
+
|
229
|
+
Args:
|
230
|
+
service_name: Name of the MCP service
|
231
|
+
|
232
|
+
Returns:
|
233
|
+
Static service configuration dict or None if service not known
|
234
|
+
"""
|
235
|
+
if service_name not in self.STATIC_MCP_CONFIGS:
|
236
|
+
return None
|
237
|
+
|
238
|
+
config = self.STATIC_MCP_CONFIGS[service_name].copy()
|
239
|
+
|
240
|
+
# Special handling for mcp-vector-search: replace {project_root} placeholder
|
241
|
+
if service_name == "mcp-vector-search":
|
242
|
+
config["args"] = [
|
243
|
+
arg.replace("{project_root}", str(self.project_root)) if "{project_root}" in arg else arg
|
244
|
+
for arg in config["args"]
|
245
|
+
]
|
246
|
+
|
247
|
+
return config
|
248
|
+
|
198
249
|
def generate_service_config(self, service_name: str) -> Optional[Dict]:
|
199
250
|
"""
|
200
251
|
Generate configuration for a specific MCP service.
|
201
252
|
|
202
|
-
Prefers
|
253
|
+
Prefers static configurations over detection. Falls back to detection
|
254
|
+
only for unknown services.
|
203
255
|
|
204
256
|
Args:
|
205
257
|
service_name: Name of the MCP service
|
@@ -207,6 +259,12 @@ class MCPConfigManager:
|
|
207
259
|
Returns:
|
208
260
|
Service configuration dict or None if service not found
|
209
261
|
"""
|
262
|
+
# First try to get static configuration
|
263
|
+
static_config = self.get_static_service_config(service_name)
|
264
|
+
if static_config:
|
265
|
+
return static_config
|
266
|
+
|
267
|
+
# Fall back to detection-based configuration for unknown services
|
210
268
|
import shutil
|
211
269
|
|
212
270
|
# Check for pipx run first (preferred for isolation)
|
@@ -308,7 +366,7 @@ class MCPConfigManager:
|
|
308
366
|
|
309
367
|
elif service_name == "kuzu-memory":
|
310
368
|
# Determine kuzu-memory command version
|
311
|
-
kuzu_args = ["mcp", "serve"] # Default to the
|
369
|
+
kuzu_args = ["mcp", "serve"] # Default to the standard v1.1.0+ format
|
312
370
|
test_cmd = None
|
313
371
|
|
314
372
|
if use_pipx_run:
|
@@ -330,20 +388,21 @@ class MCPConfigManager:
|
|
330
388
|
# Check for MCP support in help output
|
331
389
|
help_output = result.stdout.lower() + result.stderr.lower()
|
332
390
|
|
333
|
-
#
|
391
|
+
# Standard version detection - look for "mcp serve" command (v1.1.0+)
|
392
|
+
# This is the correct format for kuzu-memory v1.1.0 and later
|
334
393
|
if "mcp serve" in help_output or ("mcp" in help_output and "serve" in help_output):
|
335
|
-
#
|
394
|
+
# Standard v1.1.0+ version with mcp serve command
|
336
395
|
kuzu_args = ["mcp", "serve"]
|
337
396
|
# Legacy version detection - only "serve" without "mcp"
|
338
397
|
elif "serve" in help_output and "mcp" not in help_output:
|
339
398
|
# Very old version that only has serve command
|
340
399
|
kuzu_args = ["serve"]
|
341
|
-
# Note: "claude mcp-server" format is deprecated and not used
|
342
400
|
else:
|
343
|
-
# Default to the
|
401
|
+
# Default to the standard mcp serve format (v1.1.0+)
|
402
|
+
# Note: "claude mcp-server" format is deprecated and does not work
|
344
403
|
kuzu_args = ["mcp", "serve"]
|
345
404
|
except Exception:
|
346
|
-
# Default to the
|
405
|
+
# Default to the standard mcp serve command on any error
|
347
406
|
kuzu_args = ["mcp", "serve"]
|
348
407
|
|
349
408
|
if use_pipx_run:
|
@@ -371,17 +430,20 @@ class MCPConfigManager:
|
|
371
430
|
|
372
431
|
def ensure_mcp_services_configured(self) -> Tuple[bool, str]:
|
373
432
|
"""
|
374
|
-
Ensure MCP services are configured in ~/.claude.json on startup.
|
433
|
+
Ensure MCP services are configured correctly in ~/.claude.json on startup.
|
375
434
|
|
376
|
-
This method checks
|
377
|
-
|
435
|
+
This method checks ALL projects in ~/.claude.json and ensures each has
|
436
|
+
the correct, static MCP service configurations. It will:
|
437
|
+
1. Add missing services
|
438
|
+
2. Fix incorrect configurations
|
439
|
+
3. Update all projects, not just the current one
|
378
440
|
|
379
441
|
Returns:
|
380
442
|
Tuple of (success, message)
|
381
443
|
"""
|
382
444
|
updated = False
|
445
|
+
fixed_services = []
|
383
446
|
added_services = []
|
384
|
-
project_key = str(self.project_root)
|
385
447
|
|
386
448
|
# Load existing Claude config or create minimal structure
|
387
449
|
claude_config = {}
|
@@ -396,9 +458,23 @@ class MCPConfigManager:
|
|
396
458
|
# Ensure projects structure exists
|
397
459
|
if "projects" not in claude_config:
|
398
460
|
claude_config["projects"] = {}
|
461
|
+
updated = True
|
399
462
|
|
400
|
-
|
401
|
-
|
463
|
+
# Check and fix mcp-ticketer dependencies ONCE before processing projects
|
464
|
+
# This avoids running the same pipx inject command 100+ times
|
465
|
+
mcp_ticketer_fixed = False
|
466
|
+
if "mcp-ticketer" in self.PIPX_SERVICES:
|
467
|
+
mcp_ticketer_fixed = self._check_and_fix_mcp_ticketer_dependencies()
|
468
|
+
|
469
|
+
# Process ALL projects in the config, not just current one
|
470
|
+
projects_to_update = list(claude_config.get("projects", {}).keys())
|
471
|
+
|
472
|
+
# Also add the current project if not in list
|
473
|
+
current_project_key = str(self.project_root)
|
474
|
+
if current_project_key not in projects_to_update:
|
475
|
+
projects_to_update.append(current_project_key)
|
476
|
+
# Initialize new project structure
|
477
|
+
claude_config["projects"][current_project_key] = {
|
402
478
|
"allowedTools": [],
|
403
479
|
"history": [],
|
404
480
|
"mcpContextUris": [],
|
@@ -412,29 +488,51 @@ class MCPConfigManager:
|
|
412
488
|
}
|
413
489
|
updated = True
|
414
490
|
|
415
|
-
#
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
491
|
+
# Update each project's MCP configurations
|
492
|
+
for project_key in projects_to_update:
|
493
|
+
project_config = claude_config["projects"][project_key]
|
494
|
+
|
495
|
+
# Ensure mcpServers section exists
|
496
|
+
if "mcpServers" not in project_config:
|
497
|
+
project_config["mcpServers"] = {}
|
498
|
+
updated = True
|
499
|
+
|
500
|
+
# Check and fix each service configuration
|
501
|
+
for service_name in self.PIPX_SERVICES:
|
502
|
+
# Get the correct static configuration
|
503
|
+
correct_config = self.STATIC_MCP_CONFIGS[service_name].copy()
|
504
|
+
|
505
|
+
# Special handling for mcp-vector-search: replace {project_root} placeholder
|
506
|
+
if service_name == "mcp-vector-search":
|
507
|
+
# Use the project key as the project root for each project
|
508
|
+
correct_config["args"] = [
|
509
|
+
arg.replace("{project_root}", project_key) if "{project_root}" in arg else arg
|
510
|
+
for arg in correct_config["args"]
|
511
|
+
]
|
512
|
+
|
513
|
+
# Check if service exists and has correct configuration
|
514
|
+
existing_config = project_config["mcpServers"].get(service_name)
|
515
|
+
|
516
|
+
# Determine if we need to update
|
517
|
+
needs_update = False
|
518
|
+
if not existing_config:
|
519
|
+
# Service is missing
|
520
|
+
needs_update = True
|
521
|
+
added_services.append(f"{service_name} in {Path(project_key).name}")
|
435
522
|
else:
|
523
|
+
# Service exists, check if configuration is correct
|
524
|
+
# Compare command and args (the most critical parts)
|
525
|
+
if (existing_config.get("command") != correct_config.get("command") or
|
526
|
+
existing_config.get("args") != correct_config.get("args")):
|
527
|
+
needs_update = True
|
528
|
+
fixed_services.append(f"{service_name} in {Path(project_key).name}")
|
529
|
+
|
530
|
+
# Update configuration if needed
|
531
|
+
if needs_update:
|
532
|
+
project_config["mcpServers"][service_name] = correct_config
|
533
|
+
updated = True
|
436
534
|
self.logger.debug(
|
437
|
-
f"MCP service {service_name}
|
535
|
+
f"Updated MCP service config for {service_name} in project {Path(project_key).name}"
|
438
536
|
)
|
439
537
|
|
440
538
|
# Write updated config if changes were made
|
@@ -448,7 +546,6 @@ class MCPConfigManager:
|
|
448
546
|
f".backup.{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}.json"
|
449
547
|
)
|
450
548
|
import shutil
|
451
|
-
|
452
549
|
shutil.copy2(self.claude_config_path, backup_path)
|
453
550
|
self.logger.debug(f"Created backup: {backup_path}")
|
454
551
|
|
@@ -456,18 +553,20 @@ class MCPConfigManager:
|
|
456
553
|
with open(self.claude_config_path, "w") as f:
|
457
554
|
json.dump(claude_config, f, indent=2)
|
458
555
|
|
556
|
+
messages = []
|
459
557
|
if added_services:
|
460
|
-
|
461
|
-
|
462
|
-
)
|
463
|
-
|
464
|
-
|
465
|
-
|
558
|
+
messages.append(f"Added MCP services: {', '.join(added_services[:3])}")
|
559
|
+
if fixed_services:
|
560
|
+
messages.append(f"Fixed MCP services: {', '.join(fixed_services[:3])}")
|
561
|
+
|
562
|
+
if messages:
|
563
|
+
return True, "; ".join(messages)
|
564
|
+
return True, "All MCP services already configured correctly"
|
466
565
|
except Exception as e:
|
467
566
|
self.logger.error(f"Failed to write Claude config: {e}")
|
468
567
|
return False, f"Failed to write configuration: {e}"
|
469
568
|
|
470
|
-
return True, "All MCP services already configured"
|
569
|
+
return True, "All MCP services already configured correctly"
|
471
570
|
|
472
571
|
def update_mcp_config(self, force_pipx: bool = True) -> Tuple[bool, str]:
|
473
572
|
"""
|
@@ -695,6 +794,49 @@ class MCPConfigManager:
|
|
695
794
|
|
696
795
|
return False, "none"
|
697
796
|
|
797
|
+
def _check_and_fix_mcp_ticketer_dependencies(self) -> bool:
|
798
|
+
"""Check and fix mcp-ticketer missing gql dependency.
|
799
|
+
|
800
|
+
Note: This is a workaround for mcp-ticketer <= 0.1.8 which is missing
|
801
|
+
the gql dependency in its package metadata. Future versions (> 0.1.8)
|
802
|
+
should include 'gql[httpx]>=3.0.0' as a dependency, making this fix
|
803
|
+
unnecessary. We keep this for backward compatibility with older versions.
|
804
|
+
"""
|
805
|
+
try:
|
806
|
+
# Test if gql is available in mcp-ticketer's environment
|
807
|
+
test_result = subprocess.run(
|
808
|
+
["pipx", "run", "--spec", "mcp-ticketer", "python", "-c", "import gql"],
|
809
|
+
capture_output=True,
|
810
|
+
text=True,
|
811
|
+
timeout=5,
|
812
|
+
check=False,
|
813
|
+
)
|
814
|
+
|
815
|
+
# If import fails, inject the dependency
|
816
|
+
if test_result.returncode != 0:
|
817
|
+
self.logger.info("🔧 mcp-ticketer missing gql dependency, fixing...")
|
818
|
+
|
819
|
+
inject_result = subprocess.run(
|
820
|
+
["pipx", "inject", "mcp-ticketer", "gql"],
|
821
|
+
capture_output=True,
|
822
|
+
text=True,
|
823
|
+
timeout=30,
|
824
|
+
check=False,
|
825
|
+
)
|
826
|
+
|
827
|
+
if inject_result.returncode == 0:
|
828
|
+
self.logger.info("✅ Successfully injected gql dependency into mcp-ticketer")
|
829
|
+
return True
|
830
|
+
else:
|
831
|
+
self.logger.warning(f"Failed to inject gql: {inject_result.stderr}")
|
832
|
+
return False
|
833
|
+
|
834
|
+
return False
|
835
|
+
|
836
|
+
except Exception as e:
|
837
|
+
self.logger.debug(f"Could not check/fix mcp-ticketer dependencies: {e}")
|
838
|
+
return False
|
839
|
+
|
698
840
|
def _verify_service_installed(self, service_name: str, method: str) -> bool:
|
699
841
|
"""
|
700
842
|
Verify that a service was successfully installed and is functional.
|
@@ -711,6 +853,9 @@ class MCPConfigManager:
|
|
711
853
|
# Give the installation a moment to settle
|
712
854
|
time.sleep(1)
|
713
855
|
|
856
|
+
# Note: mcp-ticketer dependency fix is now handled once in ensure_mcp_services_configured()
|
857
|
+
# to avoid running the same pipx inject command multiple times
|
858
|
+
|
714
859
|
# Check if we can find the service
|
715
860
|
service_path = self.detect_service_path(service_name)
|
716
861
|
if not service_path:
|
@@ -1,5 +1,5 @@
|
|
1
1
|
claude_mpm/BUILD_NUMBER,sha256=toytnNjkIKPgQaGwDqQdC1rpNTAdSEc6Vja50d7Ovug,4
|
2
|
-
claude_mpm/VERSION,sha256=
|
2
|
+
claude_mpm/VERSION,sha256=k8-5xMoy2UlbzZa4MVGVy1j0Ym4rIg_47OvMJE5eNKg,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
|
@@ -411,7 +411,7 @@ claude_mpm/services/event_aggregator.py,sha256=DDcehIZVpiEDzs9o18gDZyvjMBHCq2H8H
|
|
411
411
|
claude_mpm/services/exceptions.py,sha256=5lVZETr_6-xk0ItH7BTfYUiX5RlckS1e8ah_UalYG9c,26475
|
412
412
|
claude_mpm/services/hook_installer_service.py,sha256=z3kKeriEY1Y9bFesuGlHBxhCtc0Wzd3Zv02k2_rEyGo,19727
|
413
413
|
claude_mpm/services/hook_service.py,sha256=rZnMn_4qxX5g9KAn0IQdoG50WmySNfsTmfG0XHuRHXk,15737
|
414
|
-
claude_mpm/services/mcp_config_manager.py,sha256=
|
414
|
+
claude_mpm/services/mcp_config_manager.py,sha256=iEBnl9rPFR-PccOol8ZdufyCvhZvVN47wYtTteQVpBY,35112
|
415
415
|
claude_mpm/services/mcp_service_verifier.py,sha256=ngiegCngX18AFehfyJdvqQAvscoIBvFN_DeOoGTjxj0,25164
|
416
416
|
claude_mpm/services/memory_hook_service.py,sha256=pRlTClkRcw30Jhwbha4BC8IMdzKZxF8aWqf52JlntgY,11600
|
417
417
|
claude_mpm/services/monitor_build_service.py,sha256=8gWR9CaqgXdG6-OjOFXGpk28GCcJTlHhojkUYnMCebI,12160
|
@@ -547,7 +547,7 @@ claude_mpm/services/core/interfaces/communication.py,sha256=evwtLbYCFa3Zb8kEfL10
|
|
547
547
|
claude_mpm/services/core/interfaces/infrastructure.py,sha256=eLtr_dFhA3Ux3mPOV_4DbWhGjHpfpGnj6xOhfQcgZGk,10037
|
548
548
|
claude_mpm/services/core/interfaces/service.py,sha256=hNfHXe45LcPCp_dToOmZCfnUZBF5axMf_TdxqCSm2-I,11536
|
549
549
|
claude_mpm/services/diagnostics/__init__.py,sha256=WTRucANR9EwNi53rotjkeE4k75s18RjHJ8s1BfBj7ic,614
|
550
|
-
claude_mpm/services/diagnostics/diagnostic_runner.py,sha256=
|
550
|
+
claude_mpm/services/diagnostics/diagnostic_runner.py,sha256=PJje2C3_20JAeDBf2LpZ_rfi3rwSWk73EXJmZEkDcvs,9470
|
551
551
|
claude_mpm/services/diagnostics/doctor_reporter.py,sha256=WhlHBWy-KI8OhAWujOu77VAgSvkBvtF3sDrjacYZhvg,19649
|
552
552
|
claude_mpm/services/diagnostics/models.py,sha256=nqOQLllZyZmw3Zt5eFJfE1Al7C3Vrn3REgFlARtT3jQ,3831
|
553
553
|
claude_mpm/services/diagnostics/checks/__init__.py,sha256=aNdOeJHZVIpEqqzr6xWUOiyZCIrN4vckfRxkW70cqeo,987
|
@@ -560,7 +560,7 @@ claude_mpm/services/diagnostics/checks/filesystem_check.py,sha256=V5HoHDYlSuoK2l
|
|
560
560
|
claude_mpm/services/diagnostics/checks/installation_check.py,sha256=WoTt15R8Wg-6k2JZFAtmffFuih1AIyCX71QOHEFH-Ro,19562
|
561
561
|
claude_mpm/services/diagnostics/checks/instructions_check.py,sha256=VbgBorl0RpFvxKQ_SC1gibTmGSiXaKSp-vVZt6hbH1g,16290
|
562
562
|
claude_mpm/services/diagnostics/checks/mcp_check.py,sha256=SftuhP70abopyMD8GlLA_K3XHEYnBAeITggUQI0cYP4,12173
|
563
|
-
claude_mpm/services/diagnostics/checks/mcp_services_check.py,sha256=
|
563
|
+
claude_mpm/services/diagnostics/checks/mcp_services_check.py,sha256=SrHdpRbEc62e7mkfC_m8aGFtQIkhNOmiiCQZ78MME98,40800
|
564
564
|
claude_mpm/services/diagnostics/checks/monitor_check.py,sha256=NUx5G1yjHWlukZmwhUz4o8STRWgsQEx01YjIMReNC0A,10096
|
565
565
|
claude_mpm/services/diagnostics/checks/startup_log_check.py,sha256=DrXdml2rHvmhFBdb_sntE3xmwaP_DZIKjdVbCn8Dy7E,12258
|
566
566
|
claude_mpm/services/event_bus/__init__.py,sha256=ETCo4a6puIeyVWAv55uCDjjhzNyUwbVAHEcAVkVapx8,688
|
@@ -773,9 +773,9 @@ claude_mpm/utils/subprocess_utils.py,sha256=D0izRT8anjiUb_JG72zlJR_JAw1cDkb7kalN
|
|
773
773
|
claude_mpm/validation/__init__.py,sha256=YZhwE3mhit-lslvRLuwfX82xJ_k4haZeKmh4IWaVwtk,156
|
774
774
|
claude_mpm/validation/agent_validator.py,sha256=Nm2WmcbCb0EwOG4nFcikc3wVdiiAfjGBBI3YoR6ainQ,20915
|
775
775
|
claude_mpm/validation/frontmatter_validator.py,sha256=IDBOCBweO6umydSnUJjBh81sKk3cy9hRFYm61DCiXbI,7020
|
776
|
-
claude_mpm-4.4.
|
777
|
-
claude_mpm-4.4.
|
778
|
-
claude_mpm-4.4.
|
779
|
-
claude_mpm-4.4.
|
780
|
-
claude_mpm-4.4.
|
781
|
-
claude_mpm-4.4.
|
776
|
+
claude_mpm-4.4.9.dist-info/licenses/LICENSE,sha256=lpaivOlPuBZW1ds05uQLJJswy8Rp_HMNieJEbFlqvLk,1072
|
777
|
+
claude_mpm-4.4.9.dist-info/METADATA,sha256=RSN3ddmTCsiu9uTUoaSFYFeB7O56BR7awyIOpLkSVsA,17517
|
778
|
+
claude_mpm-4.4.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
779
|
+
claude_mpm-4.4.9.dist-info/entry_points.txt,sha256=FDPZgz8JOvD-6iuXY2l9Zbo9zYVRuE4uz4Qr0vLeGOk,471
|
780
|
+
claude_mpm-4.4.9.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
|
781
|
+
claude_mpm-4.4.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|