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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_PM.md +41 -8
- claude_mpm/agents/PM_INSTRUCTIONS.md +85 -43
- claude_mpm/agents/templates/clerk-ops.json +223 -0
- claude_mpm/agents/templates/data_engineer.json +41 -5
- claude_mpm/agents/templates/php-engineer.json +185 -0
- claude_mpm/agents/templates/research.json +20 -8
- claude_mpm/agents/templates/web_qa.json +25 -10
- claude_mpm/cli/__init__.py +41 -2
- claude_mpm/cli/commands/agents.py +2 -2
- claude_mpm/cli/commands/analyze.py +4 -4
- claude_mpm/cli/commands/cleanup.py +7 -7
- claude_mpm/cli/commands/configure_tui.py +2 -2
- claude_mpm/cli/commands/debug.py +2 -2
- claude_mpm/cli/commands/info.py +3 -4
- claude_mpm/cli/commands/mcp.py +8 -6
- claude_mpm/cli/commands/mcp_command_router.py +11 -0
- claude_mpm/cli/commands/mcp_config.py +157 -0
- claude_mpm/cli/commands/mcp_external_commands.py +241 -0
- claude_mpm/cli/commands/mcp_install_commands.py +73 -32
- claude_mpm/cli/commands/mcp_setup_external.py +829 -0
- claude_mpm/cli/commands/run.py +73 -3
- claude_mpm/cli/commands/search.py +285 -0
- claude_mpm/cli/parsers/base_parser.py +13 -0
- claude_mpm/cli/parsers/mcp_parser.py +17 -0
- claude_mpm/cli/parsers/run_parser.py +5 -0
- claude_mpm/cli/parsers/search_parser.py +239 -0
- claude_mpm/cli/startup_logging.py +20 -7
- claude_mpm/constants.py +1 -0
- claude_mpm/core/unified_agent_registry.py +7 -0
- claude_mpm/hooks/instruction_reinforcement.py +295 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +28 -13
- claude_mpm/services/agents/deployment/agent_discovery_service.py +16 -6
- claude_mpm/services/agents/deployment/deployment_wrapper.py +59 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +6 -4
- claude_mpm/services/cli/agent_cleanup_service.py +5 -0
- claude_mpm/services/mcp_config_manager.py +294 -0
- claude_mpm/services/mcp_gateway/config/configuration.py +17 -0
- claude_mpm/services/mcp_gateway/main.py +38 -0
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +390 -0
- claude_mpm/utils/log_cleanup.py +17 -17
- claude_mpm/utils/subprocess_utils.py +6 -6
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/METADATA +24 -1
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/RECORD +48 -39
- claude_mpm/agents/templates/agent-manager.md +0 -619
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/WHEEL +0 -0
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/licenses/LICENSE +0 -0
- {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:
|