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,829 @@
|
|
|
1
|
+
"""MCP external services setup module.
|
|
2
|
+
|
|
3
|
+
This module handles the registration of external MCP services
|
|
4
|
+
(mcp-vector-search, mcp-browser) as separate MCP servers in Claude Desktop.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Dict, Optional, List, Tuple
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MCPExternalServicesSetup:
|
|
16
|
+
"""Handles setup of external MCP services in Claude Desktop configuration."""
|
|
17
|
+
|
|
18
|
+
def get_project_services(self, project_path: Path) -> Dict:
|
|
19
|
+
"""Get external services configuration for the current project.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
project_path: Path to the project directory
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Dict: Configuration for external services
|
|
26
|
+
"""
|
|
27
|
+
# Detect best command paths for services
|
|
28
|
+
mcp_browser_config = self._get_best_service_config("mcp-browser", project_path)
|
|
29
|
+
mcp_vector_search_config = self._get_best_service_config("mcp-vector-search", project_path)
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
"mcp-vector-search": {
|
|
33
|
+
"package_name": "mcp-vector-search",
|
|
34
|
+
"module_name": "mcp_vector_search",
|
|
35
|
+
"description": "Semantic code search with vector embeddings",
|
|
36
|
+
"config": mcp_vector_search_config
|
|
37
|
+
},
|
|
38
|
+
"mcp-browser": {
|
|
39
|
+
"package_name": "mcp-browser",
|
|
40
|
+
"module_name": "mcp_browser",
|
|
41
|
+
"description": "Web browsing and content extraction",
|
|
42
|
+
"config": mcp_browser_config
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
def _get_best_service_config(self, service_name: str, project_path: Path) -> Dict:
|
|
47
|
+
"""Get the best configuration for a service.
|
|
48
|
+
|
|
49
|
+
Priority order:
|
|
50
|
+
1. Pipx installation (preferred for isolation and reliability)
|
|
51
|
+
2. Local development installation (e.g., ~/Projects/managed/)
|
|
52
|
+
3. Local project venv
|
|
53
|
+
4. System Python
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
service_name: Name of the service
|
|
57
|
+
project_path: Path to the project directory
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Dict: Service configuration
|
|
61
|
+
"""
|
|
62
|
+
# First try pipx (preferred for isolation and reliability)
|
|
63
|
+
pipx_config = self._get_pipx_config(service_name, project_path)
|
|
64
|
+
if pipx_config:
|
|
65
|
+
# Verify the executable actually exists before using it
|
|
66
|
+
command = pipx_config.get("command", "")
|
|
67
|
+
if Path(command).exists():
|
|
68
|
+
return pipx_config
|
|
69
|
+
|
|
70
|
+
# Then try local development installations
|
|
71
|
+
local_dev_config = self._get_local_dev_config(service_name, project_path)
|
|
72
|
+
if local_dev_config:
|
|
73
|
+
# Verify the command exists
|
|
74
|
+
command = local_dev_config.get("command", "")
|
|
75
|
+
if Path(command).exists():
|
|
76
|
+
return local_dev_config
|
|
77
|
+
|
|
78
|
+
# Then try local venv if exists
|
|
79
|
+
venv_config = self._get_venv_config(service_name, project_path)
|
|
80
|
+
if venv_config:
|
|
81
|
+
command = venv_config.get("command", "")
|
|
82
|
+
if Path(command).exists():
|
|
83
|
+
return venv_config
|
|
84
|
+
|
|
85
|
+
# Fall back to system Python
|
|
86
|
+
return self._get_system_config(service_name, project_path)
|
|
87
|
+
|
|
88
|
+
def _get_local_dev_config(self, service_name: str, project_path: Path) -> Optional[Dict]:
|
|
89
|
+
"""Get configuration for a locally developed service.
|
|
90
|
+
|
|
91
|
+
Checks common development locations like ~/Projects/managed/
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
service_name: Name of the service
|
|
95
|
+
project_path: Path to the project directory
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Configuration dict or None if not available
|
|
99
|
+
"""
|
|
100
|
+
# Check common local development locations
|
|
101
|
+
dev_locations = [
|
|
102
|
+
Path.home() / "Projects" / "managed" / service_name,
|
|
103
|
+
Path.home() / "Projects" / service_name,
|
|
104
|
+
Path.home() / "Development" / service_name,
|
|
105
|
+
Path.home() / "dev" / service_name,
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
for dev_path in dev_locations:
|
|
109
|
+
if not dev_path.exists():
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
# Check for venv in the development location
|
|
113
|
+
venv_paths = [
|
|
114
|
+
dev_path / ".venv" / "bin" / "python",
|
|
115
|
+
dev_path / "venv" / "bin" / "python",
|
|
116
|
+
dev_path / "env" / "bin" / "python",
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
for venv_python in venv_paths:
|
|
120
|
+
if venv_python.exists():
|
|
121
|
+
# Special handling for mcp-browser
|
|
122
|
+
if service_name == "mcp-browser":
|
|
123
|
+
# First check for mcp-browser binary in the same directory as python
|
|
124
|
+
mcp_browser_binary = venv_python.parent / "mcp-browser"
|
|
125
|
+
if mcp_browser_binary.exists():
|
|
126
|
+
return {
|
|
127
|
+
"type": "stdio",
|
|
128
|
+
"command": str(mcp_browser_binary),
|
|
129
|
+
"args": ["mcp"],
|
|
130
|
+
"env": {
|
|
131
|
+
"MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser")
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# Then check for mcp-server.py
|
|
136
|
+
mcp_server = dev_path / "mcp-server.py"
|
|
137
|
+
if mcp_server.exists():
|
|
138
|
+
return {
|
|
139
|
+
"type": "stdio",
|
|
140
|
+
"command": str(venv_python),
|
|
141
|
+
"args": [str(mcp_server)],
|
|
142
|
+
"env": {
|
|
143
|
+
"MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser"),
|
|
144
|
+
"PYTHONPATH": str(dev_path)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Check if the package is installed in this venv
|
|
149
|
+
module_name = service_name.replace("-", "_")
|
|
150
|
+
try:
|
|
151
|
+
result = subprocess.run(
|
|
152
|
+
[str(venv_python), "-c", f"import {module_name}"],
|
|
153
|
+
capture_output=True,
|
|
154
|
+
timeout=5
|
|
155
|
+
)
|
|
156
|
+
if result.returncode == 0:
|
|
157
|
+
# Use special configuration for local dev
|
|
158
|
+
if service_name == "mcp-vector-search":
|
|
159
|
+
return {
|
|
160
|
+
"type": "stdio",
|
|
161
|
+
"command": str(venv_python),
|
|
162
|
+
"args": ["-m", "mcp_vector_search.mcp.server", str(project_path)],
|
|
163
|
+
"env": {}
|
|
164
|
+
}
|
|
165
|
+
elif service_name == "mcp-browser":
|
|
166
|
+
# Fallback for mcp-browser without mcp-server.py
|
|
167
|
+
return {
|
|
168
|
+
"type": "stdio",
|
|
169
|
+
"command": str(venv_python),
|
|
170
|
+
"args": ["-m", "mcp_browser", "mcp"],
|
|
171
|
+
"env": {
|
|
172
|
+
"MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser"),
|
|
173
|
+
"PYTHONPATH": str(dev_path)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
except:
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
def _get_venv_config(self, service_name: str, project_path: Path) -> Optional[Dict]:
|
|
182
|
+
"""Get configuration for a service in the local virtual environment.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
service_name: Name of the service
|
|
186
|
+
project_path: Path to the project directory
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Configuration dict or None if not available
|
|
190
|
+
"""
|
|
191
|
+
# Check common venv locations
|
|
192
|
+
venv_paths = [
|
|
193
|
+
project_path / "venv" / "bin" / "python",
|
|
194
|
+
project_path / ".venv" / "bin" / "python",
|
|
195
|
+
project_path / "env" / "bin" / "python",
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
for venv_python in venv_paths:
|
|
199
|
+
if venv_python.exists():
|
|
200
|
+
# Check if the package is installed in this venv
|
|
201
|
+
module_name = service_name.replace("-", "_")
|
|
202
|
+
try:
|
|
203
|
+
result = subprocess.run(
|
|
204
|
+
[str(venv_python), "-c", f"import {module_name}"],
|
|
205
|
+
capture_output=True,
|
|
206
|
+
timeout=5
|
|
207
|
+
)
|
|
208
|
+
if result.returncode == 0:
|
|
209
|
+
return self._create_service_config(service_name, str(venv_python), project_path)
|
|
210
|
+
except:
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
def _get_system_config(self, service_name: str, project_path: Path) -> Dict:
|
|
216
|
+
"""Get configuration using system Python.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
service_name: Name of the service
|
|
220
|
+
project_path: Path to the project directory
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Configuration dict
|
|
224
|
+
"""
|
|
225
|
+
return self._create_service_config(service_name, sys.executable, project_path)
|
|
226
|
+
|
|
227
|
+
def _create_service_config(self, service_name: str, python_path: str, project_path: Path) -> Dict:
|
|
228
|
+
"""Create service configuration for the given Python executable.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
service_name: Name of the service
|
|
232
|
+
python_path: Path to Python executable
|
|
233
|
+
project_path: Path to the project directory
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Configuration dict
|
|
237
|
+
"""
|
|
238
|
+
if service_name == "mcp-browser":
|
|
239
|
+
# Check if mcp-browser binary exists (for pipx installations)
|
|
240
|
+
binary_path = Path(python_path).parent / "mcp-browser"
|
|
241
|
+
if binary_path.exists():
|
|
242
|
+
return {
|
|
243
|
+
"type": "stdio",
|
|
244
|
+
"command": str(binary_path),
|
|
245
|
+
"args": ["mcp"],
|
|
246
|
+
"env": {"MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser")}
|
|
247
|
+
}
|
|
248
|
+
else:
|
|
249
|
+
# Use Python module invocation
|
|
250
|
+
return {
|
|
251
|
+
"type": "stdio",
|
|
252
|
+
"command": python_path,
|
|
253
|
+
"args": ["-m", "mcp_browser", "mcp"],
|
|
254
|
+
"env": {"MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser")}
|
|
255
|
+
}
|
|
256
|
+
elif service_name == "mcp-vector-search":
|
|
257
|
+
return {
|
|
258
|
+
"type": "stdio",
|
|
259
|
+
"command": python_path,
|
|
260
|
+
"args": ["-m", "mcp_vector_search.mcp.server", str(project_path)],
|
|
261
|
+
"env": {}
|
|
262
|
+
}
|
|
263
|
+
else:
|
|
264
|
+
# Generic configuration for other services
|
|
265
|
+
module_name = service_name.replace("-", "_")
|
|
266
|
+
return {
|
|
267
|
+
"type": "stdio",
|
|
268
|
+
"command": python_path,
|
|
269
|
+
"args": ["-m", module_name],
|
|
270
|
+
"env": {}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
def detect_mcp_installations(self) -> Dict[str, Dict]:
|
|
274
|
+
"""Detect all MCP service installations and their locations.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
Dict mapping service name to installation info:
|
|
278
|
+
{
|
|
279
|
+
"service-name": {
|
|
280
|
+
"type": "local_dev" | "pipx" | "venv" | "system" | "not_installed",
|
|
281
|
+
"path": "/path/to/installation",
|
|
282
|
+
"config": {...} # Ready-to-use configuration
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
"""
|
|
286
|
+
installations = {}
|
|
287
|
+
project_path = Path.cwd()
|
|
288
|
+
|
|
289
|
+
for service_name in ["mcp-browser", "mcp-vector-search"]:
|
|
290
|
+
# Try each detection method in priority order
|
|
291
|
+
local_dev_config = self._get_local_dev_config(service_name, project_path)
|
|
292
|
+
if local_dev_config:
|
|
293
|
+
installations[service_name] = {
|
|
294
|
+
"type": "local_dev",
|
|
295
|
+
"path": local_dev_config["command"],
|
|
296
|
+
"config": local_dev_config
|
|
297
|
+
}
|
|
298
|
+
continue
|
|
299
|
+
|
|
300
|
+
pipx_config = self._get_pipx_config(service_name, project_path)
|
|
301
|
+
if pipx_config:
|
|
302
|
+
# Verify the command actually exists before reporting it as available
|
|
303
|
+
command_path = Path(pipx_config["command"])
|
|
304
|
+
if command_path.exists():
|
|
305
|
+
installations[service_name] = {
|
|
306
|
+
"type": "pipx",
|
|
307
|
+
"path": pipx_config["command"],
|
|
308
|
+
"config": pipx_config
|
|
309
|
+
}
|
|
310
|
+
continue
|
|
311
|
+
|
|
312
|
+
venv_config = self._get_venv_config(service_name, project_path)
|
|
313
|
+
if venv_config:
|
|
314
|
+
installations[service_name] = {
|
|
315
|
+
"type": "venv",
|
|
316
|
+
"path": venv_config["command"],
|
|
317
|
+
"config": venv_config
|
|
318
|
+
}
|
|
319
|
+
continue
|
|
320
|
+
|
|
321
|
+
# Check if available in system Python
|
|
322
|
+
module_name = service_name.replace("-", "_")
|
|
323
|
+
if self._check_python_package(module_name):
|
|
324
|
+
system_config = self._get_system_config(service_name, project_path)
|
|
325
|
+
installations[service_name] = {
|
|
326
|
+
"type": "system",
|
|
327
|
+
"path": system_config["command"],
|
|
328
|
+
"config": system_config
|
|
329
|
+
}
|
|
330
|
+
else:
|
|
331
|
+
installations[service_name] = {
|
|
332
|
+
"type": "not_installed",
|
|
333
|
+
"path": None,
|
|
334
|
+
"config": None
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return installations
|
|
338
|
+
|
|
339
|
+
def update_mcp_json_with_detected(self, force: bool = False) -> bool:
|
|
340
|
+
"""Update .mcp.json with auto-detected service configurations.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
force: Whether to overwrite existing configurations
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
bool: True if configuration was updated successfully
|
|
347
|
+
"""
|
|
348
|
+
print("\n🔍 Auto-detecting MCP service installations...")
|
|
349
|
+
print("=" * 50)
|
|
350
|
+
|
|
351
|
+
installations = self.detect_mcp_installations()
|
|
352
|
+
|
|
353
|
+
# Display detected installations
|
|
354
|
+
for service_name, info in installations.items():
|
|
355
|
+
print(f"\n{service_name}:")
|
|
356
|
+
if info["type"] == "not_installed":
|
|
357
|
+
print(f" ❌ Not installed")
|
|
358
|
+
else:
|
|
359
|
+
type_emoji = {
|
|
360
|
+
"local_dev": "🔧",
|
|
361
|
+
"pipx": "📦",
|
|
362
|
+
"venv": "🐍",
|
|
363
|
+
"system": "💻"
|
|
364
|
+
}.get(info["type"], "❓")
|
|
365
|
+
print(f" {type_emoji} Type: {info['type']}")
|
|
366
|
+
print(f" 📍 Path: {info['path']}")
|
|
367
|
+
|
|
368
|
+
# Load current configuration
|
|
369
|
+
config_path = Path.cwd() / ".mcp.json"
|
|
370
|
+
config = self._load_config(config_path)
|
|
371
|
+
if not config:
|
|
372
|
+
print("\n❌ Failed to load configuration")
|
|
373
|
+
return False
|
|
374
|
+
|
|
375
|
+
# Update configurations
|
|
376
|
+
updated = False
|
|
377
|
+
for service_name, info in installations.items():
|
|
378
|
+
if info["type"] == "not_installed":
|
|
379
|
+
continue
|
|
380
|
+
|
|
381
|
+
# Check if already configured
|
|
382
|
+
if service_name in config.get("mcpServers", {}) and not force:
|
|
383
|
+
print(f"\n⚠️ {service_name} already configured, skipping (use --force to override)")
|
|
384
|
+
continue
|
|
385
|
+
|
|
386
|
+
# Update configuration
|
|
387
|
+
if "mcpServers" not in config:
|
|
388
|
+
config["mcpServers"] = {}
|
|
389
|
+
|
|
390
|
+
config["mcpServers"][service_name] = info["config"]
|
|
391
|
+
print(f"\n✅ Updated {service_name} configuration")
|
|
392
|
+
updated = True
|
|
393
|
+
|
|
394
|
+
# Save configuration if updated
|
|
395
|
+
if updated:
|
|
396
|
+
if self._save_config(config, config_path):
|
|
397
|
+
print("\n✅ Successfully updated .mcp.json with detected configurations")
|
|
398
|
+
return True
|
|
399
|
+
else:
|
|
400
|
+
print("\n❌ Failed to save configuration")
|
|
401
|
+
return False
|
|
402
|
+
else:
|
|
403
|
+
print("\n📌 No updates needed")
|
|
404
|
+
return True
|
|
405
|
+
|
|
406
|
+
def __init__(self, logger):
|
|
407
|
+
"""Initialize the external services setup handler."""
|
|
408
|
+
self.logger = logger
|
|
409
|
+
self._pipx_path = Path.home() / ".local" / "pipx" / "venvs"
|
|
410
|
+
|
|
411
|
+
def setup_external_services(self, force: bool = False) -> bool:
|
|
412
|
+
"""Setup external MCP services in project .mcp.json file.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
force: Whether to overwrite existing configurations
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
bool: True if all services were set up successfully
|
|
419
|
+
"""
|
|
420
|
+
print("\n📦 Setting up External MCP Services")
|
|
421
|
+
print("=" * 50)
|
|
422
|
+
|
|
423
|
+
# Use project-level .mcp.json file
|
|
424
|
+
project_path = Path.cwd()
|
|
425
|
+
config_path = project_path / ".mcp.json"
|
|
426
|
+
|
|
427
|
+
print(f"📁 Project directory: {project_path}")
|
|
428
|
+
print(f"📄 Using config: {config_path}")
|
|
429
|
+
|
|
430
|
+
# Load existing configuration
|
|
431
|
+
config = self._load_config(config_path)
|
|
432
|
+
if config is None:
|
|
433
|
+
print("❌ Failed to load configuration")
|
|
434
|
+
return False
|
|
435
|
+
|
|
436
|
+
# Ensure mcpServers section exists
|
|
437
|
+
if "mcpServers" not in config:
|
|
438
|
+
config["mcpServers"] = {}
|
|
439
|
+
|
|
440
|
+
# Setup each external service
|
|
441
|
+
success_count = 0
|
|
442
|
+
for service_name, service_info in self.get_project_services(project_path).items():
|
|
443
|
+
if self._setup_service(config, service_name, service_info, force):
|
|
444
|
+
success_count += 1
|
|
445
|
+
|
|
446
|
+
# Save the updated configuration
|
|
447
|
+
if success_count > 0:
|
|
448
|
+
if self._save_config(config, config_path):
|
|
449
|
+
print(f"\n✅ Successfully configured {success_count} external services in .mcp.json")
|
|
450
|
+
print(f"\n📌 Note: Claude Desktop will automatically load these services")
|
|
451
|
+
print(f" when you open this project directory in Claude Desktop.")
|
|
452
|
+
return True
|
|
453
|
+
else:
|
|
454
|
+
print("❌ Failed to save configuration")
|
|
455
|
+
return False
|
|
456
|
+
else:
|
|
457
|
+
print("\n⚠️ No external services were configured")
|
|
458
|
+
return False
|
|
459
|
+
|
|
460
|
+
def _setup_service(
|
|
461
|
+
self,
|
|
462
|
+
config: Dict,
|
|
463
|
+
service_name: str,
|
|
464
|
+
service_info: Dict,
|
|
465
|
+
force: bool
|
|
466
|
+
) -> bool:
|
|
467
|
+
"""Setup a single external MCP service.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
config: The Claude Desktop configuration
|
|
471
|
+
service_name: Name of the service to setup
|
|
472
|
+
service_info: Service configuration information
|
|
473
|
+
force: Whether to overwrite existing configuration
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
bool: True if service was set up successfully
|
|
477
|
+
"""
|
|
478
|
+
print(f"\n📦 Setting up {service_name}...")
|
|
479
|
+
|
|
480
|
+
# Check if already configured
|
|
481
|
+
if service_name in config["mcpServers"] and not force:
|
|
482
|
+
existing_config = config["mcpServers"][service_name]
|
|
483
|
+
print(f" ⚠️ {service_name} already configured")
|
|
484
|
+
print(f" Current command: {existing_config.get('command')}")
|
|
485
|
+
print(f" Current args: {existing_config.get('args')}")
|
|
486
|
+
|
|
487
|
+
# Check if it's using a local development path
|
|
488
|
+
command = str(existing_config.get('command', ''))
|
|
489
|
+
if any(path in command for path in ["/Projects/managed/", "/Projects/", "/Development/"]):
|
|
490
|
+
print(" 📍 Using local development version")
|
|
491
|
+
response = input(" Keep local development version? (Y/n): ").strip().lower()
|
|
492
|
+
if response not in ["n", "no"]:
|
|
493
|
+
print(f" ✅ Keeping existing local configuration for {service_name}")
|
|
494
|
+
return True # Consider it successfully configured
|
|
495
|
+
else:
|
|
496
|
+
response = input(" Overwrite? (y/N): ").strip().lower()
|
|
497
|
+
if response not in ["y", "yes"]:
|
|
498
|
+
print(f" ⏭️ Skipping {service_name}")
|
|
499
|
+
return False
|
|
500
|
+
|
|
501
|
+
# Check if Python package is available
|
|
502
|
+
module_name = service_info.get("module_name", service_name.replace("-", "_"))
|
|
503
|
+
if not self._check_python_package(module_name):
|
|
504
|
+
print(f" ⚠️ Python package {service_info['package_name']} not installed")
|
|
505
|
+
print(f" ℹ️ Installing {service_info['package_name']}...")
|
|
506
|
+
if not self._install_python_package(service_info['package_name']):
|
|
507
|
+
print(f" ❌ Failed to install {service_info['package_name']}")
|
|
508
|
+
print(f" ℹ️ Install manually with: pip install {service_info['package_name']}")
|
|
509
|
+
return False
|
|
510
|
+
|
|
511
|
+
# Add service configuration
|
|
512
|
+
config["mcpServers"][service_name] = service_info["config"]
|
|
513
|
+
print(f" ✅ Configured {service_name}")
|
|
514
|
+
print(f" Command: {service_info['config']['command']}")
|
|
515
|
+
print(f" Args: {service_info['config']['args']}")
|
|
516
|
+
if "env" in service_info["config"]:
|
|
517
|
+
print(f" Environment: {list(service_info['config']['env'].keys())}")
|
|
518
|
+
|
|
519
|
+
return True
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def check_and_install_pip_packages(self) -> bool:
|
|
523
|
+
"""Check and install Python packages for external services.
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
bool: True if all packages are available
|
|
527
|
+
"""
|
|
528
|
+
print("\n🐍 Checking Python packages for external services...")
|
|
529
|
+
|
|
530
|
+
packages_to_check = [
|
|
531
|
+
("mcp-vector-search", "mcp_vector_search"),
|
|
532
|
+
("mcp-browser", "mcp_browser")
|
|
533
|
+
]
|
|
534
|
+
|
|
535
|
+
all_installed = True
|
|
536
|
+
for package_name, module_name in packages_to_check:
|
|
537
|
+
if self._check_python_package(module_name):
|
|
538
|
+
print(f" ✅ {package_name} is installed")
|
|
539
|
+
else:
|
|
540
|
+
print(f" 📦 Installing {package_name}...")
|
|
541
|
+
if self._install_python_package(package_name):
|
|
542
|
+
print(f" ✅ Successfully installed {package_name}")
|
|
543
|
+
else:
|
|
544
|
+
print(f" ❌ Failed to install {package_name}")
|
|
545
|
+
all_installed = False
|
|
546
|
+
|
|
547
|
+
return all_installed
|
|
548
|
+
|
|
549
|
+
def _check_python_package(self, module_name: str) -> bool:
|
|
550
|
+
"""Check if a Python package is installed.
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
module_name: Name of the module to import
|
|
554
|
+
|
|
555
|
+
Returns:
|
|
556
|
+
bool: True if package is installed
|
|
557
|
+
"""
|
|
558
|
+
try:
|
|
559
|
+
import importlib.util
|
|
560
|
+
spec = importlib.util.find_spec(module_name)
|
|
561
|
+
return spec is not None
|
|
562
|
+
except (ImportError, ModuleNotFoundError):
|
|
563
|
+
return False
|
|
564
|
+
|
|
565
|
+
def _install_python_package(self, package_name: str) -> bool:
|
|
566
|
+
"""Install a Python package using pip.
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
package_name: Name of the package to install
|
|
570
|
+
|
|
571
|
+
Returns:
|
|
572
|
+
bool: True if installation was successful
|
|
573
|
+
"""
|
|
574
|
+
try:
|
|
575
|
+
result = subprocess.run(
|
|
576
|
+
[sys.executable, "-m", "pip", "install", package_name],
|
|
577
|
+
capture_output=True,
|
|
578
|
+
text=True,
|
|
579
|
+
timeout=60
|
|
580
|
+
)
|
|
581
|
+
return result.returncode == 0
|
|
582
|
+
except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
|
|
583
|
+
return False
|
|
584
|
+
|
|
585
|
+
def _load_config(self, config_path: Path) -> Optional[Dict]:
|
|
586
|
+
"""Load MCP configuration.
|
|
587
|
+
|
|
588
|
+
Args:
|
|
589
|
+
config_path: Path to the configuration file
|
|
590
|
+
|
|
591
|
+
Returns:
|
|
592
|
+
Optional[Dict]: Configuration dictionary or None if failed
|
|
593
|
+
"""
|
|
594
|
+
try:
|
|
595
|
+
if config_path.exists():
|
|
596
|
+
with open(config_path) as f:
|
|
597
|
+
config = json.load(f)
|
|
598
|
+
# Ensure mcpServers key exists
|
|
599
|
+
if "mcpServers" not in config:
|
|
600
|
+
config["mcpServers"] = {}
|
|
601
|
+
return config
|
|
602
|
+
else:
|
|
603
|
+
# Create new configuration
|
|
604
|
+
print(f" 📝 Creating new .mcp.json file")
|
|
605
|
+
return {"mcpServers": {}}
|
|
606
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
607
|
+
print(f"❌ Error loading config: {e}")
|
|
608
|
+
# Try to return empty config instead of None
|
|
609
|
+
return {"mcpServers": {}}
|
|
610
|
+
|
|
611
|
+
def _save_config(self, config: Dict, config_path: Path) -> bool:
|
|
612
|
+
"""Save MCP configuration.
|
|
613
|
+
|
|
614
|
+
Args:
|
|
615
|
+
config: Configuration dictionary
|
|
616
|
+
config_path: Path to save the configuration
|
|
617
|
+
|
|
618
|
+
Returns:
|
|
619
|
+
bool: True if save was successful
|
|
620
|
+
"""
|
|
621
|
+
try:
|
|
622
|
+
# Ensure directory exists
|
|
623
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
624
|
+
|
|
625
|
+
# Create backup if file exists
|
|
626
|
+
if config_path.exists():
|
|
627
|
+
from datetime import datetime
|
|
628
|
+
backup_path = config_path.parent / f".mcp.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
|
629
|
+
import shutil
|
|
630
|
+
shutil.copy2(config_path, backup_path)
|
|
631
|
+
print(f" 📁 Created backup: {backup_path}")
|
|
632
|
+
|
|
633
|
+
# Write configuration with proper formatting
|
|
634
|
+
with open(config_path, "w") as f:
|
|
635
|
+
json.dump(config, f, indent=2)
|
|
636
|
+
f.write("\n") # Add newline at end of file
|
|
637
|
+
|
|
638
|
+
print(f" 💾 Saved configuration to {config_path}")
|
|
639
|
+
return True
|
|
640
|
+
|
|
641
|
+
except Exception as e:
|
|
642
|
+
print(f"❌ Error saving config: {e}")
|
|
643
|
+
return False
|
|
644
|
+
|
|
645
|
+
def _get_pipx_config(self, package_name: str, project_path: Path) -> Optional[Dict]:
|
|
646
|
+
"""Get configuration for a pipx-installed package.
|
|
647
|
+
|
|
648
|
+
Args:
|
|
649
|
+
package_name: Name of the package (e.g., "mcp-browser")
|
|
650
|
+
project_path: Path to the project directory
|
|
651
|
+
|
|
652
|
+
Returns:
|
|
653
|
+
Configuration dict for the service or None if not found
|
|
654
|
+
"""
|
|
655
|
+
pipx_venv = self._pipx_path / package_name
|
|
656
|
+
if not pipx_venv.exists():
|
|
657
|
+
return None
|
|
658
|
+
|
|
659
|
+
if package_name == "mcp-browser":
|
|
660
|
+
# mcp-browser uses Python module invocation for MCP mode
|
|
661
|
+
python_path = pipx_venv / "bin" / "python"
|
|
662
|
+
if python_path.exists():
|
|
663
|
+
# Check if module is importable
|
|
664
|
+
try:
|
|
665
|
+
result = subprocess.run(
|
|
666
|
+
[str(python_path), "-c", "import mcp_browser.cli.main"],
|
|
667
|
+
capture_output=True,
|
|
668
|
+
timeout=5
|
|
669
|
+
)
|
|
670
|
+
if result.returncode == 0:
|
|
671
|
+
return {
|
|
672
|
+
"type": "stdio",
|
|
673
|
+
"command": str(python_path),
|
|
674
|
+
"args": ["-m", "mcp_browser.cli.main", "mcp"],
|
|
675
|
+
"env": {"MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser")}
|
|
676
|
+
}
|
|
677
|
+
except:
|
|
678
|
+
pass
|
|
679
|
+
elif package_name == "mcp-vector-search":
|
|
680
|
+
# mcp-vector-search uses Python module invocation
|
|
681
|
+
python_path = pipx_venv / "bin" / "python"
|
|
682
|
+
if python_path.exists():
|
|
683
|
+
# Check if module is importable
|
|
684
|
+
try:
|
|
685
|
+
result = subprocess.run(
|
|
686
|
+
[str(python_path), "-c", "import mcp_vector_search"],
|
|
687
|
+
capture_output=True,
|
|
688
|
+
timeout=5
|
|
689
|
+
)
|
|
690
|
+
if result.returncode == 0:
|
|
691
|
+
return {
|
|
692
|
+
"type": "stdio",
|
|
693
|
+
"command": str(python_path),
|
|
694
|
+
"args": ["-m", "mcp_vector_search.mcp.server", str(project_path)],
|
|
695
|
+
"env": {}
|
|
696
|
+
}
|
|
697
|
+
except:
|
|
698
|
+
pass
|
|
699
|
+
|
|
700
|
+
return None
|
|
701
|
+
|
|
702
|
+
def _check_pipx_installation(self, package_name: str) -> Tuple[bool, str]:
|
|
703
|
+
"""Check if a package is installed via pipx.
|
|
704
|
+
|
|
705
|
+
Args:
|
|
706
|
+
package_name: Name of the package to check
|
|
707
|
+
|
|
708
|
+
Returns:
|
|
709
|
+
Tuple of (is_installed, installation_type)
|
|
710
|
+
"""
|
|
711
|
+
pipx_venv = self._pipx_path / package_name
|
|
712
|
+
if pipx_venv.exists():
|
|
713
|
+
return True, "pipx"
|
|
714
|
+
|
|
715
|
+
# Check if available as Python module
|
|
716
|
+
module_name = package_name.replace("-", "_")
|
|
717
|
+
if self._check_python_package(module_name):
|
|
718
|
+
return True, "pip"
|
|
719
|
+
|
|
720
|
+
return False, "none"
|
|
721
|
+
|
|
722
|
+
def fix_browser_configuration(self) -> bool:
|
|
723
|
+
"""Quick fix for mcp-browser configuration in project .mcp.json.
|
|
724
|
+
|
|
725
|
+
Updates only the mcp-browser configuration in the project's .mcp.json
|
|
726
|
+
to use the best available installation (pipx preferred).
|
|
727
|
+
|
|
728
|
+
Returns:
|
|
729
|
+
bool: True if configuration was updated successfully
|
|
730
|
+
"""
|
|
731
|
+
print("\n🔧 Fixing mcp-browser Configuration")
|
|
732
|
+
print("=" * 50)
|
|
733
|
+
|
|
734
|
+
project_path = Path.cwd()
|
|
735
|
+
config_path = project_path / ".mcp.json"
|
|
736
|
+
|
|
737
|
+
print(f"📁 Project directory: {project_path}")
|
|
738
|
+
print(f"📄 Using config: {config_path}")
|
|
739
|
+
|
|
740
|
+
# Check if mcp-browser is installed
|
|
741
|
+
is_installed, install_type = self._check_pipx_installation("mcp-browser")
|
|
742
|
+
if not is_installed:
|
|
743
|
+
print("❌ mcp-browser is not installed")
|
|
744
|
+
print(" Install with: pipx install mcp-browser")
|
|
745
|
+
return False
|
|
746
|
+
|
|
747
|
+
if install_type != "pipx":
|
|
748
|
+
print("⚠️ mcp-browser is not installed via pipx")
|
|
749
|
+
print(" For best results, install with: pipx install mcp-browser")
|
|
750
|
+
|
|
751
|
+
# Get best configuration for mcp-browser
|
|
752
|
+
browser_config = self._get_best_service_config("mcp-browser", project_path)
|
|
753
|
+
if not browser_config:
|
|
754
|
+
print("❌ Could not determine mcp-browser configuration")
|
|
755
|
+
return False
|
|
756
|
+
|
|
757
|
+
# Load project configuration
|
|
758
|
+
config = self._load_config(config_path)
|
|
759
|
+
if not config:
|
|
760
|
+
print("❌ Failed to load configuration")
|
|
761
|
+
return False
|
|
762
|
+
|
|
763
|
+
# Update mcp-browser configuration
|
|
764
|
+
if "mcpServers" not in config:
|
|
765
|
+
config["mcpServers"] = {}
|
|
766
|
+
|
|
767
|
+
config["mcpServers"]["mcp-browser"] = browser_config
|
|
768
|
+
|
|
769
|
+
# Save configuration
|
|
770
|
+
if self._save_config(config, config_path):
|
|
771
|
+
print("✅ Successfully updated mcp-browser configuration in .mcp.json")
|
|
772
|
+
print(f" Command: {browser_config['command']}")
|
|
773
|
+
print(f" Args: {browser_config['args']}")
|
|
774
|
+
print("\n📌 Note: Claude Desktop will automatically use this configuration")
|
|
775
|
+
print(" when you open this project directory.")
|
|
776
|
+
return True
|
|
777
|
+
else:
|
|
778
|
+
print("❌ Failed to save configuration")
|
|
779
|
+
return False
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
def list_external_services(self) -> None:
|
|
783
|
+
"""List all available external MCP services and their status."""
|
|
784
|
+
print("\n📋 Available External MCP Services")
|
|
785
|
+
print("=" * 50)
|
|
786
|
+
|
|
787
|
+
# Check project-level .mcp.json
|
|
788
|
+
project_path = Path.cwd()
|
|
789
|
+
mcp_config_path = project_path / ".mcp.json"
|
|
790
|
+
mcp_config = {}
|
|
791
|
+
|
|
792
|
+
if mcp_config_path.exists():
|
|
793
|
+
try:
|
|
794
|
+
with open(mcp_config_path) as f:
|
|
795
|
+
mcp_config = json.load(f)
|
|
796
|
+
print(f"\n📁 Project MCP config: {mcp_config_path}")
|
|
797
|
+
except:
|
|
798
|
+
print(f"\n⚠️ Could not read project .mcp.json")
|
|
799
|
+
else:
|
|
800
|
+
print(f"\n📝 No .mcp.json found in project directory")
|
|
801
|
+
|
|
802
|
+
# Get service configurations for this project
|
|
803
|
+
services = self.get_project_services(project_path)
|
|
804
|
+
|
|
805
|
+
for service_name, service_info in services.items():
|
|
806
|
+
print(f"\n{service_name}:")
|
|
807
|
+
print(f" Description: {service_info['description']}")
|
|
808
|
+
print(f" Python Package: {service_info['package_name']}")
|
|
809
|
+
|
|
810
|
+
# Check if configured in .mcp.json
|
|
811
|
+
if mcp_config.get("mcpServers", {}).get(service_name):
|
|
812
|
+
print(f" Project Status: ✅ Configured in .mcp.json")
|
|
813
|
+
service_config = mcp_config["mcpServers"][service_name]
|
|
814
|
+
print(f" Command: {service_config.get('command')}")
|
|
815
|
+
if service_config.get('args'):
|
|
816
|
+
print(f" Args: {service_config.get('args')}")
|
|
817
|
+
else:
|
|
818
|
+
print(f" Project Status: ❌ Not configured in .mcp.json")
|
|
819
|
+
|
|
820
|
+
# Check installation type
|
|
821
|
+
is_installed, install_type = self._check_pipx_installation(service_name)
|
|
822
|
+
if is_installed:
|
|
823
|
+
if install_type == "pipx":
|
|
824
|
+
print(f" Installation: ✅ Installed via pipx (recommended)")
|
|
825
|
+
else:
|
|
826
|
+
print(f" Installation: ✅ Installed via pip")
|
|
827
|
+
else:
|
|
828
|
+
print(f" Installation: ❌ Not installed")
|
|
829
|
+
print(f" Install with: pipx install {service_info['package_name']}")
|