claude-mpm 4.3.11__py3-none-any.whl → 4.3.13__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 (207) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/PM_INSTRUCTIONS.md +390 -28
  3. claude_mpm/agents/templates/data_engineer.json +39 -14
  4. claude_mpm/agents/templates/research.json +20 -8
  5. claude_mpm/agents/templates/web_qa.json +25 -10
  6. claude_mpm/cli/__init__.py +1 -0
  7. claude_mpm/cli/commands/agent_manager.py +3 -3
  8. claude_mpm/cli/commands/agents.py +2 -2
  9. claude_mpm/cli/commands/aggregate.py +1 -1
  10. claude_mpm/cli/commands/config.py +2 -2
  11. claude_mpm/cli/commands/configure.py +5 -5
  12. claude_mpm/cli/commands/configure_tui.py +7 -7
  13. claude_mpm/cli/commands/dashboard.py +1 -1
  14. claude_mpm/cli/commands/debug.py +5 -5
  15. claude_mpm/cli/commands/mcp.py +1 -1
  16. claude_mpm/cli/commands/mcp_command_router.py +12 -1
  17. claude_mpm/cli/commands/mcp_config.py +154 -0
  18. claude_mpm/cli/commands/mcp_external_commands.py +249 -0
  19. claude_mpm/cli/commands/mcp_install_commands.py +93 -24
  20. claude_mpm/cli/commands/mcp_setup_external.py +870 -0
  21. claude_mpm/cli/commands/monitor.py +2 -2
  22. claude_mpm/cli/commands/mpm_init_handler.py +1 -1
  23. claude_mpm/cli/commands/run.py +114 -0
  24. claude_mpm/cli/commands/search.py +292 -0
  25. claude_mpm/cli/interactive/agent_wizard.py +2 -2
  26. claude_mpm/cli/parsers/base_parser.py +13 -0
  27. claude_mpm/cli/parsers/mcp_parser.py +15 -0
  28. claude_mpm/cli/parsers/run_parser.py +5 -0
  29. claude_mpm/cli/parsers/search_parser.py +245 -0
  30. claude_mpm/cli/startup_logging.py +3 -5
  31. claude_mpm/cli/utils.py +1 -1
  32. claude_mpm/constants.py +1 -0
  33. claude_mpm/core/agent_registry.py +12 -8
  34. claude_mpm/core/agent_session_manager.py +8 -8
  35. claude_mpm/core/api_validator.py +4 -4
  36. claude_mpm/core/base_service.py +10 -10
  37. claude_mpm/core/cache.py +5 -5
  38. claude_mpm/core/config_constants.py +1 -1
  39. claude_mpm/core/container.py +1 -1
  40. claude_mpm/core/error_handler.py +2 -2
  41. claude_mpm/core/file_utils.py +1 -1
  42. claude_mpm/core/framework_loader.py +3 -3
  43. claude_mpm/core/hook_manager.py +8 -6
  44. claude_mpm/core/instruction_reinforcement_hook.py +2 -2
  45. claude_mpm/core/interactive_session.py +1 -1
  46. claude_mpm/core/lazy.py +3 -3
  47. claude_mpm/core/log_manager.py +16 -12
  48. claude_mpm/core/logger.py +16 -11
  49. claude_mpm/core/logging_config.py +4 -2
  50. claude_mpm/core/oneshot_session.py +1 -1
  51. claude_mpm/core/optimized_agent_loader.py +6 -6
  52. claude_mpm/core/output_style_manager.py +1 -1
  53. claude_mpm/core/pm_hook_interceptor.py +3 -3
  54. claude_mpm/core/service_registry.py +1 -1
  55. claude_mpm/core/session_manager.py +11 -9
  56. claude_mpm/core/socketio_pool.py +13 -13
  57. claude_mpm/core/types.py +2 -2
  58. claude_mpm/core/unified_agent_registry.py +9 -2
  59. claude_mpm/core/unified_paths.py +1 -1
  60. claude_mpm/dashboard/analysis_runner.py +4 -4
  61. claude_mpm/dashboard/api/simple_directory.py +1 -1
  62. claude_mpm/generators/agent_profile_generator.py +4 -2
  63. claude_mpm/hooks/base_hook.py +2 -2
  64. claude_mpm/hooks/claude_hooks/connection_pool.py +4 -4
  65. claude_mpm/hooks/claude_hooks/event_handlers.py +12 -12
  66. claude_mpm/hooks/claude_hooks/hook_handler.py +4 -4
  67. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +3 -3
  68. claude_mpm/hooks/claude_hooks/hook_handler_original.py +15 -14
  69. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +4 -4
  70. claude_mpm/hooks/claude_hooks/installer.py +3 -3
  71. claude_mpm/hooks/claude_hooks/memory_integration.py +3 -3
  72. claude_mpm/hooks/claude_hooks/response_tracking.py +3 -3
  73. claude_mpm/hooks/claude_hooks/services/connection_manager.py +5 -5
  74. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +3 -3
  75. claude_mpm/hooks/claude_hooks/services/state_manager.py +8 -7
  76. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +3 -3
  77. claude_mpm/hooks/claude_hooks/tool_analysis.py +2 -2
  78. claude_mpm/hooks/memory_integration_hook.py +1 -1
  79. claude_mpm/hooks/tool_call_interceptor.py +2 -2
  80. claude_mpm/models/agent_session.py +5 -5
  81. claude_mpm/services/__init__.py +1 -1
  82. claude_mpm/services/agent_capabilities_service.py +1 -1
  83. claude_mpm/services/agents/agent_builder.py +3 -3
  84. claude_mpm/services/agents/deployment/agent_deployment.py +29 -13
  85. claude_mpm/services/agents/deployment/agent_discovery_service.py +22 -6
  86. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +7 -5
  87. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +3 -1
  88. claude_mpm/services/agents/deployment/agent_metrics_collector.py +1 -1
  89. claude_mpm/services/agents/deployment/agent_operation_service.py +2 -2
  90. claude_mpm/services/agents/deployment/agent_state_service.py +2 -2
  91. claude_mpm/services/agents/deployment/agent_template_builder.py +1 -1
  92. claude_mpm/services/agents/deployment/agent_versioning.py +1 -1
  93. claude_mpm/services/agents/deployment/deployment_wrapper.py +2 -3
  94. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +6 -4
  95. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +1 -1
  96. claude_mpm/services/agents/loading/agent_profile_loader.py +5 -3
  97. claude_mpm/services/agents/loading/base_agent_manager.py +2 -2
  98. claude_mpm/services/agents/local_template_manager.py +6 -6
  99. claude_mpm/services/agents/management/agent_management_service.py +3 -3
  100. claude_mpm/services/agents/memory/content_manager.py +3 -3
  101. claude_mpm/services/agents/memory/memory_format_service.py +2 -2
  102. claude_mpm/services/agents/memory/template_generator.py +3 -3
  103. claude_mpm/services/agents/registry/__init__.py +1 -1
  104. claude_mpm/services/agents/registry/modification_tracker.py +2 -2
  105. claude_mpm/services/async_session_logger.py +3 -3
  106. claude_mpm/services/claude_session_logger.py +4 -4
  107. claude_mpm/services/cli/agent_cleanup_service.py +5 -0
  108. claude_mpm/services/cli/agent_listing_service.py +1 -1
  109. claude_mpm/services/cli/agent_validation_service.py +1 -0
  110. claude_mpm/services/cli/memory_crud_service.py +11 -6
  111. claude_mpm/services/cli/memory_output_formatter.py +1 -1
  112. claude_mpm/services/cli/session_manager.py +15 -11
  113. claude_mpm/services/cli/unified_dashboard_manager.py +1 -1
  114. claude_mpm/services/core/memory_manager.py +81 -23
  115. claude_mpm/services/core/path_resolver.py +2 -2
  116. claude_mpm/services/diagnostics/checks/installation_check.py +1 -1
  117. claude_mpm/services/event_aggregator.py +4 -2
  118. claude_mpm/services/event_bus/direct_relay.py +5 -3
  119. claude_mpm/services/event_bus/event_bus.py +3 -3
  120. claude_mpm/services/event_bus/relay.py +6 -4
  121. claude_mpm/services/events/consumers/dead_letter.py +5 -3
  122. claude_mpm/services/events/core.py +3 -3
  123. claude_mpm/services/events/producers/hook.py +6 -6
  124. claude_mpm/services/events/producers/system.py +8 -8
  125. claude_mpm/services/exceptions.py +5 -5
  126. claude_mpm/services/framework_claude_md_generator/content_assembler.py +3 -3
  127. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +2 -2
  128. claude_mpm/services/hook_installer_service.py +1 -1
  129. claude_mpm/services/infrastructure/context_preservation.py +6 -4
  130. claude_mpm/services/infrastructure/daemon_manager.py +2 -2
  131. claude_mpm/services/infrastructure/logging.py +2 -2
  132. claude_mpm/services/mcp_config_manager.py +439 -0
  133. claude_mpm/services/mcp_gateway/__init__.py +1 -1
  134. claude_mpm/services/mcp_gateway/auto_configure.py +3 -3
  135. claude_mpm/services/mcp_gateway/config/config_loader.py +1 -1
  136. claude_mpm/services/mcp_gateway/config/configuration.py +18 -1
  137. claude_mpm/services/mcp_gateway/core/base.py +2 -2
  138. claude_mpm/services/mcp_gateway/main.py +52 -0
  139. claude_mpm/services/mcp_gateway/registry/tool_registry.py +10 -8
  140. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +4 -4
  141. claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
  142. claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -3
  143. claude_mpm/services/mcp_gateway/tools/base_adapter.py +15 -15
  144. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +7 -5
  145. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +443 -0
  146. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +5 -5
  147. claude_mpm/services/mcp_gateway/tools/hello_world.py +9 -9
  148. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +16 -16
  149. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +17 -17
  150. claude_mpm/services/memory/builder.py +7 -5
  151. claude_mpm/services/memory/indexed_memory.py +4 -4
  152. claude_mpm/services/memory/optimizer.py +6 -6
  153. claude_mpm/services/memory/router.py +3 -3
  154. claude_mpm/services/monitor/daemon.py +1 -1
  155. claude_mpm/services/monitor/daemon_manager.py +6 -6
  156. claude_mpm/services/monitor/event_emitter.py +2 -2
  157. claude_mpm/services/monitor/handlers/file.py +1 -1
  158. claude_mpm/services/monitor/management/lifecycle.py +1 -1
  159. claude_mpm/services/monitor/server.py +4 -4
  160. claude_mpm/services/monitor_build_service.py +2 -2
  161. claude_mpm/services/port_manager.py +2 -2
  162. claude_mpm/services/response_tracker.py +2 -2
  163. claude_mpm/services/session_management_service.py +3 -2
  164. claude_mpm/services/socketio/client_proxy.py +2 -2
  165. claude_mpm/services/socketio/dashboard_server.py +4 -3
  166. claude_mpm/services/socketio/event_normalizer.py +12 -8
  167. claude_mpm/services/socketio/handlers/base.py +2 -2
  168. claude_mpm/services/socketio/handlers/connection.py +10 -10
  169. claude_mpm/services/socketio/handlers/connection_handler.py +13 -10
  170. claude_mpm/services/socketio/handlers/file.py +1 -1
  171. claude_mpm/services/socketio/handlers/git.py +1 -1
  172. claude_mpm/services/socketio/handlers/hook.py +16 -15
  173. claude_mpm/services/socketio/migration_utils.py +1 -1
  174. claude_mpm/services/socketio/monitor_client.py +5 -5
  175. claude_mpm/services/socketio/server/broadcaster.py +9 -7
  176. claude_mpm/services/socketio/server/connection_manager.py +2 -2
  177. claude_mpm/services/socketio/server/core.py +7 -5
  178. claude_mpm/services/socketio/server/eventbus_integration.py +18 -11
  179. claude_mpm/services/socketio/server/main.py +13 -13
  180. claude_mpm/services/socketio_client_manager.py +4 -4
  181. claude_mpm/services/system_instructions_service.py +2 -2
  182. claude_mpm/services/ticket_services/validation_service.py +1 -1
  183. claude_mpm/services/utility_service.py +5 -2
  184. claude_mpm/services/version_control/branch_strategy.py +2 -2
  185. claude_mpm/services/version_control/git_operations.py +22 -20
  186. claude_mpm/services/version_control/semantic_versioning.py +3 -3
  187. claude_mpm/services/version_control/version_parser.py +7 -5
  188. claude_mpm/services/visualization/mermaid_generator.py +1 -1
  189. claude_mpm/storage/state_storage.py +1 -1
  190. claude_mpm/tools/code_tree_analyzer.py +19 -18
  191. claude_mpm/tools/code_tree_builder.py +2 -2
  192. claude_mpm/tools/code_tree_events.py +10 -8
  193. claude_mpm/tools/socketio_debug.py +3 -3
  194. claude_mpm/utils/agent_dependency_loader.py +2 -2
  195. claude_mpm/utils/dependency_strategies.py +8 -3
  196. claude_mpm/utils/environment_context.py +2 -2
  197. claude_mpm/utils/error_handler.py +2 -2
  198. claude_mpm/utils/file_utils.py +1 -1
  199. claude_mpm/utils/imports.py +1 -1
  200. claude_mpm/utils/log_cleanup.py +21 -7
  201. claude_mpm/validation/agent_validator.py +2 -2
  202. {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/METADATA +4 -1
  203. {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/RECORD +207 -200
  204. {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/WHEEL +0 -0
  205. {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/entry_points.txt +0 -0
  206. {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/licenses/LICENSE +0 -0
  207. {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,870 @@
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 subprocess
9
+ import sys
10
+ from datetime import datetime, timezone
11
+ from pathlib import Path
12
+ from typing import Dict, Optional, 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(
30
+ "mcp-vector-search", project_path
31
+ )
32
+
33
+ return {
34
+ "mcp-vector-search": {
35
+ "package_name": "mcp-vector-search",
36
+ "module_name": "mcp_vector_search",
37
+ "description": "Semantic code search with vector embeddings",
38
+ "config": mcp_vector_search_config,
39
+ },
40
+ "mcp-browser": {
41
+ "package_name": "mcp-browser",
42
+ "module_name": "mcp_browser",
43
+ "description": "Web browsing and content extraction",
44
+ "config": mcp_browser_config,
45
+ },
46
+ }
47
+
48
+ def _get_best_service_config(self, service_name: str, project_path: Path) -> Dict:
49
+ """Get the best configuration for a service.
50
+
51
+ Priority order:
52
+ 1. Pipx installation (preferred for isolation and reliability)
53
+ 2. Local development installation (e.g., ~/Projects/managed/)
54
+ 3. Local project venv
55
+ 4. System Python
56
+
57
+ Args:
58
+ service_name: Name of the service
59
+ project_path: Path to the project directory
60
+
61
+ Returns:
62
+ Dict: Service configuration
63
+ """
64
+ # First try pipx (preferred for isolation and reliability)
65
+ pipx_config = self._get_pipx_config(service_name, project_path)
66
+ if pipx_config:
67
+ # Verify the executable actually exists before using it
68
+ command = pipx_config.get("command", "")
69
+ if Path(command).exists():
70
+ return pipx_config
71
+
72
+ # Then try local development installations
73
+ local_dev_config = self._get_local_dev_config(service_name, project_path)
74
+ if local_dev_config:
75
+ # Verify the command exists
76
+ command = local_dev_config.get("command", "")
77
+ if Path(command).exists():
78
+ return local_dev_config
79
+
80
+ # Then try local venv if exists
81
+ venv_config = self._get_venv_config(service_name, project_path)
82
+ if venv_config:
83
+ command = venv_config.get("command", "")
84
+ if Path(command).exists():
85
+ return venv_config
86
+
87
+ # Fall back to system Python
88
+ return self._get_system_config(service_name, project_path)
89
+
90
+ def _get_local_dev_config(
91
+ self, service_name: str, project_path: Path
92
+ ) -> Optional[Dict]:
93
+ """Get configuration for a locally developed service.
94
+
95
+ Checks common development locations like ~/Projects/managed/
96
+
97
+ Args:
98
+ service_name: Name of the service
99
+ project_path: Path to the project directory
100
+
101
+ Returns:
102
+ Configuration dict or None if not available
103
+ """
104
+ # Check common local development locations
105
+ dev_locations = [
106
+ Path.home() / "Projects" / "managed" / service_name,
107
+ Path.home() / "Projects" / service_name,
108
+ Path.home() / "Development" / service_name,
109
+ Path.home() / "dev" / service_name,
110
+ ]
111
+
112
+ for dev_path in dev_locations:
113
+ if not dev_path.exists():
114
+ continue
115
+
116
+ # Check for venv in the development location
117
+ venv_paths = [
118
+ dev_path / ".venv" / "bin" / "python",
119
+ dev_path / "venv" / "bin" / "python",
120
+ dev_path / "env" / "bin" / "python",
121
+ ]
122
+
123
+ for venv_python in venv_paths:
124
+ if venv_python.exists():
125
+ # Special handling for mcp-browser
126
+ if service_name == "mcp-browser":
127
+ # First check for mcp-browser binary in the same directory as python
128
+ mcp_browser_binary = venv_python.parent / "mcp-browser"
129
+ if mcp_browser_binary.exists():
130
+ return {
131
+ "type": "stdio",
132
+ "command": str(mcp_browser_binary),
133
+ "args": ["mcp"],
134
+ "env": {
135
+ "MCP_BROWSER_HOME": str(
136
+ Path.home() / ".mcp-browser"
137
+ )
138
+ },
139
+ }
140
+
141
+ # Then check for mcp-server.py
142
+ mcp_server = dev_path / "mcp-server.py"
143
+ if mcp_server.exists():
144
+ return {
145
+ "type": "stdio",
146
+ "command": str(venv_python),
147
+ "args": [str(mcp_server)],
148
+ "env": {
149
+ "MCP_BROWSER_HOME": str(
150
+ Path.home() / ".mcp-browser"
151
+ ),
152
+ "PYTHONPATH": str(dev_path),
153
+ },
154
+ }
155
+
156
+ # Check if the package is installed in this venv
157
+ module_name = service_name.replace("-", "_")
158
+ try:
159
+ result = subprocess.run(
160
+ [str(venv_python), "-c", f"import {module_name}"],
161
+ capture_output=True,
162
+ timeout=5,
163
+ check=False,
164
+ )
165
+ if result.returncode == 0:
166
+ # Use special configuration for local dev
167
+ if service_name == "mcp-vector-search":
168
+ return {
169
+ "type": "stdio",
170
+ "command": str(venv_python),
171
+ "args": [
172
+ "-m",
173
+ "mcp_vector_search.mcp.server",
174
+ str(project_path),
175
+ ],
176
+ "env": {},
177
+ }
178
+ if service_name == "mcp-browser":
179
+ # Fallback for mcp-browser without mcp-server.py
180
+ return {
181
+ "type": "stdio",
182
+ "command": str(venv_python),
183
+ "args": ["-m", "mcp_browser", "mcp"],
184
+ "env": {
185
+ "MCP_BROWSER_HOME": str(
186
+ Path.home() / ".mcp-browser"
187
+ ),
188
+ "PYTHONPATH": str(dev_path),
189
+ },
190
+ }
191
+ except Exception:
192
+ continue
193
+
194
+ return None
195
+
196
+ def _get_venv_config(self, service_name: str, project_path: Path) -> Optional[Dict]:
197
+ """Get configuration for a service in the local virtual environment.
198
+
199
+ Args:
200
+ service_name: Name of the service
201
+ project_path: Path to the project directory
202
+
203
+ Returns:
204
+ Configuration dict or None if not available
205
+ """
206
+ # Check common venv locations
207
+ venv_paths = [
208
+ project_path / "venv" / "bin" / "python",
209
+ project_path / ".venv" / "bin" / "python",
210
+ project_path / "env" / "bin" / "python",
211
+ ]
212
+
213
+ for venv_python in venv_paths:
214
+ if venv_python.exists():
215
+ # Check if the package is installed in this venv
216
+ module_name = service_name.replace("-", "_")
217
+ try:
218
+ result = subprocess.run(
219
+ [str(venv_python), "-c", f"import {module_name}"],
220
+ capture_output=True,
221
+ timeout=5,
222
+ check=False,
223
+ )
224
+ if result.returncode == 0:
225
+ return self._create_service_config(
226
+ service_name, str(venv_python), project_path
227
+ )
228
+ except Exception:
229
+ continue
230
+
231
+ return None
232
+
233
+ def _get_system_config(self, service_name: str, project_path: Path) -> Dict:
234
+ """Get configuration using system Python.
235
+
236
+ Args:
237
+ service_name: Name of the service
238
+ project_path: Path to the project directory
239
+
240
+ Returns:
241
+ Configuration dict
242
+ """
243
+ return self._create_service_config(service_name, sys.executable, project_path)
244
+
245
+ def _create_service_config(
246
+ self, service_name: str, python_path: str, project_path: Path
247
+ ) -> Dict:
248
+ """Create service configuration for the given Python executable.
249
+
250
+ Args:
251
+ service_name: Name of the service
252
+ python_path: Path to Python executable
253
+ project_path: Path to the project directory
254
+
255
+ Returns:
256
+ Configuration dict
257
+ """
258
+ if service_name == "mcp-browser":
259
+ # Check if mcp-browser binary exists (for pipx installations)
260
+ binary_path = Path(python_path).parent / "mcp-browser"
261
+ if binary_path.exists():
262
+ return {
263
+ "type": "stdio",
264
+ "command": str(binary_path),
265
+ "args": ["mcp"],
266
+ "env": {"MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser")},
267
+ }
268
+ # Use Python module invocation
269
+ return {
270
+ "type": "stdio",
271
+ "command": python_path,
272
+ "args": ["-m", "mcp_browser", "mcp"],
273
+ "env": {"MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser")},
274
+ }
275
+ if service_name == "mcp-vector-search":
276
+ return {
277
+ "type": "stdio",
278
+ "command": python_path,
279
+ "args": ["-m", "mcp_vector_search.mcp.server", str(project_path)],
280
+ "env": {},
281
+ }
282
+ # Generic configuration for other services
283
+ module_name = service_name.replace("-", "_")
284
+ return {
285
+ "type": "stdio",
286
+ "command": python_path,
287
+ "args": ["-m", module_name],
288
+ "env": {},
289
+ }
290
+
291
+ def detect_mcp_installations(self) -> Dict[str, Dict]:
292
+ """Detect all MCP service installations and their locations.
293
+
294
+ Returns:
295
+ Dict mapping service name to installation info:
296
+ {
297
+ "service-name": {
298
+ "type": "local_dev" | "pipx" | "venv" | "system" | "not_installed",
299
+ "path": "/path/to/installation",
300
+ "config": {...} # Ready-to-use configuration
301
+ }
302
+ }
303
+ """
304
+ installations = {}
305
+ project_path = Path.cwd()
306
+
307
+ for service_name in ["mcp-browser", "mcp-vector-search"]:
308
+ # Try each detection method in priority order
309
+ local_dev_config = self._get_local_dev_config(service_name, project_path)
310
+ if local_dev_config:
311
+ installations[service_name] = {
312
+ "type": "local_dev",
313
+ "path": local_dev_config["command"],
314
+ "config": local_dev_config,
315
+ }
316
+ continue
317
+
318
+ pipx_config = self._get_pipx_config(service_name, project_path)
319
+ if pipx_config:
320
+ # Verify the command actually exists before reporting it as available
321
+ command_path = Path(pipx_config["command"])
322
+ if command_path.exists():
323
+ installations[service_name] = {
324
+ "type": "pipx",
325
+ "path": pipx_config["command"],
326
+ "config": pipx_config,
327
+ }
328
+ continue
329
+
330
+ venv_config = self._get_venv_config(service_name, project_path)
331
+ if venv_config:
332
+ installations[service_name] = {
333
+ "type": "venv",
334
+ "path": venv_config["command"],
335
+ "config": venv_config,
336
+ }
337
+ continue
338
+
339
+ # Check if available in system Python
340
+ module_name = service_name.replace("-", "_")
341
+ if self._check_python_package(module_name):
342
+ system_config = self._get_system_config(service_name, project_path)
343
+ installations[service_name] = {
344
+ "type": "system",
345
+ "path": system_config["command"],
346
+ "config": system_config,
347
+ }
348
+ else:
349
+ installations[service_name] = {
350
+ "type": "not_installed",
351
+ "path": None,
352
+ "config": None,
353
+ }
354
+
355
+ return installations
356
+
357
+ def update_mcp_json_with_detected(self, force: bool = False) -> bool:
358
+ """Update .mcp.json with auto-detected service configurations.
359
+
360
+ Args:
361
+ force: Whether to overwrite existing configurations
362
+
363
+ Returns:
364
+ bool: True if configuration was updated successfully
365
+ """
366
+ print("\nšŸ” Auto-detecting MCP service installations...")
367
+ print("=" * 50)
368
+
369
+ installations = self.detect_mcp_installations()
370
+
371
+ # Display detected installations
372
+ for service_name, info in installations.items():
373
+ print(f"\n{service_name}:")
374
+ if info["type"] == "not_installed":
375
+ print(" āŒ Not installed")
376
+ else:
377
+ type_emoji = {
378
+ "local_dev": "šŸ”§",
379
+ "pipx": "šŸ“¦",
380
+ "venv": "šŸ",
381
+ "system": "šŸ’»",
382
+ }.get(info["type"], "ā“")
383
+ print(f" {type_emoji} Type: {info['type']}")
384
+ print(f" šŸ“ Path: {info['path']}")
385
+
386
+ # Load current configuration
387
+ config_path = Path.cwd() / ".mcp.json"
388
+ config = self._load_config(config_path)
389
+ if not config:
390
+ print("\nāŒ Failed to load configuration")
391
+ return False
392
+
393
+ # Update configurations
394
+ updated = False
395
+ for service_name, info in installations.items():
396
+ if info["type"] == "not_installed":
397
+ continue
398
+
399
+ # Check if already configured
400
+ if service_name in config.get("mcpServers", {}) and not force:
401
+ print(
402
+ f"\nāš ļø {service_name} already configured, skipping (use --force to override)"
403
+ )
404
+ continue
405
+
406
+ # Update configuration
407
+ if "mcpServers" not in config:
408
+ config["mcpServers"] = {}
409
+
410
+ config["mcpServers"][service_name] = info["config"]
411
+ print(f"\nāœ… Updated {service_name} configuration")
412
+ updated = True
413
+
414
+ # Save configuration if updated
415
+ if updated:
416
+ if self._save_config(config, config_path):
417
+ print(
418
+ "\nāœ… Successfully updated .mcp.json with detected configurations"
419
+ )
420
+ return True
421
+ print("\nāŒ Failed to save configuration")
422
+ return False
423
+ print("\nšŸ“Œ No updates needed")
424
+ return True
425
+
426
+ def __init__(self, logger):
427
+ """Initialize the external services setup handler."""
428
+ self.logger = logger
429
+ self._pipx_path = Path.home() / ".local" / "pipx" / "venvs"
430
+
431
+ def setup_external_services(self, force: bool = False) -> bool:
432
+ """Setup external MCP services in project .mcp.json file.
433
+
434
+ Args:
435
+ force: Whether to overwrite existing configurations
436
+
437
+ Returns:
438
+ bool: True if all services were set up successfully
439
+ """
440
+ print("\nšŸ“¦ Setting up External MCP Services")
441
+ print("=" * 50)
442
+
443
+ # Use project-level .mcp.json file
444
+ project_path = Path.cwd()
445
+ config_path = project_path / ".mcp.json"
446
+
447
+ print(f"šŸ“ Project directory: {project_path}")
448
+ print(f"šŸ“„ Using config: {config_path}")
449
+
450
+ # Load existing configuration
451
+ config = self._load_config(config_path)
452
+ if config is None:
453
+ print("āŒ Failed to load configuration")
454
+ return False
455
+
456
+ # Ensure mcpServers section exists
457
+ if "mcpServers" not in config:
458
+ config["mcpServers"] = {}
459
+
460
+ # Setup each external service
461
+ success_count = 0
462
+ for service_name, service_info in self.get_project_services(
463
+ project_path
464
+ ).items():
465
+ if self._setup_service(config, service_name, service_info, force):
466
+ success_count += 1
467
+
468
+ # Save the updated configuration
469
+ if success_count > 0:
470
+ if self._save_config(config, config_path):
471
+ print(
472
+ f"\nāœ… Successfully configured {success_count} external services in .mcp.json"
473
+ )
474
+ print(
475
+ "\nšŸ“Œ Note: Claude Desktop will automatically load these services"
476
+ )
477
+ print(" when you open this project directory in Claude Desktop.")
478
+ return True
479
+ print("āŒ Failed to save configuration")
480
+ return False
481
+ print("\nāš ļø No external services were configured")
482
+ return False
483
+
484
+ def _setup_service(
485
+ self, config: Dict, service_name: str, service_info: Dict, force: bool
486
+ ) -> bool:
487
+ """Setup a single external MCP service.
488
+
489
+ Args:
490
+ config: The Claude Desktop configuration
491
+ service_name: Name of the service to setup
492
+ service_info: Service configuration information
493
+ force: Whether to overwrite existing configuration
494
+
495
+ Returns:
496
+ bool: True if service was set up successfully
497
+ """
498
+ print(f"\nšŸ“¦ Setting up {service_name}...")
499
+
500
+ # Check if already configured
501
+ if service_name in config["mcpServers"] and not force:
502
+ existing_config = config["mcpServers"][service_name]
503
+ print(f" āš ļø {service_name} already configured")
504
+ print(f" Current command: {existing_config.get('command')}")
505
+ print(f" Current args: {existing_config.get('args')}")
506
+
507
+ # Check if it's using a local development path
508
+ command = str(existing_config.get("command", ""))
509
+ if any(
510
+ path in command
511
+ for path in ["/Projects/managed/", "/Projects/", "/Development/"]
512
+ ):
513
+ print(" šŸ“ Using local development version")
514
+ response = (
515
+ input(" Keep local development version? (Y/n): ").strip().lower()
516
+ )
517
+ if response not in ["n", "no"]:
518
+ print(
519
+ f" āœ… Keeping existing local configuration for {service_name}"
520
+ )
521
+ return True # Consider it successfully configured
522
+ else:
523
+ response = input(" Overwrite? (y/N): ").strip().lower()
524
+ if response not in ["y", "yes"]:
525
+ print(f" ā­ļø Skipping {service_name}")
526
+ return False
527
+
528
+ # Check if Python package is available
529
+ module_name = service_info.get("module_name", service_name.replace("-", "_"))
530
+ if not self._check_python_package(module_name):
531
+ print(f" āš ļø Python package {service_info['package_name']} not installed")
532
+ print(f" ā„¹ļø Installing {service_info['package_name']}...")
533
+ if not self._install_python_package(service_info["package_name"]):
534
+ print(f" āŒ Failed to install {service_info['package_name']}")
535
+ print(
536
+ f" ā„¹ļø Install manually with: pip install {service_info['package_name']}"
537
+ )
538
+ return False
539
+
540
+ # Add service configuration
541
+ config["mcpServers"][service_name] = service_info["config"]
542
+ print(f" āœ… Configured {service_name}")
543
+ print(f" Command: {service_info['config']['command']}")
544
+ print(f" Args: {service_info['config']['args']}")
545
+ if "env" in service_info["config"]:
546
+ print(f" Environment: {list(service_info['config']['env'].keys())}")
547
+
548
+ return True
549
+
550
+ def check_and_install_pip_packages(self) -> bool:
551
+ """Check and install Python packages for external services.
552
+
553
+ Returns:
554
+ bool: True if all packages are available
555
+ """
556
+ print("\nšŸ Checking Python packages for external services...")
557
+
558
+ packages_to_check = [
559
+ ("mcp-vector-search", "mcp_vector_search"),
560
+ ("mcp-browser", "mcp_browser"),
561
+ ]
562
+
563
+ all_installed = True
564
+ for package_name, module_name in packages_to_check:
565
+ if self._check_python_package(module_name):
566
+ print(f" āœ… {package_name} is installed")
567
+ else:
568
+ print(f" šŸ“¦ Installing {package_name}...")
569
+ if self._install_python_package(package_name):
570
+ print(f" āœ… Successfully installed {package_name}")
571
+ else:
572
+ print(f" āŒ Failed to install {package_name}")
573
+ all_installed = False
574
+
575
+ return all_installed
576
+
577
+ def _check_python_package(self, module_name: str) -> bool:
578
+ """Check if a Python package is installed.
579
+
580
+ Args:
581
+ module_name: Name of the module to import
582
+
583
+ Returns:
584
+ bool: True if package is installed
585
+ """
586
+ try:
587
+ import importlib.util
588
+
589
+ spec = importlib.util.find_spec(module_name)
590
+ return spec is not None
591
+ except (ImportError, ModuleNotFoundError):
592
+ return False
593
+
594
+ def _install_python_package(self, package_name: str) -> bool:
595
+ """Install a Python package using pip.
596
+
597
+ Args:
598
+ package_name: Name of the package to install
599
+
600
+ Returns:
601
+ bool: True if installation was successful
602
+ """
603
+ try:
604
+ result = subprocess.run(
605
+ [sys.executable, "-m", "pip", "install", package_name],
606
+ capture_output=True,
607
+ text=True,
608
+ timeout=60,
609
+ check=False,
610
+ )
611
+ return result.returncode == 0
612
+ except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
613
+ return False
614
+
615
+ def _load_config(self, config_path: Path) -> Optional[Dict]:
616
+ """Load MCP configuration.
617
+
618
+ Args:
619
+ config_path: Path to the configuration file
620
+
621
+ Returns:
622
+ Optional[Dict]: Configuration dictionary or None if failed
623
+ """
624
+ try:
625
+ if config_path.exists():
626
+ with open(config_path) as f:
627
+ config = json.load(f)
628
+ # Ensure mcpServers key exists
629
+ if "mcpServers" not in config:
630
+ config["mcpServers"] = {}
631
+ return config
632
+ else:
633
+ # Create new configuration
634
+ print(" šŸ“ Creating new .mcp.json file")
635
+ return {"mcpServers": {}}
636
+ except (OSError, json.JSONDecodeError) as e:
637
+ print(f"āŒ Error loading config: {e}")
638
+ # Try to return empty config instead of None
639
+ return {"mcpServers": {}}
640
+
641
+ def _save_config(self, config: Dict, config_path: Path) -> bool:
642
+ """Save MCP configuration.
643
+
644
+ Args:
645
+ config: Configuration dictionary
646
+ config_path: Path to save the configuration
647
+
648
+ Returns:
649
+ bool: True if save was successful
650
+ """
651
+ try:
652
+ # Ensure directory exists
653
+ config_path.parent.mkdir(parents=True, exist_ok=True)
654
+
655
+ # Create backup if file exists
656
+ if config_path.exists():
657
+ from datetime import datetime
658
+
659
+ backup_path = (
660
+ config_path.parent
661
+ / f".mcp.backup.{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}.json"
662
+ )
663
+ import shutil
664
+
665
+ shutil.copy2(config_path, backup_path)
666
+ print(f" šŸ“ Created backup: {backup_path}")
667
+
668
+ # Write configuration with proper formatting
669
+ with open(config_path, "w") as f:
670
+ json.dump(config, f, indent=2)
671
+ f.write("\n") # Add newline at end of file
672
+
673
+ print(f" šŸ’¾ Saved configuration to {config_path}")
674
+ return True
675
+
676
+ except Exception as e:
677
+ print(f"āŒ Error saving config: {e}")
678
+ return False
679
+
680
+ def _get_pipx_config(self, package_name: str, project_path: Path) -> Optional[Dict]:
681
+ """Get configuration for a pipx-installed package.
682
+
683
+ Args:
684
+ package_name: Name of the package (e.g., "mcp-browser")
685
+ project_path: Path to the project directory
686
+
687
+ Returns:
688
+ Configuration dict for the service or None if not found
689
+ """
690
+ pipx_venv = self._pipx_path / package_name
691
+ if not pipx_venv.exists():
692
+ return None
693
+
694
+ if package_name == "mcp-browser":
695
+ # mcp-browser uses Python module invocation for MCP mode
696
+ python_path = pipx_venv / "bin" / "python"
697
+ if python_path.exists():
698
+ # Check if module is importable
699
+ try:
700
+ result = subprocess.run(
701
+ [str(python_path), "-c", "import mcp_browser.cli.main"],
702
+ capture_output=True,
703
+ timeout=5,
704
+ check=False,
705
+ )
706
+ if result.returncode == 0:
707
+ return {
708
+ "type": "stdio",
709
+ "command": str(python_path),
710
+ "args": ["-m", "mcp_browser.cli.main", "mcp"],
711
+ "env": {
712
+ "MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser")
713
+ },
714
+ }
715
+ except Exception:
716
+ pass
717
+ elif package_name == "mcp-vector-search":
718
+ # mcp-vector-search uses Python module invocation
719
+ python_path = pipx_venv / "bin" / "python"
720
+ if python_path.exists():
721
+ # Check if module is importable
722
+ try:
723
+ result = subprocess.run(
724
+ [str(python_path), "-c", "import mcp_vector_search"],
725
+ capture_output=True,
726
+ timeout=5,
727
+ check=False,
728
+ )
729
+ if result.returncode == 0:
730
+ return {
731
+ "type": "stdio",
732
+ "command": str(python_path),
733
+ "args": [
734
+ "-m",
735
+ "mcp_vector_search.mcp.server",
736
+ str(project_path),
737
+ ],
738
+ "env": {},
739
+ }
740
+ except Exception:
741
+ pass
742
+
743
+ return None
744
+
745
+ def _check_pipx_installation(self, package_name: str) -> Tuple[bool, str]:
746
+ """Check if a package is installed via pipx.
747
+
748
+ Args:
749
+ package_name: Name of the package to check
750
+
751
+ Returns:
752
+ Tuple of (is_installed, installation_type)
753
+ """
754
+ pipx_venv = self._pipx_path / package_name
755
+ if pipx_venv.exists():
756
+ return True, "pipx"
757
+
758
+ # Check if available as Python module
759
+ module_name = package_name.replace("-", "_")
760
+ if self._check_python_package(module_name):
761
+ return True, "pip"
762
+
763
+ return False, "none"
764
+
765
+ def fix_browser_configuration(self) -> bool:
766
+ """Quick fix for mcp-browser configuration in project .mcp.json.
767
+
768
+ Updates only the mcp-browser configuration in the project's .mcp.json
769
+ to use the best available installation (pipx preferred).
770
+
771
+ Returns:
772
+ bool: True if configuration was updated successfully
773
+ """
774
+ print("\nšŸ”§ Fixing mcp-browser Configuration")
775
+ print("=" * 50)
776
+
777
+ project_path = Path.cwd()
778
+ config_path = project_path / ".mcp.json"
779
+
780
+ print(f"šŸ“ Project directory: {project_path}")
781
+ print(f"šŸ“„ Using config: {config_path}")
782
+
783
+ # Check if mcp-browser is installed
784
+ is_installed, install_type = self._check_pipx_installation("mcp-browser")
785
+ if not is_installed:
786
+ print("āŒ mcp-browser is not installed")
787
+ print(" Install with: pipx install mcp-browser")
788
+ return False
789
+
790
+ if install_type != "pipx":
791
+ print("āš ļø mcp-browser is not installed via pipx")
792
+ print(" For best results, install with: pipx install mcp-browser")
793
+
794
+ # Get best configuration for mcp-browser
795
+ browser_config = self._get_best_service_config("mcp-browser", project_path)
796
+ if not browser_config:
797
+ print("āŒ Could not determine mcp-browser configuration")
798
+ return False
799
+
800
+ # Load project configuration
801
+ config = self._load_config(config_path)
802
+ if not config:
803
+ print("āŒ Failed to load configuration")
804
+ return False
805
+
806
+ # Update mcp-browser configuration
807
+ if "mcpServers" not in config:
808
+ config["mcpServers"] = {}
809
+
810
+ config["mcpServers"]["mcp-browser"] = browser_config
811
+
812
+ # Save configuration
813
+ if self._save_config(config, config_path):
814
+ print("āœ… Successfully updated mcp-browser configuration in .mcp.json")
815
+ print(f" Command: {browser_config['command']}")
816
+ print(f" Args: {browser_config['args']}")
817
+ print("\nšŸ“Œ Note: Claude Desktop will automatically use this configuration")
818
+ print(" when you open this project directory.")
819
+ return True
820
+ print("āŒ Failed to save configuration")
821
+ return False
822
+
823
+ def list_external_services(self) -> None:
824
+ """List all available external MCP services and their status."""
825
+ print("\nšŸ“‹ Available External MCP Services")
826
+ print("=" * 50)
827
+
828
+ # Check project-level .mcp.json
829
+ project_path = Path.cwd()
830
+ mcp_config_path = project_path / ".mcp.json"
831
+ mcp_config = {}
832
+
833
+ if mcp_config_path.exists():
834
+ try:
835
+ with open(mcp_config_path) as f:
836
+ mcp_config = json.load(f)
837
+ print(f"\nšŸ“ Project MCP config: {mcp_config_path}")
838
+ except Exception:
839
+ print("\nāš ļø Could not read project .mcp.json")
840
+ else:
841
+ print("\nšŸ“ No .mcp.json found in project directory")
842
+
843
+ # Get service configurations for this project
844
+ services = self.get_project_services(project_path)
845
+
846
+ for service_name, service_info in services.items():
847
+ print(f"\n{service_name}:")
848
+ print(f" Description: {service_info['description']}")
849
+ print(f" Python Package: {service_info['package_name']}")
850
+
851
+ # Check if configured in .mcp.json
852
+ if mcp_config.get("mcpServers", {}).get(service_name):
853
+ print(" Project Status: āœ… Configured in .mcp.json")
854
+ service_config = mcp_config["mcpServers"][service_name]
855
+ print(f" Command: {service_config.get('command')}")
856
+ if service_config.get("args"):
857
+ print(f" Args: {service_config.get('args')}")
858
+ else:
859
+ print(" Project Status: āŒ Not configured in .mcp.json")
860
+
861
+ # Check installation type
862
+ is_installed, install_type = self._check_pipx_installation(service_name)
863
+ if is_installed:
864
+ if install_type == "pipx":
865
+ print(" Installation: āœ… Installed via pipx (recommended)")
866
+ else:
867
+ print(" Installation: āœ… Installed via pip")
868
+ else:
869
+ print(" Installation: āŒ Not installed")
870
+ print(f" Install with: pipx install {service_info['package_name']}")