claude-mpm 4.3.6__py3-none-any.whl → 4.3.12__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 (49) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_PM.md +41 -8
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +85 -43
  4. claude_mpm/agents/templates/clerk-ops.json +223 -0
  5. claude_mpm/agents/templates/data_engineer.json +41 -5
  6. claude_mpm/agents/templates/php-engineer.json +185 -0
  7. claude_mpm/agents/templates/research.json +20 -8
  8. claude_mpm/agents/templates/web_qa.json +25 -10
  9. claude_mpm/cli/__init__.py +41 -2
  10. claude_mpm/cli/commands/agents.py +2 -2
  11. claude_mpm/cli/commands/analyze.py +4 -4
  12. claude_mpm/cli/commands/cleanup.py +7 -7
  13. claude_mpm/cli/commands/configure_tui.py +2 -2
  14. claude_mpm/cli/commands/debug.py +2 -2
  15. claude_mpm/cli/commands/info.py +3 -4
  16. claude_mpm/cli/commands/mcp.py +8 -6
  17. claude_mpm/cli/commands/mcp_command_router.py +11 -0
  18. claude_mpm/cli/commands/mcp_config.py +157 -0
  19. claude_mpm/cli/commands/mcp_external_commands.py +241 -0
  20. claude_mpm/cli/commands/mcp_install_commands.py +73 -32
  21. claude_mpm/cli/commands/mcp_setup_external.py +829 -0
  22. claude_mpm/cli/commands/run.py +73 -3
  23. claude_mpm/cli/commands/search.py +285 -0
  24. claude_mpm/cli/parsers/base_parser.py +13 -0
  25. claude_mpm/cli/parsers/mcp_parser.py +17 -0
  26. claude_mpm/cli/parsers/run_parser.py +5 -0
  27. claude_mpm/cli/parsers/search_parser.py +239 -0
  28. claude_mpm/cli/startup_logging.py +20 -7
  29. claude_mpm/constants.py +1 -0
  30. claude_mpm/core/unified_agent_registry.py +7 -0
  31. claude_mpm/hooks/instruction_reinforcement.py +295 -0
  32. claude_mpm/services/agents/deployment/agent_deployment.py +28 -13
  33. claude_mpm/services/agents/deployment/agent_discovery_service.py +16 -6
  34. claude_mpm/services/agents/deployment/deployment_wrapper.py +59 -0
  35. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +6 -4
  36. claude_mpm/services/cli/agent_cleanup_service.py +5 -0
  37. claude_mpm/services/mcp_config_manager.py +294 -0
  38. claude_mpm/services/mcp_gateway/config/configuration.py +17 -0
  39. claude_mpm/services/mcp_gateway/main.py +38 -0
  40. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +390 -0
  41. claude_mpm/utils/log_cleanup.py +17 -17
  42. claude_mpm/utils/subprocess_utils.py +6 -6
  43. {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/METADATA +24 -1
  44. {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/RECORD +48 -39
  45. claude_mpm/agents/templates/agent-manager.md +0 -619
  46. {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/WHEEL +0 -0
  47. {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/entry_points.txt +0 -0
  48. {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/licenses/LICENSE +0 -0
  49. {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,294 @@
1
+ """
2
+ MCP Configuration Manager
3
+ ========================
4
+
5
+ Manages MCP service configurations, preferring pipx installations
6
+ over local virtual environments for better isolation and management.
7
+
8
+ This module provides utilities to detect, configure, and validate
9
+ MCP service installations.
10
+ """
11
+
12
+ import json
13
+ import os
14
+ import subprocess
15
+ from pathlib import Path
16
+ from typing import Dict, Optional, Tuple
17
+
18
+ from ..core.logger import get_logger
19
+
20
+
21
+ class MCPConfigManager:
22
+ """Manages MCP service configurations with pipx preference."""
23
+
24
+ # Standard MCP services that should use pipx
25
+ PIPX_SERVICES = {
26
+ "mcp-vector-search",
27
+ "mcp-browser",
28
+ "mcp-ticketer",
29
+ }
30
+
31
+ def __init__(self):
32
+ """Initialize the MCP configuration manager."""
33
+ self.logger = get_logger(__name__)
34
+ self.pipx_base = Path.home() / ".local" / "pipx" / "venvs"
35
+ self.project_root = Path.cwd()
36
+
37
+ def detect_service_path(self, service_name: str) -> Optional[str]:
38
+ """
39
+ Detect the best path for an MCP service.
40
+
41
+ Priority order:
42
+ 1. Pipx installation (preferred)
43
+ 2. System PATH (likely from pipx)
44
+ 3. Local venv (fallback)
45
+
46
+ Args:
47
+ service_name: Name of the MCP service
48
+
49
+ Returns:
50
+ Path to the service executable or None if not found
51
+ """
52
+ # Check pipx installation first
53
+ pipx_path = self._check_pipx_installation(service_name)
54
+ if pipx_path:
55
+ self.logger.debug(f"Found {service_name} via pipx: {pipx_path}")
56
+ return pipx_path
57
+
58
+ # Check system PATH
59
+ system_path = self._check_system_path(service_name)
60
+ if system_path:
61
+ self.logger.debug(f"Found {service_name} in PATH: {system_path}")
62
+ return system_path
63
+
64
+ # Fallback to local venv
65
+ local_path = self._check_local_venv(service_name)
66
+ if local_path:
67
+ self.logger.warning(
68
+ f"Using local venv for {service_name} (consider installing via pipx)"
69
+ )
70
+ return local_path
71
+
72
+ self.logger.warning(f"Service {service_name} not found")
73
+ return None
74
+
75
+ def _check_pipx_installation(self, service_name: str) -> Optional[str]:
76
+ """Check if service is installed via pipx."""
77
+ pipx_venv = self.pipx_base / service_name
78
+
79
+ if not pipx_venv.exists():
80
+ return None
81
+
82
+ # Special handling for mcp-vector-search (needs Python interpreter)
83
+ if service_name == "mcp-vector-search":
84
+ python_bin = pipx_venv / "bin" / "python"
85
+ if python_bin.exists() and python_bin.is_file():
86
+ return str(python_bin)
87
+ else:
88
+ # Other services use direct binary
89
+ service_bin = pipx_venv / "bin" / service_name
90
+ if service_bin.exists() and service_bin.is_file():
91
+ return str(service_bin)
92
+
93
+ return None
94
+
95
+ def _check_system_path(self, service_name: str) -> Optional[str]:
96
+ """Check if service is available in system PATH."""
97
+ try:
98
+ result = subprocess.run(
99
+ ["which", service_name],
100
+ capture_output=True,
101
+ text=True,
102
+ check=False,
103
+ )
104
+ if result.returncode == 0:
105
+ path = result.stdout.strip()
106
+ # Verify it's from pipx
107
+ if "/.local/bin/" in path or "/pipx/" in path:
108
+ return path
109
+ except Exception as e:
110
+ self.logger.debug(f"Error checking system PATH: {e}")
111
+
112
+ return None
113
+
114
+ def _check_local_venv(self, service_name: str) -> Optional[str]:
115
+ """Check for local virtual environment installation (fallback)."""
116
+ # Common local development paths
117
+ possible_paths = [
118
+ Path.home() / "Projects" / "managed" / service_name / ".venv" / "bin",
119
+ self.project_root / ".venv" / "bin",
120
+ self.project_root / "venv" / "bin",
121
+ ]
122
+
123
+ for base_path in possible_paths:
124
+ if service_name == "mcp-vector-search":
125
+ python_bin = base_path / "python"
126
+ if python_bin.exists():
127
+ return str(python_bin)
128
+ else:
129
+ service_bin = base_path / service_name
130
+ if service_bin.exists():
131
+ return str(service_bin)
132
+
133
+ return None
134
+
135
+ def generate_service_config(self, service_name: str) -> Optional[Dict]:
136
+ """
137
+ Generate configuration for a specific MCP service.
138
+
139
+ Args:
140
+ service_name: Name of the MCP service
141
+
142
+ Returns:
143
+ Service configuration dict or None if service not found
144
+ """
145
+ service_path = self.detect_service_path(service_name)
146
+ if not service_path:
147
+ return None
148
+
149
+ config = {
150
+ "type": "stdio",
151
+ "command": service_path,
152
+ }
153
+
154
+ # Service-specific configurations
155
+ if service_name == "mcp-vector-search":
156
+ config["args"] = [
157
+ "-m",
158
+ "mcp_vector_search.mcp.server",
159
+ str(self.project_root),
160
+ ]
161
+ config["env"] = {}
162
+ elif service_name == "mcp-browser":
163
+ config["args"] = ["mcp"]
164
+ config["env"] = {
165
+ "MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser")
166
+ }
167
+ elif service_name == "mcp-ticketer":
168
+ config["args"] = ["mcp"]
169
+ else:
170
+ # Generic config for unknown services
171
+ config["args"] = []
172
+
173
+ return config
174
+
175
+ def update_mcp_config(self, force_pipx: bool = True) -> Tuple[bool, str]:
176
+ """
177
+ Update the .mcp.json configuration file.
178
+
179
+ Args:
180
+ force_pipx: If True, only use pipx installations
181
+
182
+ Returns:
183
+ Tuple of (success, message)
184
+ """
185
+ mcp_config_path = self.project_root / ".mcp.json"
186
+
187
+ # Load existing config if it exists
188
+ existing_config = {}
189
+ if mcp_config_path.exists():
190
+ try:
191
+ with open(mcp_config_path, "r") as f:
192
+ existing_config = json.load(f)
193
+ except Exception as e:
194
+ self.logger.error(f"Error reading existing config: {e}")
195
+
196
+ # Generate new configurations
197
+ new_config = {"mcpServers": {}}
198
+ missing_services = []
199
+
200
+ for service_name in self.PIPX_SERVICES:
201
+ config = self.generate_service_config(service_name)
202
+ if config:
203
+ new_config["mcpServers"][service_name] = config
204
+ elif force_pipx:
205
+ missing_services.append(service_name)
206
+ else:
207
+ # Keep existing config if not forcing pipx
208
+ if service_name in existing_config.get("mcpServers", {}):
209
+ new_config["mcpServers"][service_name] = existing_config[
210
+ "mcpServers"
211
+ ][service_name]
212
+
213
+ # Add any additional services from existing config
214
+ for service_name, config in existing_config.get("mcpServers", {}).items():
215
+ if service_name not in new_config["mcpServers"]:
216
+ new_config["mcpServers"][service_name] = config
217
+
218
+ # Write the updated configuration
219
+ try:
220
+ with open(mcp_config_path, "w") as f:
221
+ json.dump(new_config, f, indent=2)
222
+
223
+ if missing_services:
224
+ message = f"Updated .mcp.json. Missing services (install via pipx): {', '.join(missing_services)}"
225
+ return True, message
226
+ else:
227
+ return True, "Successfully updated .mcp.json with pipx paths"
228
+ except Exception as e:
229
+ return False, f"Failed to update .mcp.json: {e}"
230
+
231
+ def validate_configuration(self) -> Dict[str, bool]:
232
+ """
233
+ Validate that all configured MCP services are accessible.
234
+
235
+ Returns:
236
+ Dict mapping service names to availability status
237
+ """
238
+ mcp_config_path = self.project_root / ".mcp.json"
239
+ if not mcp_config_path.exists():
240
+ return {}
241
+
242
+ try:
243
+ with open(mcp_config_path, "r") as f:
244
+ config = json.load(f)
245
+ except Exception as e:
246
+ self.logger.error(f"Error reading config: {e}")
247
+ return {}
248
+
249
+ results = {}
250
+ for service_name, service_config in config.get("mcpServers", {}).items():
251
+ command_path = service_config.get("command", "")
252
+ results[service_name] = Path(command_path).exists()
253
+
254
+ return results
255
+
256
+ def install_missing_services(self) -> Tuple[bool, str]:
257
+ """
258
+ Install missing MCP services via pipx.
259
+
260
+ Returns:
261
+ Tuple of (success, message)
262
+ """
263
+ missing = []
264
+ for service_name in self.PIPX_SERVICES:
265
+ if not self.detect_service_path(service_name):
266
+ missing.append(service_name)
267
+
268
+ if not missing:
269
+ return True, "All MCP services are already installed"
270
+
271
+ installed = []
272
+ failed = []
273
+
274
+ for service_name in missing:
275
+ try:
276
+ self.logger.info(f"Installing {service_name} via pipx...")
277
+ result = subprocess.run(
278
+ ["pipx", "install", service_name],
279
+ capture_output=True,
280
+ text=True,
281
+ check=True,
282
+ )
283
+ installed.append(service_name)
284
+ self.logger.info(f"Successfully installed {service_name}")
285
+ except subprocess.CalledProcessError as e:
286
+ failed.append(service_name)
287
+ self.logger.error(f"Failed to install {service_name}: {e.stderr}")
288
+
289
+ if failed:
290
+ return False, f"Failed to install: {', '.join(failed)}"
291
+ elif installed:
292
+ return True, f"Successfully installed: {', '.join(installed)}"
293
+ else:
294
+ return True, "No services needed installation"
@@ -60,6 +60,23 @@ class MCPConfiguration(BaseMCPService, IMCPConfiguration):
60
60
  "timeout_default": 30, # seconds
61
61
  "max_concurrent": 10,
62
62
  },
63
+ "external_services": {
64
+ "enabled": True,
65
+ "auto_install": True,
66
+ "services": [
67
+ {
68
+ "name": "mcp-vector-search",
69
+ "package": "mcp-vector-search",
70
+ "enabled": True,
71
+ "auto_index": True,
72
+ },
73
+ {
74
+ "name": "mcp-browser",
75
+ "package": "mcp-browser",
76
+ "enabled": True,
77
+ },
78
+ ],
79
+ },
63
80
  "logging": {
64
81
  "level": "INFO",
65
82
  "file": "~/.claude/logs/mcp_gateway.log",
@@ -115,6 +115,14 @@ except ImportError:
115
115
  # Unified ticket tool is optional
116
116
  UnifiedTicketTool = None
117
117
 
118
+ try:
119
+ from claude_mpm.services.mcp_gateway.tools.external_mcp_services import (
120
+ ExternalMCPServiceManager,
121
+ )
122
+ except ImportError:
123
+ # External MCP services are optional
124
+ ExternalMCPServiceManager = None
125
+
118
126
  # Manager module removed - using simplified architecture
119
127
 
120
128
 
@@ -148,6 +156,7 @@ class MCPGatewayOrchestrator:
148
156
  self.registry: Optional[ToolRegistry] = None
149
157
  self.communication: Optional[StdioHandler] = None
150
158
  self.configuration: Optional[MCPConfiguration] = None
159
+ self.external_services: Optional[ExternalMCPServiceManager] = None
151
160
 
152
161
  # Shutdown handling
153
162
  self._shutdown_event = asyncio.Event()
@@ -199,6 +208,28 @@ class MCPGatewayOrchestrator:
199
208
  self.logger.warning(f"Failed to register some tools: {e}")
200
209
  # Continue - server can run with partial tools
201
210
 
211
+ # Initialize external MCP services if available
212
+ if ExternalMCPServiceManager is not None:
213
+ try:
214
+ self.logger.info("Initializing external MCP services...")
215
+ self.external_services = ExternalMCPServiceManager()
216
+ external_services = await self.external_services.initialize_services()
217
+
218
+ if external_services and self.registry:
219
+ for service in external_services:
220
+ try:
221
+ if self.registry.register_tool(service, category="external"):
222
+ self.logger.info(f"Registered external service: {service.service_name}")
223
+ else:
224
+ self.logger.warning(f"Failed to register external service: {service.service_name}")
225
+ except Exception as e:
226
+ self.logger.warning(f"Error registering {service.service_name}: {e}")
227
+
228
+ self.logger.info(f"Initialized {len(external_services)} external MCP services")
229
+ except Exception as e:
230
+ self.logger.warning(f"Failed to initialize external MCP services: {e}")
231
+ self.external_services = None
232
+
202
233
  # Initialize communication handler with fallback
203
234
  try:
204
235
  self.communication = StdioHandler()
@@ -371,6 +402,13 @@ class MCPGatewayOrchestrator:
371
402
  except Exception as e:
372
403
  self.logger.warning(f"Error during communication shutdown: {e}")
373
404
 
405
+ # Shutdown external services
406
+ if self.external_services:
407
+ try:
408
+ await self.external_services.shutdown()
409
+ except Exception as e:
410
+ self.logger.warning(f"Error during external services shutdown: {e}")
411
+
374
412
  self.logger.info("MCP Gateway shutdown complete")
375
413
 
376
414
  except Exception as e: