claude-mpm 4.0.32__py3-none-any.whl → 4.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +70 -2
  3. claude_mpm/agents/OUTPUT_STYLE.md +0 -11
  4. claude_mpm/agents/WORKFLOW.md +14 -2
  5. claude_mpm/agents/templates/documentation.json +51 -34
  6. claude_mpm/agents/templates/research.json +0 -11
  7. claude_mpm/cli/__init__.py +111 -33
  8. claude_mpm/cli/commands/agent_manager.py +10 -8
  9. claude_mpm/cli/commands/agents.py +82 -0
  10. claude_mpm/cli/commands/cleanup_orphaned_agents.py +150 -0
  11. claude_mpm/cli/commands/mcp_pipx_config.py +199 -0
  12. claude_mpm/cli/parsers/agents_parser.py +27 -0
  13. claude_mpm/cli/parsers/base_parser.py +6 -0
  14. claude_mpm/cli/startup_logging.py +75 -0
  15. claude_mpm/core/framework_loader.py +173 -84
  16. claude_mpm/dashboard/static/css/dashboard.css +449 -0
  17. claude_mpm/dashboard/static/dist/components/agent-inference.js +1 -1
  18. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  19. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +1 -1
  20. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  21. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  22. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  23. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  24. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +774 -0
  25. claude_mpm/dashboard/static/js/components/agent-inference.js +257 -3
  26. claude_mpm/dashboard/static/js/components/build-tracker.js +323 -0
  27. claude_mpm/dashboard/static/js/components/event-viewer.js +168 -39
  28. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +17 -0
  29. claude_mpm/dashboard/static/js/components/session-manager.js +23 -3
  30. claude_mpm/dashboard/static/js/components/socket-manager.js +2 -0
  31. claude_mpm/dashboard/static/js/dashboard.js +207 -31
  32. claude_mpm/dashboard/static/js/socket-client.js +92 -11
  33. claude_mpm/dashboard/templates/index.html +1 -0
  34. claude_mpm/hooks/claude_hooks/connection_pool.py +25 -4
  35. claude_mpm/hooks/claude_hooks/event_handlers.py +81 -19
  36. claude_mpm/hooks/claude_hooks/hook_handler.py +125 -163
  37. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +398 -0
  38. claude_mpm/hooks/claude_hooks/response_tracking.py +10 -0
  39. claude_mpm/services/agents/deployment/agent_deployment.py +34 -48
  40. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -1
  41. claude_mpm/services/agents/deployment/agent_template_builder.py +20 -11
  42. claude_mpm/services/agents/deployment/agent_version_manager.py +4 -1
  43. claude_mpm/services/agents/deployment/agents_directory_resolver.py +10 -25
  44. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +396 -13
  45. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +3 -2
  46. claude_mpm/services/agents/deployment/strategies/system_strategy.py +10 -3
  47. claude_mpm/services/agents/deployment/strategies/user_strategy.py +10 -14
  48. claude_mpm/services/agents/deployment/system_instructions_deployer.py +8 -85
  49. claude_mpm/services/agents/memory/content_manager.py +98 -105
  50. claude_mpm/services/event_bus/__init__.py +18 -0
  51. claude_mpm/services/event_bus/config.py +165 -0
  52. claude_mpm/services/event_bus/event_bus.py +349 -0
  53. claude_mpm/services/event_bus/relay.py +297 -0
  54. claude_mpm/services/events/__init__.py +44 -0
  55. claude_mpm/services/events/consumers/__init__.py +18 -0
  56. claude_mpm/services/events/consumers/dead_letter.py +296 -0
  57. claude_mpm/services/events/consumers/logging.py +183 -0
  58. claude_mpm/services/events/consumers/metrics.py +242 -0
  59. claude_mpm/services/events/consumers/socketio.py +376 -0
  60. claude_mpm/services/events/core.py +470 -0
  61. claude_mpm/services/events/interfaces.py +230 -0
  62. claude_mpm/services/events/producers/__init__.py +14 -0
  63. claude_mpm/services/events/producers/hook.py +269 -0
  64. claude_mpm/services/events/producers/system.py +327 -0
  65. claude_mpm/services/mcp_gateway/auto_configure.py +372 -0
  66. claude_mpm/services/mcp_gateway/core/process_pool.py +411 -0
  67. claude_mpm/services/mcp_gateway/server/stdio_server.py +13 -0
  68. claude_mpm/services/monitor_build_service.py +345 -0
  69. claude_mpm/services/socketio/event_normalizer.py +667 -0
  70. claude_mpm/services/socketio/handlers/connection.py +81 -23
  71. claude_mpm/services/socketio/handlers/hook.py +14 -5
  72. claude_mpm/services/socketio/migration_utils.py +329 -0
  73. claude_mpm/services/socketio/server/broadcaster.py +26 -33
  74. claude_mpm/services/socketio/server/core.py +29 -5
  75. claude_mpm/services/socketio/server/eventbus_integration.py +189 -0
  76. claude_mpm/services/socketio/server/main.py +25 -0
  77. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/METADATA +28 -9
  78. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/RECORD +82 -56
  79. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/WHEEL +0 -0
  80. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/entry_points.txt +0 -0
  81. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/licenses/LICENSE +0 -0
  82. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,372 @@
1
+ """
2
+ MCP Gateway Auto-Configuration Service
3
+ ======================================
4
+
5
+ Provides automatic MCP configuration for pipx installations with user consent.
6
+ Detects unconfigured MCP setups and offers one-time configuration prompts.
7
+
8
+ WHY: Users installing via pipx should have MCP work out-of-the-box with minimal
9
+ friction. This service detects unconfigured installations and offers automatic
10
+ setup with user consent.
11
+
12
+ DESIGN DECISIONS:
13
+ - Only prompts once (saves preference to avoid repeated prompts)
14
+ - Quick timeout with safe default (no configuration)
15
+ - Non-intrusive with environment variable override
16
+ - Creates backups before modifying any configuration
17
+ - Validates JSON before and after modifications
18
+ """
19
+
20
+ import json
21
+ import os
22
+ import sys
23
+ import time
24
+ from datetime import datetime
25
+ from pathlib import Path
26
+ from typing import Optional, Dict, Any, Tuple
27
+
28
+ from claude_mpm.core.logger import get_logger
29
+ from claude_mpm.config.paths import paths
30
+
31
+
32
+ class MCPAutoConfigurator:
33
+ """
34
+ Handles automatic MCP configuration for pipx installations.
35
+
36
+ Provides a one-time prompt to configure MCP Gateway with user consent,
37
+ making the experience seamless for pipx users while respecting choice.
38
+ """
39
+
40
+ def __init__(self):
41
+ """Initialize the auto-configurator."""
42
+ self.logger = get_logger("MCPAutoConfig")
43
+ self.config_dir = paths.claude_mpm_dir_hidden
44
+ self.preference_file = self.config_dir / "mcp_auto_config_preference.json"
45
+ self.claude_config_path = Path.home() / ".claude.json"
46
+
47
+ def should_auto_configure(self) -> bool:
48
+ """
49
+ Check if auto-configuration should be attempted.
50
+
51
+ Returns:
52
+ True if auto-configuration should be offered, False otherwise
53
+ """
54
+ # Check environment variable override
55
+ if os.environ.get("CLAUDE_MPM_NO_AUTO_CONFIG"):
56
+ self.logger.debug("Auto-configuration disabled via environment variable")
57
+ return False
58
+
59
+ # Check if already configured
60
+ if self._is_mcp_configured():
61
+ self.logger.debug("MCP already configured")
62
+ return False
63
+
64
+ # Check if this is a pipx installation
65
+ if not self._is_pipx_installation():
66
+ self.logger.debug("Not a pipx installation")
67
+ return False
68
+
69
+ # Check if we've already asked
70
+ if self._has_user_preference():
71
+ self.logger.debug("User preference already saved")
72
+ return False
73
+
74
+ return True
75
+
76
+ def _is_mcp_configured(self) -> bool:
77
+ """Check if MCP is already configured in Claude Code."""
78
+ if not self.claude_config_path.exists():
79
+ return False
80
+
81
+ try:
82
+ with open(self.claude_config_path, 'r') as f:
83
+ config = json.load(f)
84
+
85
+ # Check if claude-mpm-gateway is configured
86
+ mcp_servers = config.get("mcpServers", {})
87
+ return "claude-mpm-gateway" in mcp_servers
88
+
89
+ except (json.JSONDecodeError, IOError):
90
+ return False
91
+
92
+ def _is_pipx_installation(self) -> bool:
93
+ """Check if claude-mpm is installed via pipx."""
94
+ # Check if running from pipx virtual environment
95
+ if "pipx" in sys.executable.lower():
96
+ return True
97
+
98
+ # Check module path
99
+ try:
100
+ import claude_mpm
101
+ module_path = Path(claude_mpm.__file__).parent
102
+ if "pipx" in str(module_path):
103
+ return True
104
+ except Exception:
105
+ pass
106
+
107
+ # Check for pipx in PATH for claude-mpm command
108
+ try:
109
+ import subprocess
110
+ import platform
111
+
112
+ # Use appropriate command for OS
113
+ if platform.system() == "Windows":
114
+ cmd = ["where", "claude-mpm"]
115
+ else:
116
+ cmd = ["which", "claude-mpm"]
117
+
118
+ result = subprocess.run(
119
+ cmd,
120
+ capture_output=True,
121
+ text=True,
122
+ timeout=2
123
+ )
124
+ if result.returncode == 0 and "pipx" in result.stdout:
125
+ return True
126
+ except Exception:
127
+ pass
128
+
129
+ return False
130
+
131
+ def _has_user_preference(self) -> bool:
132
+ """Check if user has already been asked about auto-configuration."""
133
+ if not self.preference_file.exists():
134
+ return False
135
+
136
+ try:
137
+ with open(self.preference_file, 'r') as f:
138
+ prefs = json.load(f)
139
+ return prefs.get("asked", False)
140
+ except (json.JSONDecodeError, IOError):
141
+ return False
142
+
143
+ def _save_user_preference(self, choice: str):
144
+ """Save user's preference to avoid asking again."""
145
+ self.config_dir.mkdir(parents=True, exist_ok=True)
146
+
147
+ prefs = {
148
+ "asked": True,
149
+ "choice": choice,
150
+ "timestamp": datetime.now().isoformat()
151
+ }
152
+
153
+ try:
154
+ with open(self.preference_file, 'w') as f:
155
+ json.dump(prefs, f, indent=2)
156
+ except Exception as e:
157
+ self.logger.debug(f"Could not save preference: {e}")
158
+
159
+ def prompt_user(self, timeout: int = 10) -> Optional[bool]:
160
+ """
161
+ Prompt user for auto-configuration with timeout.
162
+
163
+ Args:
164
+ timeout: Seconds to wait for response (default 10)
165
+
166
+ Returns:
167
+ True if user agrees, False if declines, None if timeout
168
+ """
169
+ print("\n" + "="*60)
170
+ print("🔧 MCP Gateway Configuration")
171
+ print("="*60)
172
+ print("\nClaude MPM can automatically configure MCP Gateway for")
173
+ print("Claude Code integration. This enables advanced features:")
174
+ print(" • File analysis and summarization")
175
+ print(" • System diagnostics")
176
+ print(" • Ticket management")
177
+ print(" • And more...")
178
+ print("\nWould you like to configure it now? (y/n)")
179
+ print(f"(Auto-declining in {timeout} seconds)")
180
+
181
+ # Use threading for cross-platform timeout support
182
+ import threading
183
+ try:
184
+ # Python 3.7+ has queue built-in
185
+ import queue
186
+ except ImportError:
187
+ # Python 2.x fallback
188
+ import Queue as queue
189
+
190
+ user_input = None
191
+
192
+ def get_input():
193
+ nonlocal user_input
194
+ try:
195
+ user_input = input("> ").strip().lower()
196
+ except (EOFError, KeyboardInterrupt):
197
+ user_input = 'n'
198
+
199
+ # Start input thread
200
+ input_thread = threading.Thread(target=get_input)
201
+ input_thread.daemon = True
202
+ input_thread.start()
203
+
204
+ # Wait for input or timeout
205
+ input_thread.join(timeout)
206
+
207
+ if input_thread.is_alive():
208
+ # Timed out
209
+ print("\n(Timed out - declining)")
210
+ return None
211
+ else:
212
+ # Got input
213
+ if user_input in ['y', 'yes']:
214
+ return True
215
+ else:
216
+ return False
217
+
218
+ def auto_configure(self) -> bool:
219
+ """
220
+ Perform automatic MCP configuration.
221
+
222
+ Returns:
223
+ True if configuration successful, False otherwise
224
+ """
225
+ try:
226
+ # Create backup if config exists
227
+ if self.claude_config_path.exists():
228
+ backup_path = self._create_backup()
229
+ if backup_path:
230
+ print(f"✅ Backup created: {backup_path}")
231
+
232
+ # Load or create configuration
233
+ config = self._load_or_create_config()
234
+
235
+ # Add MCP Gateway configuration
236
+ if "mcpServers" not in config:
237
+ config["mcpServers"] = {}
238
+
239
+ # Find claude-mpm executable
240
+ executable = self._find_claude_mpm_executable()
241
+ if not executable:
242
+ print("❌ Could not find claude-mpm executable")
243
+ return False
244
+
245
+ # Configure MCP server
246
+ config["mcpServers"]["claude-mpm-gateway"] = {
247
+ "command": str(executable),
248
+ "args": ["mcp", "server"],
249
+ "env": {
250
+ "MCP_MODE": "production"
251
+ }
252
+ }
253
+
254
+ # Save configuration
255
+ with open(self.claude_config_path, 'w') as f:
256
+ json.dump(config, f, indent=2)
257
+
258
+ print(f"✅ Configuration saved to: {self.claude_config_path}")
259
+ print("\n🎉 MCP Gateway configured successfully!")
260
+ print("\nNext steps:")
261
+ print("1. Restart Claude Code (if running)")
262
+ print("2. Look for the MCP icon in the interface")
263
+ print("3. Try @claude-mpm-gateway in a conversation")
264
+
265
+ return True
266
+
267
+ except Exception as e:
268
+ self.logger.error(f"Auto-configuration failed: {e}")
269
+ print(f"❌ Configuration failed: {e}")
270
+ print("\nYou can configure manually with:")
271
+ print(" claude-mpm mcp install")
272
+ return False
273
+
274
+ def _create_backup(self) -> Optional[Path]:
275
+ """Create backup of existing configuration."""
276
+ try:
277
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
278
+ backup_path = self.claude_config_path.with_suffix(f'.backup.{timestamp}.json')
279
+
280
+ import shutil
281
+ shutil.copy2(self.claude_config_path, backup_path)
282
+ return backup_path
283
+
284
+ except Exception as e:
285
+ self.logger.debug(f"Could not create backup: {e}")
286
+ return None
287
+
288
+ def _load_or_create_config(self) -> Dict[str, Any]:
289
+ """Load existing config or create new one."""
290
+ if self.claude_config_path.exists():
291
+ try:
292
+ with open(self.claude_config_path, 'r') as f:
293
+ return json.load(f)
294
+ except json.JSONDecodeError:
295
+ self.logger.warning("Existing config is invalid JSON, creating new")
296
+
297
+ return {}
298
+
299
+ def _find_claude_mpm_executable(self) -> Optional[str]:
300
+ """Find the claude-mpm executable path."""
301
+ # Try direct command first
302
+ import subprocess
303
+ import platform
304
+
305
+ try:
306
+ # Use appropriate command for OS
307
+ if platform.system() == "Windows":
308
+ cmd = ["where", "claude-mpm"]
309
+ else:
310
+ cmd = ["which", "claude-mpm"]
311
+
312
+ result = subprocess.run(
313
+ cmd,
314
+ capture_output=True,
315
+ text=True,
316
+ timeout=2
317
+ )
318
+ if result.returncode == 0:
319
+ executable_path = result.stdout.strip()
320
+ # On Windows, 'where' might return multiple paths
321
+ if platform.system() == "Windows" and '\n' in executable_path:
322
+ executable_path = executable_path.split('\n')[0]
323
+ return executable_path
324
+ except Exception:
325
+ pass
326
+
327
+ # Try to find via shutil.which (more portable)
328
+ import shutil
329
+ claude_mpm_path = shutil.which("claude-mpm")
330
+ if claude_mpm_path:
331
+ return claude_mpm_path
332
+
333
+ # Fallback to Python module invocation
334
+ return sys.executable
335
+
336
+ def run(self) -> bool:
337
+ """
338
+ Main entry point for auto-configuration.
339
+
340
+ Returns:
341
+ True if configured (or already configured), False otherwise
342
+ """
343
+ if not self.should_auto_configure():
344
+ return True # Already configured or not applicable
345
+
346
+ # Prompt user
347
+ user_choice = self.prompt_user()
348
+
349
+ # Save preference to not ask again
350
+ self._save_user_preference("yes" if user_choice else "no")
351
+
352
+ if user_choice:
353
+ return self.auto_configure()
354
+ else:
355
+ if user_choice is False: # User explicitly said no
356
+ print("\n📝 You can configure MCP later with:")
357
+ print(" claude-mpm mcp install")
358
+ # If timeout (None), don't show additional message
359
+ return False
360
+
361
+
362
+ def check_and_configure_mcp() -> bool:
363
+ """
364
+ Check and potentially configure MCP for pipx installations.
365
+
366
+ This is the main entry point called during CLI initialization.
367
+
368
+ Returns:
369
+ True if MCP is configured (or configuration was successful), False otherwise
370
+ """
371
+ configurator = MCPAutoConfigurator()
372
+ return configurator.run()