claude-mpm 4.0.29__py3-none-any.whl → 4.0.31__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/agents/deployment/system_instructions_deployer.py +2 -2
- 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.31.dist-info}/METADATA +4 -1
- {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.31.dist-info}/RECORD +65 -48
- {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.31.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.31.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.31.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.29.dist-info → claude_mpm-4.0.31.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Reporter for formatting and displaying diagnostic results.
|
|
3
|
+
|
|
4
|
+
WHY: Provide clear, actionable output from diagnostic checks with proper
|
|
5
|
+
formatting for terminal display and JSON export.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from .models import DiagnosticResult, DiagnosticStatus, DiagnosticSummary
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DoctorReporter:
|
|
16
|
+
"""Format and display diagnostic results.
|
|
17
|
+
|
|
18
|
+
WHY: Consistent, user-friendly output that clearly shows system health
|
|
19
|
+
status and provides actionable fixes for any issues.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Status symbols and colors
|
|
23
|
+
STATUS_SYMBOLS = {
|
|
24
|
+
DiagnosticStatus.OK: "✅",
|
|
25
|
+
DiagnosticStatus.WARNING: "⚠️ ",
|
|
26
|
+
DiagnosticStatus.ERROR: "❌",
|
|
27
|
+
DiagnosticStatus.SKIPPED: "⏭️ "
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# ANSI color codes
|
|
31
|
+
COLORS = {
|
|
32
|
+
"reset": "\033[0m",
|
|
33
|
+
"bold": "\033[1m",
|
|
34
|
+
"red": "\033[91m",
|
|
35
|
+
"green": "\033[92m",
|
|
36
|
+
"yellow": "\033[93m",
|
|
37
|
+
"blue": "\033[94m",
|
|
38
|
+
"gray": "\033[90m"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def __init__(self, use_color: bool = True, verbose: bool = False):
|
|
42
|
+
"""Initialize reporter.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
use_color: Whether to use ANSI color codes
|
|
46
|
+
verbose: Whether to include detailed information
|
|
47
|
+
"""
|
|
48
|
+
self.use_color = use_color and sys.stdout.isatty()
|
|
49
|
+
self.verbose = verbose
|
|
50
|
+
|
|
51
|
+
def report(self, summary: DiagnosticSummary, format: str = "terminal"):
|
|
52
|
+
"""Generate and output diagnostic report.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
summary: DiagnosticSummary with all results
|
|
56
|
+
format: Output format ("terminal", "json", "markdown")
|
|
57
|
+
"""
|
|
58
|
+
if format == "json":
|
|
59
|
+
self._report_json(summary)
|
|
60
|
+
elif format == "markdown":
|
|
61
|
+
self._report_markdown(summary)
|
|
62
|
+
else:
|
|
63
|
+
self._report_terminal(summary)
|
|
64
|
+
|
|
65
|
+
def _report_terminal(self, summary: DiagnosticSummary):
|
|
66
|
+
"""Generate terminal-formatted report."""
|
|
67
|
+
# Header
|
|
68
|
+
self._print_header()
|
|
69
|
+
|
|
70
|
+
# Results by category
|
|
71
|
+
for result in summary.results:
|
|
72
|
+
self._print_result(result)
|
|
73
|
+
|
|
74
|
+
# Summary
|
|
75
|
+
self._print_summary(summary)
|
|
76
|
+
|
|
77
|
+
# Fix suggestions
|
|
78
|
+
self._print_fixes(summary)
|
|
79
|
+
|
|
80
|
+
def _print_header(self):
|
|
81
|
+
"""Print report header."""
|
|
82
|
+
print()
|
|
83
|
+
print(self._color("Claude MPM Doctor Report", "bold"))
|
|
84
|
+
print("=" * 40)
|
|
85
|
+
print()
|
|
86
|
+
|
|
87
|
+
def _print_result(self, result: DiagnosticResult, indent: int = 0):
|
|
88
|
+
"""Print a single diagnostic result."""
|
|
89
|
+
indent_str = " " * indent
|
|
90
|
+
|
|
91
|
+
# Status symbol and category
|
|
92
|
+
symbol = self.STATUS_SYMBOLS.get(result.status, "?")
|
|
93
|
+
color = self._get_status_color(result.status)
|
|
94
|
+
|
|
95
|
+
# Main result line
|
|
96
|
+
line = f"{indent_str}{symbol} {result.category}: "
|
|
97
|
+
|
|
98
|
+
if result.status == DiagnosticStatus.OK:
|
|
99
|
+
line += self._color("OK", color)
|
|
100
|
+
elif result.status == DiagnosticStatus.WARNING:
|
|
101
|
+
line += self._color("Warning", color)
|
|
102
|
+
elif result.status == DiagnosticStatus.ERROR:
|
|
103
|
+
line += self._color("Error", color)
|
|
104
|
+
else:
|
|
105
|
+
line += self._color("Skipped", color)
|
|
106
|
+
|
|
107
|
+
print(line)
|
|
108
|
+
|
|
109
|
+
# Message
|
|
110
|
+
message_indent = " " + indent_str
|
|
111
|
+
print(f"{message_indent}{result.message}")
|
|
112
|
+
|
|
113
|
+
# Details (in verbose mode)
|
|
114
|
+
if self.verbose and result.details:
|
|
115
|
+
for key, value in result.details.items():
|
|
116
|
+
if key not in ["error", "issues"]: # Skip complex fields
|
|
117
|
+
print(f"{message_indent}{self._color(key, 'gray')}: {value}")
|
|
118
|
+
|
|
119
|
+
# Fix suggestion
|
|
120
|
+
if result.fix_command:
|
|
121
|
+
fix_indent = " " + indent_str
|
|
122
|
+
print(f"{fix_indent}{self._color('→ Fix:', 'blue')} Run '{result.fix_command}'")
|
|
123
|
+
if result.fix_description:
|
|
124
|
+
print(f"{fix_indent} {self._color(result.fix_description, 'gray')}")
|
|
125
|
+
|
|
126
|
+
# Sub-results (in verbose mode)
|
|
127
|
+
if self.verbose and result.sub_results:
|
|
128
|
+
for sub_result in result.sub_results:
|
|
129
|
+
self._print_result(sub_result, indent + 1)
|
|
130
|
+
|
|
131
|
+
if indent == 0:
|
|
132
|
+
print() # Extra line between top-level results
|
|
133
|
+
|
|
134
|
+
def _print_summary(self, summary: DiagnosticSummary):
|
|
135
|
+
"""Print summary statistics."""
|
|
136
|
+
print(self._color("─" * 40, "gray"))
|
|
137
|
+
|
|
138
|
+
status_line = f"Summary: "
|
|
139
|
+
parts = []
|
|
140
|
+
|
|
141
|
+
if summary.ok_count > 0:
|
|
142
|
+
parts.append(self._color(f"{summary.ok_count} OK", "green"))
|
|
143
|
+
if summary.warning_count > 0:
|
|
144
|
+
parts.append(self._color(f"{summary.warning_count} Warning{'s' if summary.warning_count != 1 else ''}", "yellow"))
|
|
145
|
+
if summary.error_count > 0:
|
|
146
|
+
parts.append(self._color(f"{summary.error_count} Error{'s' if summary.error_count != 1 else ''}", "red"))
|
|
147
|
+
if summary.skipped_count > 0:
|
|
148
|
+
parts.append(self._color(f"{summary.skipped_count} Skipped", "gray"))
|
|
149
|
+
|
|
150
|
+
status_line += " | ".join(parts)
|
|
151
|
+
print(status_line)
|
|
152
|
+
|
|
153
|
+
# Overall health
|
|
154
|
+
overall = summary.overall_status
|
|
155
|
+
if overall == DiagnosticStatus.OK:
|
|
156
|
+
print(self._color("\n✅ System is healthy!", "green"))
|
|
157
|
+
elif overall == DiagnosticStatus.WARNING:
|
|
158
|
+
print(self._color("\n⚠️ System has minor issues that should be addressed.", "yellow"))
|
|
159
|
+
else:
|
|
160
|
+
print(self._color("\n❌ System has critical issues that need immediate attention!", "red"))
|
|
161
|
+
|
|
162
|
+
def _print_fixes(self, summary: DiagnosticSummary):
|
|
163
|
+
"""Print consolidated fix suggestions."""
|
|
164
|
+
fixes = []
|
|
165
|
+
|
|
166
|
+
for result in summary.results:
|
|
167
|
+
if result.fix_command and result.has_issues:
|
|
168
|
+
fixes.append((result.category, result.fix_command, result.fix_description))
|
|
169
|
+
|
|
170
|
+
if fixes:
|
|
171
|
+
print()
|
|
172
|
+
print(self._color("Suggested Fixes:", "bold"))
|
|
173
|
+
print(self._color("─" * 40, "gray"))
|
|
174
|
+
|
|
175
|
+
for i, (category, command, description) in enumerate(fixes, 1):
|
|
176
|
+
print(f"{i}. {category}:")
|
|
177
|
+
print(f" {self._color(command, 'blue')}")
|
|
178
|
+
if description:
|
|
179
|
+
print(f" {self._color(description, 'gray')}")
|
|
180
|
+
print()
|
|
181
|
+
|
|
182
|
+
if self.verbose:
|
|
183
|
+
print(self._color("Run 'claude-mpm doctor --fix' to attempt automatic fixes", "gray"))
|
|
184
|
+
else:
|
|
185
|
+
print(self._color("Run 'claude-mpm doctor --verbose' for more details", "gray"))
|
|
186
|
+
|
|
187
|
+
def _report_json(self, summary: DiagnosticSummary):
|
|
188
|
+
"""Generate JSON-formatted report."""
|
|
189
|
+
output = summary.to_dict()
|
|
190
|
+
|
|
191
|
+
# Add metadata
|
|
192
|
+
output["metadata"] = {
|
|
193
|
+
"tool": "claude-mpm doctor",
|
|
194
|
+
"version": self._get_version(),
|
|
195
|
+
"verbose": self.verbose
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# Add fix suggestions
|
|
199
|
+
fixes = []
|
|
200
|
+
for result in summary.results:
|
|
201
|
+
if result.fix_command and result.has_issues:
|
|
202
|
+
fixes.append({
|
|
203
|
+
"category": result.category,
|
|
204
|
+
"command": result.fix_command,
|
|
205
|
+
"description": result.fix_description
|
|
206
|
+
})
|
|
207
|
+
output["fixes"] = fixes
|
|
208
|
+
|
|
209
|
+
print(json.dumps(output, indent=2))
|
|
210
|
+
|
|
211
|
+
def _report_markdown(self, summary: DiagnosticSummary):
|
|
212
|
+
"""Generate Markdown-formatted report."""
|
|
213
|
+
print("# Claude MPM Doctor Report\n")
|
|
214
|
+
|
|
215
|
+
# Summary table
|
|
216
|
+
print("## Summary\n")
|
|
217
|
+
print("| Status | Count |")
|
|
218
|
+
print("|--------|-------|")
|
|
219
|
+
print(f"| ✅ OK | {summary.ok_count} |")
|
|
220
|
+
print(f"| ⚠️ Warning | {summary.warning_count} |")
|
|
221
|
+
print(f"| ❌ Error | {summary.error_count} |")
|
|
222
|
+
print(f"| ⏭️ Skipped | {summary.skipped_count} |")
|
|
223
|
+
print()
|
|
224
|
+
|
|
225
|
+
# Detailed results
|
|
226
|
+
print("## Diagnostic Results\n")
|
|
227
|
+
|
|
228
|
+
for result in summary.results:
|
|
229
|
+
symbol = self.STATUS_SYMBOLS.get(result.status, "?")
|
|
230
|
+
print(f"### {symbol} {result.category}\n")
|
|
231
|
+
print(f"**Status:** {result.status.value}")
|
|
232
|
+
print(f"**Message:** {result.message}\n")
|
|
233
|
+
|
|
234
|
+
if result.fix_command:
|
|
235
|
+
print(f"**Fix:** `{result.fix_command}`")
|
|
236
|
+
if result.fix_description:
|
|
237
|
+
print(f"_{result.fix_description}_\n")
|
|
238
|
+
|
|
239
|
+
if self.verbose and result.details:
|
|
240
|
+
print("**Details:**")
|
|
241
|
+
for key, value in result.details.items():
|
|
242
|
+
print(f"- {key}: {value}")
|
|
243
|
+
print()
|
|
244
|
+
|
|
245
|
+
# Fixes section
|
|
246
|
+
fixes = [(r.category, r.fix_command, r.fix_description)
|
|
247
|
+
for r in summary.results if r.fix_command and r.has_issues]
|
|
248
|
+
|
|
249
|
+
if fixes:
|
|
250
|
+
print("## Suggested Fixes\n")
|
|
251
|
+
for category, command, description in fixes:
|
|
252
|
+
print(f"- **{category}:** `{command}`")
|
|
253
|
+
if description:
|
|
254
|
+
print(f" - {description}")
|
|
255
|
+
print()
|
|
256
|
+
|
|
257
|
+
def _color(self, text: str, color: str) -> str:
|
|
258
|
+
"""Apply color to text if colors are enabled."""
|
|
259
|
+
if not self.use_color:
|
|
260
|
+
return text
|
|
261
|
+
|
|
262
|
+
color_code = self.COLORS.get(color, "")
|
|
263
|
+
reset_code = self.COLORS["reset"]
|
|
264
|
+
return f"{color_code}{text}{reset_code}"
|
|
265
|
+
|
|
266
|
+
def _get_status_color(self, status: DiagnosticStatus) -> str:
|
|
267
|
+
"""Get color for a status."""
|
|
268
|
+
color_map = {
|
|
269
|
+
DiagnosticStatus.OK: "green",
|
|
270
|
+
DiagnosticStatus.WARNING: "yellow",
|
|
271
|
+
DiagnosticStatus.ERROR: "red",
|
|
272
|
+
DiagnosticStatus.SKIPPED: "gray"
|
|
273
|
+
}
|
|
274
|
+
return color_map.get(status, "reset")
|
|
275
|
+
|
|
276
|
+
def _get_version(self) -> str:
|
|
277
|
+
"""Get claude-mpm version."""
|
|
278
|
+
try:
|
|
279
|
+
from ..version_service import VersionService
|
|
280
|
+
service = VersionService()
|
|
281
|
+
return service.get_version()
|
|
282
|
+
except Exception:
|
|
283
|
+
return "unknown"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data models for the diagnostic system.
|
|
3
|
+
|
|
4
|
+
WHY: Define clear data structures for diagnostic results to ensure
|
|
5
|
+
consistency across all checks and reporting.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DiagnosticStatus(Enum):
|
|
14
|
+
"""Status levels for diagnostic results."""
|
|
15
|
+
OK = "ok"
|
|
16
|
+
WARNING = "warning"
|
|
17
|
+
ERROR = "error"
|
|
18
|
+
SKIPPED = "skipped"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class DiagnosticResult:
|
|
23
|
+
"""Result from a diagnostic check.
|
|
24
|
+
|
|
25
|
+
WHY: Standardized result format ensures consistent reporting
|
|
26
|
+
and makes it easy to aggregate and display results.
|
|
27
|
+
"""
|
|
28
|
+
category: str # e.g., "Installation", "Agents", "MCP Server"
|
|
29
|
+
status: DiagnosticStatus
|
|
30
|
+
message: str
|
|
31
|
+
details: Dict[str, Any] = field(default_factory=dict)
|
|
32
|
+
fix_command: Optional[str] = None
|
|
33
|
+
fix_description: Optional[str] = None
|
|
34
|
+
sub_results: List['DiagnosticResult'] = field(default_factory=list)
|
|
35
|
+
|
|
36
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
37
|
+
"""Convert to dictionary for JSON serialization."""
|
|
38
|
+
return {
|
|
39
|
+
"category": self.category,
|
|
40
|
+
"status": self.status.value,
|
|
41
|
+
"message": self.message,
|
|
42
|
+
"details": self.details,
|
|
43
|
+
"fix_command": self.fix_command,
|
|
44
|
+
"fix_description": self.fix_description,
|
|
45
|
+
"sub_results": [r.to_dict() for r in self.sub_results]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def has_issues(self) -> bool:
|
|
50
|
+
"""Check if this result indicates any issues."""
|
|
51
|
+
return self.status in (DiagnosticStatus.WARNING, DiagnosticStatus.ERROR)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def severity_level(self) -> int:
|
|
55
|
+
"""Get numeric severity level for sorting."""
|
|
56
|
+
severity_map = {
|
|
57
|
+
DiagnosticStatus.OK: 0,
|
|
58
|
+
DiagnosticStatus.SKIPPED: 1,
|
|
59
|
+
DiagnosticStatus.WARNING: 2,
|
|
60
|
+
DiagnosticStatus.ERROR: 3
|
|
61
|
+
}
|
|
62
|
+
return severity_map.get(self.status, 0)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class DiagnosticSummary:
|
|
67
|
+
"""Summary of all diagnostic results.
|
|
68
|
+
|
|
69
|
+
WHY: Provides a high-level overview of system health
|
|
70
|
+
and quick access to issues that need attention.
|
|
71
|
+
"""
|
|
72
|
+
total_checks: int = 0
|
|
73
|
+
ok_count: int = 0
|
|
74
|
+
warning_count: int = 0
|
|
75
|
+
error_count: int = 0
|
|
76
|
+
skipped_count: int = 0
|
|
77
|
+
results: List[DiagnosticResult] = field(default_factory=list)
|
|
78
|
+
|
|
79
|
+
def add_result(self, result: DiagnosticResult):
|
|
80
|
+
"""Add a result to the summary."""
|
|
81
|
+
self.results.append(result)
|
|
82
|
+
self.total_checks += 1
|
|
83
|
+
|
|
84
|
+
if result.status == DiagnosticStatus.OK:
|
|
85
|
+
self.ok_count += 1
|
|
86
|
+
elif result.status == DiagnosticStatus.WARNING:
|
|
87
|
+
self.warning_count += 1
|
|
88
|
+
elif result.status == DiagnosticStatus.ERROR:
|
|
89
|
+
self.error_count += 1
|
|
90
|
+
elif result.status == DiagnosticStatus.SKIPPED:
|
|
91
|
+
self.skipped_count += 1
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def has_issues(self) -> bool:
|
|
95
|
+
"""Check if there are any warnings or errors."""
|
|
96
|
+
return self.warning_count > 0 or self.error_count > 0
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def overall_status(self) -> DiagnosticStatus:
|
|
100
|
+
"""Get overall system status."""
|
|
101
|
+
if self.error_count > 0:
|
|
102
|
+
return DiagnosticStatus.ERROR
|
|
103
|
+
elif self.warning_count > 0:
|
|
104
|
+
return DiagnosticStatus.WARNING
|
|
105
|
+
else:
|
|
106
|
+
return DiagnosticStatus.OK
|
|
107
|
+
|
|
108
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
109
|
+
"""Convert to dictionary for JSON serialization."""
|
|
110
|
+
return {
|
|
111
|
+
"summary": {
|
|
112
|
+
"total_checks": self.total_checks,
|
|
113
|
+
"ok": self.ok_count,
|
|
114
|
+
"warnings": self.warning_count,
|
|
115
|
+
"errors": self.error_count,
|
|
116
|
+
"skipped": self.skipped_count,
|
|
117
|
+
"overall_status": self.overall_status.value
|
|
118
|
+
},
|
|
119
|
+
"results": [r.to_dict() for r in self.results]
|
|
120
|
+
}
|
|
@@ -413,7 +413,7 @@ class IMCPGateway(IMCPLifecycle):
|
|
|
413
413
|
Main interface for MCP gateway implementation.
|
|
414
414
|
|
|
415
415
|
Orchestrates tool registry, communication, and request handling.
|
|
416
|
-
Acts as a protocol bridge between Claude
|
|
416
|
+
Acts as a protocol bridge between Claude Code and internal tools.
|
|
417
417
|
"""
|
|
418
418
|
|
|
419
419
|
@abstractmethod
|
|
@@ -442,7 +442,7 @@ Examples:
|
|
|
442
442
|
# Run with debug logging
|
|
443
443
|
python -m claude_mpm.services.mcp_gateway.main --debug
|
|
444
444
|
|
|
445
|
-
# Run as MCP server for Claude
|
|
445
|
+
# Run as MCP server for Claude Code
|
|
446
446
|
python -m claude_mpm.services.mcp_gateway.main --stdio
|
|
447
447
|
""",
|
|
448
448
|
)
|
|
@@ -6,8 +6,8 @@ MCP protocol gateway using Anthropic's official MCP package.
|
|
|
6
6
|
Handles stdio-based communication, request routing, and tool invocation.
|
|
7
7
|
Acts as a bridge between Claude Code and internal tools.
|
|
8
8
|
|
|
9
|
-
NOTE: MCP is ONLY for Claude Code - NOT for Claude
|
|
10
|
-
Claude
|
|
9
|
+
NOTE: MCP is ONLY for Claude Code - NOT for Claude Code.
|
|
10
|
+
Claude Code uses a different system for agent deployment.
|
|
11
11
|
|
|
12
12
|
Part of ISS-0035: MCP Gateway Implementation - Core Gateway and Tool Registry
|
|
13
13
|
"""
|
|
@@ -339,7 +339,7 @@ class MCPGateway(BaseMCPService, IMCPGateway):
|
|
|
339
339
|
to handle incoming requests from Claude Code.
|
|
340
340
|
|
|
341
341
|
WHY: We use stdio (stdin/stdout) as it's the standard communication
|
|
342
|
-
method for MCP servers in Claude
|
|
342
|
+
method for MCP servers in Claude Code. This ensures compatibility
|
|
343
343
|
and allows the server to be launched as a subprocess.
|
|
344
344
|
"""
|
|
345
345
|
try:
|
|
@@ -24,7 +24,7 @@ class StdioHandler(BaseMCPService, IMCPCommunication):
|
|
|
24
24
|
STDIO-based communication handler for MCP.
|
|
25
25
|
|
|
26
26
|
WHY: The MCP protocol uses stdio (stdin/stdout) for communication between
|
|
27
|
-
Claude
|
|
27
|
+
Claude Code and MCP servers. This handler manages the low-level
|
|
28
28
|
message exchange, ensuring proper JSON-RPC formatting and error handling.
|
|
29
29
|
|
|
30
30
|
DESIGN DECISIONS:
|
|
@@ -3,7 +3,7 @@ Simplified MCP Stdio Server
|
|
|
3
3
|
============================
|
|
4
4
|
|
|
5
5
|
A proper stdio-based MCP server that communicates via JSON-RPC over stdin/stdout.
|
|
6
|
-
This server is spawned on-demand by Claude
|
|
6
|
+
This server is spawned on-demand by Claude Code/Code and exits when the connection closes.
|
|
7
7
|
|
|
8
8
|
WHY: MCP servers should be simple stdio-based processes that Claude can spawn and control.
|
|
9
9
|
They should NOT run as persistent background services with lock files.
|
|
@@ -45,7 +45,7 @@ def apply_backward_compatibility_patches():
|
|
|
45
45
|
Apply backward compatibility patches for MCP protocol differences.
|
|
46
46
|
|
|
47
47
|
This function patches the MCP Server to handle missing clientInfo
|
|
48
|
-
in initialize requests from older Claude
|
|
48
|
+
in initialize requests from older Claude Code versions.
|
|
49
49
|
"""
|
|
50
50
|
try:
|
|
51
51
|
from mcp.server import Server
|
|
@@ -114,7 +114,7 @@ class SimpleMCPServer:
|
|
|
114
114
|
A simple stdio-based MCP server implementation.
|
|
115
115
|
|
|
116
116
|
WHY: This server follows the MCP specification for stdio communication,
|
|
117
|
-
making it compatible with Claude
|
|
117
|
+
making it compatible with Claude Code/Code's MCP client.
|
|
118
118
|
|
|
119
119
|
DESIGN DECISIONS:
|
|
120
120
|
- No persistent state or lock files
|
|
@@ -3,9 +3,9 @@ MCP Tool Adapters for aitrackdown Ticket Management
|
|
|
3
3
|
====================================================
|
|
4
4
|
|
|
5
5
|
Provides MCP tool wrappers for common aitrackdown operations, enabling
|
|
6
|
-
ticket management through Claude
|
|
6
|
+
ticket management through Claude Code's MCP interface.
|
|
7
7
|
|
|
8
|
-
WHY: These tools allow Claude
|
|
8
|
+
WHY: These tools allow Claude Code to interact with aitrackdown for
|
|
9
9
|
ticket management without requiring direct CLI access, providing a
|
|
10
10
|
seamless integration experience.
|
|
11
11
|
|
|
@@ -70,21 +70,31 @@ class EventHandlerRegistry:
|
|
|
70
70
|
)
|
|
71
71
|
return
|
|
72
72
|
|
|
73
|
+
# Add debug logging for deployment context
|
|
74
|
+
try:
|
|
75
|
+
from ....core.unified_paths import PathContext
|
|
76
|
+
deployment_context = PathContext.detect_deployment_context()
|
|
77
|
+
self.logger.info(f"Initializing event handlers in {deployment_context.value} mode")
|
|
78
|
+
except Exception as e:
|
|
79
|
+
self.logger.debug(f"Could not detect deployment context: {e}")
|
|
80
|
+
|
|
73
81
|
handler_classes = handler_classes or self.DEFAULT_HANDLERS
|
|
82
|
+
self.logger.debug(f"Initializing {len(handler_classes)} handler classes")
|
|
74
83
|
|
|
75
84
|
for handler_class in handler_classes:
|
|
76
85
|
try:
|
|
86
|
+
self.logger.debug(f"Creating instance of {handler_class.__name__}")
|
|
77
87
|
handler = handler_class(self.server)
|
|
78
88
|
self.handlers.append(handler)
|
|
79
|
-
self.logger.info(f"Initialized handler: {handler_class.__name__}")
|
|
89
|
+
self.logger.info(f"✅ Initialized handler: {handler_class.__name__}")
|
|
80
90
|
except Exception as e:
|
|
81
|
-
self.logger.error(f"Failed to initialize {handler_class.__name__}: {e}")
|
|
91
|
+
self.logger.error(f"❌ Failed to initialize {handler_class.__name__}: {e}")
|
|
82
92
|
import traceback
|
|
83
93
|
|
|
84
94
|
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
|
85
95
|
|
|
86
96
|
self._initialized = True
|
|
87
|
-
self.logger.info(f"Registry initialized with {len(self.handlers)} handlers")
|
|
97
|
+
self.logger.info(f"🎯 Registry initialized with {len(self.handlers)} handlers")
|
|
88
98
|
|
|
89
99
|
def register_all_events(self) -> None:
|
|
90
100
|
"""Register all events from all handlers.
|
|
@@ -97,27 +107,49 @@ class EventHandlerRegistry:
|
|
|
97
107
|
self.logger.error("Registry not initialized. Call initialize() first.")
|
|
98
108
|
raise RuntimeError("EventHandlerRegistry not initialized")
|
|
99
109
|
|
|
110
|
+
self.logger.info(f"🔄 Starting event registration for {len(self.handlers)} handlers")
|
|
111
|
+
|
|
112
|
+
# Verify Socket.IO server is available
|
|
113
|
+
if not hasattr(self.server, 'core') or not self.server.core or not self.server.core.sio:
|
|
114
|
+
self.logger.error("❌ Socket.IO server instance not available for event registration")
|
|
115
|
+
raise RuntimeError("Socket.IO server not available")
|
|
116
|
+
|
|
117
|
+
self.logger.debug(f"Socket.IO server available: {type(self.server.core.sio)}")
|
|
118
|
+
|
|
100
119
|
registered_count = 0
|
|
101
120
|
for handler in self.handlers:
|
|
102
121
|
try:
|
|
122
|
+
self.logger.debug(f"Registering events for {handler.__class__.__name__}")
|
|
123
|
+
|
|
124
|
+
# Get the number of registered events before and after
|
|
125
|
+
sio_events_before = len(getattr(self.server.core.sio, 'handlers', {}))
|
|
126
|
+
|
|
103
127
|
handler.register_events()
|
|
128
|
+
|
|
129
|
+
sio_events_after = len(getattr(self.server.core.sio, 'handlers', {}))
|
|
130
|
+
events_added = sio_events_after - sio_events_before
|
|
131
|
+
|
|
104
132
|
registered_count += 1
|
|
105
|
-
self.logger.info(f"Registered events for {handler.__class__.__name__}")
|
|
133
|
+
self.logger.info(f"✅ Registered {events_added} events for {handler.__class__.__name__}")
|
|
134
|
+
|
|
106
135
|
except NotImplementedError:
|
|
107
136
|
# Handler has no events to register (like ProjectEventHandler)
|
|
108
137
|
self.logger.debug(
|
|
109
|
-
f"No events to register for {handler.__class__.__name__}"
|
|
138
|
+
f"⏭️ No events to register for {handler.__class__.__name__}"
|
|
110
139
|
)
|
|
111
140
|
except Exception as e:
|
|
112
141
|
self.logger.error(
|
|
113
|
-
f"Failed to register events for {handler.__class__.__name__}: {e}"
|
|
142
|
+
f"❌ Failed to register events for {handler.__class__.__name__}: {e}"
|
|
114
143
|
)
|
|
115
144
|
import traceback
|
|
116
145
|
|
|
117
146
|
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
|
118
147
|
|
|
148
|
+
# Final verification
|
|
149
|
+
total_sio_events = len(getattr(self.server.core.sio, 'handlers', {}))
|
|
119
150
|
self.logger.info(
|
|
120
|
-
f"
|
|
151
|
+
f"🎉 Event registration complete: {registered_count} handlers processed, "
|
|
152
|
+
f"{total_sio_events} total Socket.IO events registered"
|
|
121
153
|
)
|
|
122
154
|
|
|
123
155
|
def add_handler(self, handler_class: Type[BaseEventHandler]):
|