claude-mpm 4.0.29__py3-none-any.whl → 4.0.30__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/agents/BASE_AGENT_TEMPLATE.md +48 -3
- claude_mpm/agents/BASE_PM.md +20 -15
- claude_mpm/agents/INSTRUCTIONS.md +12 -2
- claude_mpm/agents/templates/documentation.json +16 -3
- claude_mpm/agents/templates/engineer.json +19 -5
- claude_mpm/agents/templates/ops.json +19 -5
- claude_mpm/agents/templates/qa.json +16 -3
- claude_mpm/agents/templates/refactoring_engineer.json +25 -7
- claude_mpm/agents/templates/research.json +19 -5
- claude_mpm/cli/__init__.py +2 -0
- claude_mpm/cli/commands/__init__.py +2 -0
- claude_mpm/cli/commands/agent_manager.py +10 -6
- claude_mpm/cli/commands/agents.py +2 -1
- claude_mpm/cli/commands/cleanup.py +1 -1
- claude_mpm/cli/commands/doctor.py +209 -0
- claude_mpm/cli/commands/mcp.py +3 -3
- claude_mpm/cli/commands/mcp_install_commands.py +12 -30
- claude_mpm/cli/commands/mcp_server_commands.py +9 -9
- claude_mpm/cli/commands/run.py +31 -2
- claude_mpm/cli/commands/run_config_checker.py +1 -1
- claude_mpm/cli/parsers/agent_manager_parser.py +3 -3
- claude_mpm/cli/parsers/base_parser.py +5 -1
- claude_mpm/cli/parsers/mcp_parser.py +1 -1
- claude_mpm/cli/parsers/run_parser.py +1 -1
- claude_mpm/cli/startup_logging.py +463 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +78 -0
- claude_mpm/core/framework_loader.py +45 -11
- claude_mpm/core/interactive_session.py +82 -3
- claude_mpm/core/output_style_manager.py +6 -6
- claude_mpm/core/unified_paths.py +128 -0
- claude_mpm/scripts/mcp_server.py +2 -2
- claude_mpm/services/agents/deployment/agent_validator.py +1 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +69 -1
- claude_mpm/services/diagnostics/__init__.py +18 -0
- claude_mpm/services/diagnostics/checks/__init__.py +30 -0
- claude_mpm/services/diagnostics/checks/agent_check.py +319 -0
- claude_mpm/services/diagnostics/checks/base_check.py +64 -0
- claude_mpm/services/diagnostics/checks/claude_desktop_check.py +283 -0
- claude_mpm/services/diagnostics/checks/common_issues_check.py +354 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +300 -0
- claude_mpm/services/diagnostics/checks/filesystem_check.py +233 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +255 -0
- claude_mpm/services/diagnostics/checks/mcp_check.py +315 -0
- claude_mpm/services/diagnostics/checks/monitor_check.py +282 -0
- claude_mpm/services/diagnostics/checks/startup_log_check.py +322 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +247 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +283 -0
- claude_mpm/services/diagnostics/models.py +120 -0
- claude_mpm/services/mcp_gateway/core/interfaces.py +1 -1
- claude_mpm/services/mcp_gateway/main.py +1 -1
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +3 -3
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
- claude_mpm/services/mcp_gateway/server/stdio_server.py +3 -3
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +2 -2
- claude_mpm/services/socketio/handlers/registry.py +39 -7
- claude_mpm/services/socketio/server/core.py +72 -22
- claude_mpm/validation/frontmatter_validator.py +1 -1
- {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.30.dist-info}/METADATA +4 -1
- {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.30.dist-info}/RECORD +64 -47
- {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.30.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.30.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.30.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.30.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Check MCP (Model Context Protocol) server status.
|
|
3
|
+
|
|
4
|
+
WHY: Verify that the MCP gateway is properly installed, configured,
|
|
5
|
+
and functioning for enhanced Claude Desktop capabilities.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import subprocess
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, Any
|
|
12
|
+
|
|
13
|
+
from ..models import DiagnosticResult, DiagnosticStatus
|
|
14
|
+
from .base_check import BaseDiagnosticCheck
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MCPCheck(BaseDiagnosticCheck):
|
|
18
|
+
"""Check MCP server installation and configuration."""
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def name(self) -> str:
|
|
22
|
+
return "mcp_check"
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def category(self) -> str:
|
|
26
|
+
return "MCP Server"
|
|
27
|
+
|
|
28
|
+
def run(self) -> DiagnosticResult:
|
|
29
|
+
"""Run MCP diagnostics."""
|
|
30
|
+
try:
|
|
31
|
+
from ....services.mcp_gateway.core.startup_verification import MCPGatewayStartupVerifier
|
|
32
|
+
|
|
33
|
+
sub_results = []
|
|
34
|
+
details = {}
|
|
35
|
+
|
|
36
|
+
# Check if MCP is installed
|
|
37
|
+
install_result = self._check_installation()
|
|
38
|
+
sub_results.append(install_result)
|
|
39
|
+
details["installed"] = install_result.status == DiagnosticStatus.OK
|
|
40
|
+
|
|
41
|
+
if install_result.status == DiagnosticStatus.OK:
|
|
42
|
+
# Check MCP configuration
|
|
43
|
+
config_result = self._check_configuration()
|
|
44
|
+
sub_results.append(config_result)
|
|
45
|
+
details["configured"] = config_result.status == DiagnosticStatus.OK
|
|
46
|
+
|
|
47
|
+
# Check MCP server status
|
|
48
|
+
status_result = self._check_server_status()
|
|
49
|
+
sub_results.append(status_result)
|
|
50
|
+
details["running"] = status_result.details.get("running", False)
|
|
51
|
+
|
|
52
|
+
# Verify startup
|
|
53
|
+
startup_result = self._check_startup_verification()
|
|
54
|
+
sub_results.append(startup_result)
|
|
55
|
+
|
|
56
|
+
# Determine overall status
|
|
57
|
+
if any(r.status == DiagnosticStatus.ERROR for r in sub_results):
|
|
58
|
+
status = DiagnosticStatus.ERROR
|
|
59
|
+
message = "MCP server has critical issues"
|
|
60
|
+
elif not details.get("installed", False):
|
|
61
|
+
status = DiagnosticStatus.WARNING
|
|
62
|
+
message = "MCP server not installed"
|
|
63
|
+
elif any(r.status == DiagnosticStatus.WARNING for r in sub_results):
|
|
64
|
+
status = DiagnosticStatus.WARNING
|
|
65
|
+
message = "MCP server needs configuration"
|
|
66
|
+
else:
|
|
67
|
+
status = DiagnosticStatus.OK
|
|
68
|
+
message = "MCP server properly configured"
|
|
69
|
+
|
|
70
|
+
return DiagnosticResult(
|
|
71
|
+
category=self.category,
|
|
72
|
+
status=status,
|
|
73
|
+
message=message,
|
|
74
|
+
details=details,
|
|
75
|
+
sub_results=sub_results if self.verbose else []
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
return DiagnosticResult(
|
|
80
|
+
category=self.category,
|
|
81
|
+
status=DiagnosticStatus.ERROR,
|
|
82
|
+
message=f"MCP check failed: {str(e)}",
|
|
83
|
+
details={"error": str(e)}
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def _check_installation(self) -> DiagnosticResult:
|
|
87
|
+
"""Check if MCP server is installed."""
|
|
88
|
+
# Check for MCP binary
|
|
89
|
+
mcp_paths = [
|
|
90
|
+
Path("/usr/local/bin/claude-mpm-mcp"),
|
|
91
|
+
Path.home() / ".local/bin/claude-mpm-mcp",
|
|
92
|
+
Path("/opt/claude-mpm/bin/claude-mpm-mcp")
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
for mcp_path in mcp_paths:
|
|
96
|
+
if mcp_path.exists():
|
|
97
|
+
return DiagnosticResult(
|
|
98
|
+
category="MCP Installation",
|
|
99
|
+
status=DiagnosticStatus.OK,
|
|
100
|
+
message="MCP server installed",
|
|
101
|
+
details={"path": str(mcp_path), "installed": True}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Check if it's available via command
|
|
105
|
+
try:
|
|
106
|
+
result = subprocess.run(
|
|
107
|
+
["which", "claude-mpm-mcp"],
|
|
108
|
+
capture_output=True,
|
|
109
|
+
text=True,
|
|
110
|
+
timeout=2
|
|
111
|
+
)
|
|
112
|
+
if result.returncode == 0:
|
|
113
|
+
path = result.stdout.strip()
|
|
114
|
+
return DiagnosticResult(
|
|
115
|
+
category="MCP Installation",
|
|
116
|
+
status=DiagnosticStatus.OK,
|
|
117
|
+
message="MCP server installed",
|
|
118
|
+
details={"path": path, "installed": True}
|
|
119
|
+
)
|
|
120
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
return DiagnosticResult(
|
|
124
|
+
category="MCP Installation",
|
|
125
|
+
status=DiagnosticStatus.WARNING,
|
|
126
|
+
message="MCP server not installed",
|
|
127
|
+
details={"installed": False},
|
|
128
|
+
fix_command="claude-mpm mcp install",
|
|
129
|
+
fix_description="Install MCP server for enhanced capabilities"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def _check_configuration(self) -> DiagnosticResult:
|
|
133
|
+
"""Check MCP configuration in Claude Desktop."""
|
|
134
|
+
config_paths = [
|
|
135
|
+
Path.home() / ".config/claude/claude_desktop_config.json",
|
|
136
|
+
Path.home() / "Library/Application Support/Claude/claude_desktop_config.json",
|
|
137
|
+
Path.home() / "AppData/Roaming/Claude/claude_desktop_config.json"
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
config_path = None
|
|
141
|
+
for path in config_paths:
|
|
142
|
+
if path.exists():
|
|
143
|
+
config_path = path
|
|
144
|
+
break
|
|
145
|
+
|
|
146
|
+
if not config_path:
|
|
147
|
+
return DiagnosticResult(
|
|
148
|
+
category="MCP Configuration",
|
|
149
|
+
status=DiagnosticStatus.WARNING,
|
|
150
|
+
message="Claude Desktop config not found",
|
|
151
|
+
details={"configured": False},
|
|
152
|
+
fix_command="claude-mpm mcp config",
|
|
153
|
+
fix_description="Configure MCP server in Claude Desktop"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
with open(config_path, 'r') as f:
|
|
158
|
+
config = json.load(f)
|
|
159
|
+
|
|
160
|
+
mcp_servers = config.get("mcpServers", {})
|
|
161
|
+
gateway = mcp_servers.get("claude-mpm-gateway", {})
|
|
162
|
+
|
|
163
|
+
if not gateway:
|
|
164
|
+
return DiagnosticResult(
|
|
165
|
+
category="MCP Configuration",
|
|
166
|
+
status=DiagnosticStatus.WARNING,
|
|
167
|
+
message="MCP gateway not configured",
|
|
168
|
+
details={"configured": False, "config_path": str(config_path)},
|
|
169
|
+
fix_command="claude-mpm mcp config",
|
|
170
|
+
fix_description="Add MCP gateway to Claude Desktop configuration"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Check configuration validity
|
|
174
|
+
command = gateway.get("command")
|
|
175
|
+
if not command:
|
|
176
|
+
return DiagnosticResult(
|
|
177
|
+
category="MCP Configuration",
|
|
178
|
+
status=DiagnosticStatus.ERROR,
|
|
179
|
+
message="MCP gateway misconfigured (no command)",
|
|
180
|
+
details={
|
|
181
|
+
"configured": True,
|
|
182
|
+
"valid": False,
|
|
183
|
+
"config_path": str(config_path)
|
|
184
|
+
},
|
|
185
|
+
fix_command="claude-mpm mcp config --force",
|
|
186
|
+
fix_description="Fix MCP gateway configuration"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return DiagnosticResult(
|
|
190
|
+
category="MCP Configuration",
|
|
191
|
+
status=DiagnosticStatus.OK,
|
|
192
|
+
message="MCP gateway configured",
|
|
193
|
+
details={
|
|
194
|
+
"configured": True,
|
|
195
|
+
"command": command,
|
|
196
|
+
"config_path": str(config_path)
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
except json.JSONDecodeError as e:
|
|
201
|
+
return DiagnosticResult(
|
|
202
|
+
category="MCP Configuration",
|
|
203
|
+
status=DiagnosticStatus.ERROR,
|
|
204
|
+
message="Invalid JSON in config file",
|
|
205
|
+
details={"error": str(e), "config_path": str(config_path)},
|
|
206
|
+
fix_description="Fix JSON syntax in Claude Desktop config"
|
|
207
|
+
)
|
|
208
|
+
except Exception as e:
|
|
209
|
+
return DiagnosticResult(
|
|
210
|
+
category="MCP Configuration",
|
|
211
|
+
status=DiagnosticStatus.WARNING,
|
|
212
|
+
message=f"Could not check configuration: {str(e)}",
|
|
213
|
+
details={"error": str(e)}
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def _check_server_status(self) -> DiagnosticResult:
|
|
217
|
+
"""Check if MCP server is running."""
|
|
218
|
+
try:
|
|
219
|
+
# Try to connect to the MCP server
|
|
220
|
+
result = subprocess.run(
|
|
221
|
+
["claude-mpm", "mcp", "status"],
|
|
222
|
+
capture_output=True,
|
|
223
|
+
text=True,
|
|
224
|
+
timeout=5
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
if result.returncode == 0:
|
|
228
|
+
if "running" in result.stdout.lower():
|
|
229
|
+
return DiagnosticResult(
|
|
230
|
+
category="MCP Server Status",
|
|
231
|
+
status=DiagnosticStatus.OK,
|
|
232
|
+
message="MCP server is running",
|
|
233
|
+
details={"running": True}
|
|
234
|
+
)
|
|
235
|
+
else:
|
|
236
|
+
return DiagnosticResult(
|
|
237
|
+
category="MCP Server Status",
|
|
238
|
+
status=DiagnosticStatus.WARNING,
|
|
239
|
+
message="MCP server not running",
|
|
240
|
+
details={"running": False},
|
|
241
|
+
fix_command="claude-mpm mcp start",
|
|
242
|
+
fix_description="Start the MCP server"
|
|
243
|
+
)
|
|
244
|
+
else:
|
|
245
|
+
return DiagnosticResult(
|
|
246
|
+
category="MCP Server Status",
|
|
247
|
+
status=DiagnosticStatus.WARNING,
|
|
248
|
+
message="Could not determine server status",
|
|
249
|
+
details={"running": "unknown", "error": result.stderr}
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
except subprocess.TimeoutExpired:
|
|
253
|
+
return DiagnosticResult(
|
|
254
|
+
category="MCP Server Status",
|
|
255
|
+
status=DiagnosticStatus.WARNING,
|
|
256
|
+
message="Server status check timed out",
|
|
257
|
+
details={"running": "unknown", "error": "timeout"}
|
|
258
|
+
)
|
|
259
|
+
except Exception as e:
|
|
260
|
+
return DiagnosticResult(
|
|
261
|
+
category="MCP Server Status",
|
|
262
|
+
status=DiagnosticStatus.WARNING,
|
|
263
|
+
message=f"Could not check server status: {str(e)}",
|
|
264
|
+
details={"running": "unknown", "error": str(e)}
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
def _check_startup_verification(self) -> DiagnosticResult:
|
|
268
|
+
"""Run MCP startup verification."""
|
|
269
|
+
try:
|
|
270
|
+
from ....services.mcp_gateway.core.startup_verification import MCPGatewayStartupVerifier
|
|
271
|
+
|
|
272
|
+
verifier = MCPGatewayStartupVerifier()
|
|
273
|
+
issues = verifier.verify_startup()
|
|
274
|
+
|
|
275
|
+
if not issues:
|
|
276
|
+
return DiagnosticResult(
|
|
277
|
+
category="MCP Startup Verification",
|
|
278
|
+
status=DiagnosticStatus.OK,
|
|
279
|
+
message="Startup verification passed",
|
|
280
|
+
details={"issues": []}
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# Categorize issues by severity
|
|
284
|
+
errors = [i for i in issues if "error" in i.lower() or "critical" in i.lower()]
|
|
285
|
+
warnings = [i for i in issues if i not in errors]
|
|
286
|
+
|
|
287
|
+
if errors:
|
|
288
|
+
return DiagnosticResult(
|
|
289
|
+
category="MCP Startup Verification",
|
|
290
|
+
status=DiagnosticStatus.ERROR,
|
|
291
|
+
message=f"{len(errors)} critical issue(s) found",
|
|
292
|
+
details={"errors": errors, "warnings": warnings}
|
|
293
|
+
)
|
|
294
|
+
elif warnings:
|
|
295
|
+
return DiagnosticResult(
|
|
296
|
+
category="MCP Startup Verification",
|
|
297
|
+
status=DiagnosticStatus.WARNING,
|
|
298
|
+
message=f"{len(warnings)} warning(s) found",
|
|
299
|
+
details={"warnings": warnings}
|
|
300
|
+
)
|
|
301
|
+
else:
|
|
302
|
+
return DiagnosticResult(
|
|
303
|
+
category="MCP Startup Verification",
|
|
304
|
+
status=DiagnosticStatus.OK,
|
|
305
|
+
message="Startup verification passed",
|
|
306
|
+
details={"issues": []}
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
except Exception as e:
|
|
310
|
+
return DiagnosticResult(
|
|
311
|
+
category="MCP Startup Verification",
|
|
312
|
+
status=DiagnosticStatus.WARNING,
|
|
313
|
+
message=f"Could not verify startup: {str(e)}",
|
|
314
|
+
details={"error": str(e)}
|
|
315
|
+
)
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Check monitoring and SocketIO server status.
|
|
3
|
+
|
|
4
|
+
WHY: Verify that the monitoring system and SocketIO server are
|
|
5
|
+
properly configured and accessible for real-time updates.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import socket
|
|
9
|
+
from typing import Dict, Any
|
|
10
|
+
|
|
11
|
+
from ..models import DiagnosticResult, DiagnosticStatus
|
|
12
|
+
from .base_check import BaseDiagnosticCheck
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MonitorCheck(BaseDiagnosticCheck):
|
|
16
|
+
"""Check monitoring and SocketIO server."""
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def name(self) -> str:
|
|
20
|
+
return "monitor_check"
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def category(self) -> str:
|
|
24
|
+
return "Monitor"
|
|
25
|
+
|
|
26
|
+
def run(self) -> DiagnosticResult:
|
|
27
|
+
"""Run monitor diagnostics."""
|
|
28
|
+
try:
|
|
29
|
+
from ....services.port_manager import PortManager
|
|
30
|
+
|
|
31
|
+
sub_results = []
|
|
32
|
+
details = {}
|
|
33
|
+
|
|
34
|
+
# Check SocketIO availability
|
|
35
|
+
socketio_result = self._check_socketio()
|
|
36
|
+
sub_results.append(socketio_result)
|
|
37
|
+
details["socketio_available"] = socketio_result.status == DiagnosticStatus.OK
|
|
38
|
+
|
|
39
|
+
# Check port availability
|
|
40
|
+
port_result = self._check_ports()
|
|
41
|
+
sub_results.append(port_result)
|
|
42
|
+
details["ports"] = port_result.details
|
|
43
|
+
|
|
44
|
+
# Check response logging
|
|
45
|
+
logging_result = self._check_response_logging()
|
|
46
|
+
sub_results.append(logging_result)
|
|
47
|
+
details["response_logging"] = logging_result.details.get("enabled", False)
|
|
48
|
+
|
|
49
|
+
# Check hook service
|
|
50
|
+
hook_result = self._check_hook_service()
|
|
51
|
+
sub_results.append(hook_result)
|
|
52
|
+
details["hooks_enabled"] = hook_result.status == DiagnosticStatus.OK
|
|
53
|
+
|
|
54
|
+
# Determine overall status
|
|
55
|
+
if any(r.status == DiagnosticStatus.ERROR for r in sub_results):
|
|
56
|
+
status = DiagnosticStatus.ERROR
|
|
57
|
+
message = "Monitoring has critical issues"
|
|
58
|
+
elif any(r.status == DiagnosticStatus.WARNING for r in sub_results):
|
|
59
|
+
status = DiagnosticStatus.WARNING
|
|
60
|
+
message = "Monitoring has minor issues"
|
|
61
|
+
else:
|
|
62
|
+
status = DiagnosticStatus.OK
|
|
63
|
+
message = "Monitoring properly configured"
|
|
64
|
+
|
|
65
|
+
return DiagnosticResult(
|
|
66
|
+
category=self.category,
|
|
67
|
+
status=status,
|
|
68
|
+
message=message,
|
|
69
|
+
details=details,
|
|
70
|
+
sub_results=sub_results if self.verbose else []
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
except Exception as e:
|
|
74
|
+
return DiagnosticResult(
|
|
75
|
+
category=self.category,
|
|
76
|
+
status=DiagnosticStatus.ERROR,
|
|
77
|
+
message=f"Monitor check failed: {str(e)}",
|
|
78
|
+
details={"error": str(e)}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def _check_socketio(self) -> DiagnosticResult:
|
|
82
|
+
"""Check if SocketIO is available."""
|
|
83
|
+
try:
|
|
84
|
+
import socketio
|
|
85
|
+
|
|
86
|
+
return DiagnosticResult(
|
|
87
|
+
category="SocketIO",
|
|
88
|
+
status=DiagnosticStatus.OK,
|
|
89
|
+
message="SocketIO library available",
|
|
90
|
+
details={"available": True, "version": getattr(socketio, "__version__", "unknown")}
|
|
91
|
+
)
|
|
92
|
+
except ImportError:
|
|
93
|
+
return DiagnosticResult(
|
|
94
|
+
category="SocketIO",
|
|
95
|
+
status=DiagnosticStatus.WARNING,
|
|
96
|
+
message="SocketIO not installed",
|
|
97
|
+
details={"available": False},
|
|
98
|
+
fix_command="pip install python-socketio[asyncio]",
|
|
99
|
+
fix_description="Install SocketIO for real-time monitoring"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def _check_ports(self) -> DiagnosticResult:
|
|
103
|
+
"""Check port availability for monitoring services."""
|
|
104
|
+
try:
|
|
105
|
+
from ....services.port_manager import PortManager
|
|
106
|
+
|
|
107
|
+
port_manager = PortManager()
|
|
108
|
+
|
|
109
|
+
# Check default monitoring port range
|
|
110
|
+
available_ports = []
|
|
111
|
+
blocked_ports = []
|
|
112
|
+
|
|
113
|
+
for port in range(8765, 8786):
|
|
114
|
+
if self._is_port_available(port):
|
|
115
|
+
available_ports.append(port)
|
|
116
|
+
else:
|
|
117
|
+
blocked_ports.append(port)
|
|
118
|
+
|
|
119
|
+
if not available_ports:
|
|
120
|
+
return DiagnosticResult(
|
|
121
|
+
category="Port Availability",
|
|
122
|
+
status=DiagnosticStatus.ERROR,
|
|
123
|
+
message="No monitoring ports available (8765-8785)",
|
|
124
|
+
details={
|
|
125
|
+
"available": [],
|
|
126
|
+
"blocked": blocked_ports,
|
|
127
|
+
"checked_range": "8765-8785"
|
|
128
|
+
},
|
|
129
|
+
fix_description="Free up ports in the 8765-8785 range"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Check if default port is available
|
|
133
|
+
default_port = 8765
|
|
134
|
+
if default_port not in available_ports:
|
|
135
|
+
return DiagnosticResult(
|
|
136
|
+
category="Port Availability",
|
|
137
|
+
status=DiagnosticStatus.WARNING,
|
|
138
|
+
message=f"Default port {default_port} in use, but alternatives available",
|
|
139
|
+
details={
|
|
140
|
+
"available": available_ports,
|
|
141
|
+
"blocked": blocked_ports,
|
|
142
|
+
"default_port": default_port
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return DiagnosticResult(
|
|
147
|
+
category="Port Availability",
|
|
148
|
+
status=DiagnosticStatus.OK,
|
|
149
|
+
message=f"{len(available_ports)} monitoring port(s) available",
|
|
150
|
+
details={
|
|
151
|
+
"available": available_ports,
|
|
152
|
+
"blocked": blocked_ports,
|
|
153
|
+
"default_port": default_port
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
except Exception as e:
|
|
158
|
+
return DiagnosticResult(
|
|
159
|
+
category="Port Availability",
|
|
160
|
+
status=DiagnosticStatus.WARNING,
|
|
161
|
+
message=f"Could not check ports: {str(e)}",
|
|
162
|
+
details={"error": str(e)}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def _is_port_available(self, port: int) -> bool:
|
|
166
|
+
"""Check if a specific port is available."""
|
|
167
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
168
|
+
sock.settimeout(0.1)
|
|
169
|
+
try:
|
|
170
|
+
result = sock.connect_ex(('localhost', port))
|
|
171
|
+
return result != 0
|
|
172
|
+
finally:
|
|
173
|
+
sock.close()
|
|
174
|
+
|
|
175
|
+
def _check_response_logging(self) -> DiagnosticResult:
|
|
176
|
+
"""Check response logging configuration."""
|
|
177
|
+
from pathlib import Path
|
|
178
|
+
import yaml
|
|
179
|
+
|
|
180
|
+
# Check user config
|
|
181
|
+
config_paths = [
|
|
182
|
+
Path.home() / ".claude" / "claude-mpm.yaml",
|
|
183
|
+
Path.cwd() / ".claude" / "claude-mpm.yaml"
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
response_logging_enabled = False
|
|
187
|
+
log_path = None
|
|
188
|
+
|
|
189
|
+
for config_path in config_paths:
|
|
190
|
+
if config_path.exists():
|
|
191
|
+
try:
|
|
192
|
+
with open(config_path, 'r') as f:
|
|
193
|
+
config = yaml.safe_load(f)
|
|
194
|
+
if config and isinstance(config, dict):
|
|
195
|
+
response_config = config.get("response_logging", {})
|
|
196
|
+
if response_config.get("enabled", False):
|
|
197
|
+
response_logging_enabled = True
|
|
198
|
+
log_path = response_config.get("path", "~/.claude/responses")
|
|
199
|
+
break
|
|
200
|
+
except Exception:
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
if not response_logging_enabled:
|
|
204
|
+
return DiagnosticResult(
|
|
205
|
+
category="Response Logging",
|
|
206
|
+
status=DiagnosticStatus.OK,
|
|
207
|
+
message="Response logging disabled (default)",
|
|
208
|
+
details={"enabled": False}
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Check log directory
|
|
212
|
+
log_dir = Path(log_path).expanduser()
|
|
213
|
+
if not log_dir.exists():
|
|
214
|
+
return DiagnosticResult(
|
|
215
|
+
category="Response Logging",
|
|
216
|
+
status=DiagnosticStatus.WARNING,
|
|
217
|
+
message="Response logging enabled but directory missing",
|
|
218
|
+
details={
|
|
219
|
+
"enabled": True,
|
|
220
|
+
"path": str(log_dir),
|
|
221
|
+
"exists": False
|
|
222
|
+
},
|
|
223
|
+
fix_command=f"mkdir -p {log_dir}",
|
|
224
|
+
fix_description="Create response logging directory"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
import os
|
|
228
|
+
if not os.access(log_dir, os.W_OK):
|
|
229
|
+
return DiagnosticResult(
|
|
230
|
+
category="Response Logging",
|
|
231
|
+
status=DiagnosticStatus.WARNING,
|
|
232
|
+
message="Response logging directory not writable",
|
|
233
|
+
details={
|
|
234
|
+
"enabled": True,
|
|
235
|
+
"path": str(log_dir),
|
|
236
|
+
"writable": False
|
|
237
|
+
},
|
|
238
|
+
fix_command=f"chmod 755 {log_dir}",
|
|
239
|
+
fix_description="Fix permissions on logging directory"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
return DiagnosticResult(
|
|
243
|
+
category="Response Logging",
|
|
244
|
+
status=DiagnosticStatus.OK,
|
|
245
|
+
message="Response logging enabled and configured",
|
|
246
|
+
details={
|
|
247
|
+
"enabled": True,
|
|
248
|
+
"path": str(log_dir),
|
|
249
|
+
"exists": True,
|
|
250
|
+
"writable": True
|
|
251
|
+
}
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
def _check_hook_service(self) -> DiagnosticResult:
|
|
255
|
+
"""Check hook service configuration."""
|
|
256
|
+
try:
|
|
257
|
+
from ....services.hook_service import HookService
|
|
258
|
+
|
|
259
|
+
# Try to create hook service instance
|
|
260
|
+
hook_service = HookService()
|
|
261
|
+
|
|
262
|
+
return DiagnosticResult(
|
|
263
|
+
category="Hook Service",
|
|
264
|
+
status=DiagnosticStatus.OK,
|
|
265
|
+
message="Hook service available",
|
|
266
|
+
details={"available": True}
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
except ImportError:
|
|
270
|
+
return DiagnosticResult(
|
|
271
|
+
category="Hook Service",
|
|
272
|
+
status=DiagnosticStatus.WARNING,
|
|
273
|
+
message="Hook service not available",
|
|
274
|
+
details={"available": False}
|
|
275
|
+
)
|
|
276
|
+
except Exception as e:
|
|
277
|
+
return DiagnosticResult(
|
|
278
|
+
category="Hook Service",
|
|
279
|
+
status=DiagnosticStatus.WARNING,
|
|
280
|
+
message=f"Hook service error: {str(e)}",
|
|
281
|
+
details={"available": False, "error": str(e)}
|
|
282
|
+
)
|