claude-mpm 4.0.28__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/agent-manager.json +24 -0
- claude_mpm/agents/templates/agent-manager.md +304 -0
- 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 +4 -0
- claude_mpm/cli/commands/__init__.py +4 -0
- claude_mpm/cli/commands/agent_manager.py +521 -0
- 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/memory.py +1 -1
- 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 +247 -0
- claude_mpm/cli/parsers/base_parser.py +12 -1
- claude_mpm/cli/parsers/mcp_parser.py +1 -1
- claude_mpm/cli/parsers/run_parser.py +1 -1
- claude_mpm/cli/shared/__init__.py +1 -1
- claude_mpm/cli/startup_logging.py +463 -0
- claude_mpm/constants.py +2 -0
- claude_mpm/core/claude_runner.py +81 -2
- claude_mpm/core/constants.py +2 -2
- 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/socketio_pool.py +2 -2
- claude_mpm/core/unified_paths.py +128 -0
- claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/css/dashboard.css +170 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +21 -3
- claude_mpm/dashboard/static/js/components/module-viewer.js +129 -1
- claude_mpm/dashboard/static/js/dashboard.js +116 -0
- claude_mpm/dashboard/static/js/socket-client.js +0 -1
- claude_mpm/hooks/claude_hooks/connection_pool.py +1 -1
- claude_mpm/hooks/claude_hooks/hook_handler.py +1 -1
- claude_mpm/scripts/mcp_server.py +2 -2
- claude_mpm/services/agents/agent_builder.py +455 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +10 -3
- 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/memory/__init__.py +2 -0
- claude_mpm/services/socketio/handlers/connection.py +27 -33
- 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.28.dist-info → claude_mpm-4.0.30.dist-info}/METADATA +4 -1
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/RECORD +89 -67
- /claude_mpm/cli/shared/{command_base.py → base_command.py} +0 -0
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.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
|
|
|
@@ -9,9 +9,11 @@ This module provides memory management services including:
|
|
|
9
9
|
from .builder import MemoryBuilder
|
|
10
10
|
from .optimizer import MemoryOptimizer
|
|
11
11
|
from .router import MemoryRouter
|
|
12
|
+
from .indexed_memory import IndexedMemoryService
|
|
12
13
|
|
|
13
14
|
__all__ = [
|
|
14
15
|
"MemoryBuilder",
|
|
15
16
|
"MemoryRouter",
|
|
16
17
|
"MemoryOptimizer",
|
|
18
|
+
"IndexedMemoryService",
|
|
17
19
|
]
|
|
@@ -27,59 +27,53 @@ def timeout_handler(timeout_seconds: float = 5.0):
|
|
|
27
27
|
"""
|
|
28
28
|
def decorator(func: Callable) -> Callable:
|
|
29
29
|
@functools.wraps(func)
|
|
30
|
-
async def wrapper(
|
|
30
|
+
async def wrapper(*args, **kwargs):
|
|
31
31
|
handler_name = func.__name__
|
|
32
32
|
start_time = time.time()
|
|
33
33
|
|
|
34
34
|
try:
|
|
35
35
|
# Create a task with timeout
|
|
36
36
|
result = await asyncio.wait_for(
|
|
37
|
-
func(
|
|
37
|
+
func(*args, **kwargs),
|
|
38
38
|
timeout=timeout_seconds
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
elapsed = time.time() - start_time
|
|
42
42
|
if elapsed > timeout_seconds * 0.8: # Warn if close to timeout
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
# Try to get logger from closure scope or fallback to print
|
|
44
|
+
try:
|
|
45
|
+
import logging
|
|
46
|
+
logger = logging.getLogger(__name__)
|
|
47
|
+
logger.warning(
|
|
48
|
+
f"⚠️ Handler {handler_name} took {elapsed:.2f}s "
|
|
49
|
+
f"(close to {timeout_seconds}s timeout)"
|
|
50
|
+
)
|
|
51
|
+
except:
|
|
52
|
+
print(f"⚠️ Handler {handler_name} took {elapsed:.2f}s (close to {timeout_seconds}s timeout)")
|
|
47
53
|
|
|
48
54
|
return result
|
|
49
55
|
|
|
50
56
|
except asyncio.TimeoutError:
|
|
51
57
|
elapsed = time.time() - start_time
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
try:
|
|
60
|
-
# Use a short timeout for error response
|
|
61
|
-
await asyncio.wait_for(
|
|
62
|
-
self.emit_to_client(
|
|
63
|
-
sid,
|
|
64
|
-
"error",
|
|
65
|
-
{
|
|
66
|
-
"message": f"Handler {handler_name} timed out",
|
|
67
|
-
"handler": handler_name,
|
|
68
|
-
"timeout": timeout_seconds
|
|
69
|
-
}
|
|
70
|
-
),
|
|
71
|
-
timeout=1.0
|
|
72
|
-
)
|
|
73
|
-
except:
|
|
74
|
-
pass # Best effort error notification
|
|
58
|
+
# Try to get logger from closure scope or fallback to print
|
|
59
|
+
try:
|
|
60
|
+
import logging
|
|
61
|
+
logger = logging.getLogger(__name__)
|
|
62
|
+
logger.error(f"❌ Handler {handler_name} timed out after {elapsed:.2f}s")
|
|
63
|
+
except:
|
|
64
|
+
print(f"❌ Handler {handler_name} timed out after {elapsed:.2f}s")
|
|
75
65
|
|
|
76
66
|
return None
|
|
77
67
|
|
|
78
68
|
except Exception as e:
|
|
79
69
|
elapsed = time.time() - start_time
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
70
|
+
# Try to get logger from closure scope or fallback to print
|
|
71
|
+
try:
|
|
72
|
+
import logging
|
|
73
|
+
logger = logging.getLogger(__name__)
|
|
74
|
+
logger.error(f"❌ Handler {handler_name} failed after {elapsed:.2f}s: {e}")
|
|
75
|
+
except:
|
|
76
|
+
print(f"❌ Handler {handler_name} failed after {elapsed:.2f}s: {e}")
|
|
83
77
|
raise
|
|
84
78
|
|
|
85
79
|
return wrapper
|
|
@@ -325,7 +319,7 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
325
319
|
|
|
326
320
|
@self.sio.event
|
|
327
321
|
@timeout_handler(timeout_seconds=3.0)
|
|
328
|
-
async def disconnect(sid):
|
|
322
|
+
async def disconnect(sid, *args):
|
|
329
323
|
"""Handle client disconnection.
|
|
330
324
|
|
|
331
325
|
WHY: We need to clean up client tracking when they disconnect
|