claude-mpm 5.1.9__py3-none-any.whl → 5.4.14__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (162) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +85 -0
  4. claude_mpm/agents/agent_loader.py +13 -44
  5. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  6. claude_mpm/cli/__main__.py +4 -0
  7. claude_mpm/cli/commands/agent_state_manager.py +8 -17
  8. claude_mpm/cli/commands/auto_configure.py +210 -25
  9. claude_mpm/cli/commands/config.py +88 -2
  10. claude_mpm/cli/commands/configure.py +1097 -158
  11. claude_mpm/cli/commands/configure_agent_display.py +15 -6
  12. claude_mpm/cli/commands/mpm_init/core.py +160 -46
  13. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  14. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  15. claude_mpm/cli/commands/skills.py +21 -2
  16. claude_mpm/cli/commands/summarize.py +413 -0
  17. claude_mpm/cli/executor.py +11 -3
  18. claude_mpm/cli/parsers/base_parser.py +5 -0
  19. claude_mpm/cli/parsers/config_parser.py +153 -83
  20. claude_mpm/cli/parsers/skills_parser.py +3 -2
  21. claude_mpm/cli/startup.py +333 -89
  22. claude_mpm/commands/mpm-config.md +266 -0
  23. claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
  24. claude_mpm/config/agent_sources.py +27 -0
  25. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  26. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  27. claude_mpm/core/framework_loader.py +4 -2
  28. claude_mpm/core/logger.py +13 -0
  29. claude_mpm/core/socketio_pool.py +3 -3
  30. claude_mpm/core/unified_agent_registry.py +5 -15
  31. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  32. claude_mpm/hooks/claude_hooks/event_handlers.py +206 -78
  33. claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
  34. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  35. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  36. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  37. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  38. claude_mpm/hooks/memory_integration_hook.py +46 -1
  39. claude_mpm/init.py +0 -19
  40. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  41. claude_mpm/scripts/launch_monitor.py +93 -13
  42. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  43. claude_mpm/services/agents/agent_review_service.py +280 -0
  44. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  45. claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
  46. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
  47. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +335 -53
  48. claude_mpm/services/agents/git_source_manager.py +34 -0
  49. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  50. claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
  51. claude_mpm/services/agents/toolchain_detector.py +10 -6
  52. claude_mpm/services/analysis/__init__.py +11 -1
  53. claude_mpm/services/analysis/clone_detector.py +1030 -0
  54. claude_mpm/services/command_deployment_service.py +71 -10
  55. claude_mpm/services/event_bus/config.py +3 -1
  56. claude_mpm/services/git/git_operations_service.py +93 -8
  57. claude_mpm/services/monitor/daemon.py +9 -2
  58. claude_mpm/services/monitor/daemon_manager.py +39 -3
  59. claude_mpm/services/monitor/server.py +225 -19
  60. claude_mpm/services/self_upgrade_service.py +120 -12
  61. claude_mpm/services/skills/__init__.py +3 -0
  62. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  63. claude_mpm/services/skills/selective_skill_deployer.py +230 -0
  64. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  65. claude_mpm/services/skills_deployer.py +64 -3
  66. claude_mpm/services/socketio/event_normalizer.py +15 -1
  67. claude_mpm/services/socketio/server/core.py +160 -21
  68. claude_mpm/services/version_control/git_operations.py +103 -0
  69. claude_mpm/utils/agent_filters.py +17 -44
  70. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/METADATA +47 -84
  71. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/RECORD +76 -150
  72. claude_mpm-5.4.14.dist-info/entry_points.txt +5 -0
  73. claude_mpm-5.4.14.dist-info/licenses/LICENSE +94 -0
  74. claude_mpm-5.4.14.dist-info/licenses/LICENSE-FAQ.md +153 -0
  75. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  76. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  77. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  78. claude_mpm/agents/BASE_OPS.md +0 -219
  79. claude_mpm/agents/BASE_PM.md +0 -480
  80. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  81. claude_mpm/agents/BASE_QA.md +0 -167
  82. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  83. claude_mpm/agents/base_agent.json +0 -31
  84. claude_mpm/agents/base_agent_loader.py +0 -601
  85. claude_mpm/cli/ticket_cli.py +0 -35
  86. claude_mpm/commands/mpm-config-view.md +0 -150
  87. claude_mpm/dashboard/analysis_runner.py +0 -455
  88. claude_mpm/dashboard/index.html +0 -13
  89. claude_mpm/dashboard/open_dashboard.py +0 -66
  90. claude_mpm/dashboard/static/css/activity.css +0 -1958
  91. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  92. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  93. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  94. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  95. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  96. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  97. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  98. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  99. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  100. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  101. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  102. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  103. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  104. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  105. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  106. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  107. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  108. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  109. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  110. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  111. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  112. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  113. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  114. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  115. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  116. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  117. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  118. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  119. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  120. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  121. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  122. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  123. claude_mpm/dashboard/templates/code_simple.html +0 -153
  124. claude_mpm/dashboard/templates/index.html +0 -606
  125. claude_mpm/dashboard/test_dashboard.html +0 -372
  126. claude_mpm/scripts/mcp_server.py +0 -75
  127. claude_mpm/scripts/mcp_wrapper.py +0 -39
  128. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  129. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  130. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  131. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  132. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  133. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  134. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  135. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  136. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  137. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  138. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  139. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  140. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  141. claude_mpm/services/mcp_gateway/main.py +0 -589
  142. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  143. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  144. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  145. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  146. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  147. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  148. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  149. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  150. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  151. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  152. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  153. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  154. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  155. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  156. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  157. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  158. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  159. claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
  160. claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
  161. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/WHEEL +0 -0
  162. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/top_level.txt +0 -0
@@ -1,654 +0,0 @@
1
- """
2
- External MCP Services Integration
3
- ==================================
4
-
5
- Manages detection and setup of external MCP services like mcp-vector-search
6
- and mcp-browser. These services run as separate MCP servers in Claude Code,
7
- not as part of the Claude MPM MCP Gateway.
8
-
9
- IMPORTANT: External services are NOT auto-installed. Users must manually install
10
- them using pipx or pip. This gives users explicit control over which optional
11
- services they want to enable.
12
-
13
- Installation:
14
- pipx install mcp-vector-search
15
- pipx install mcp-browser
16
- pipx install kuzu-memory
17
- pipx install mcp-ticketer
18
-
19
- Note: External services are registered as separate MCP servers in Claude Code
20
- configuration, not as tools within the gateway.
21
- """
22
-
23
- import json
24
- import subprocess
25
- import sys
26
- from pathlib import Path
27
- from typing import Any, Dict, List
28
-
29
- from claude_mpm.services.mcp_gateway.tools.base_adapter import BaseToolAdapter
30
-
31
-
32
- class ExternalMCPService(BaseToolAdapter):
33
- """Base class for external MCP service integration.
34
-
35
- External services are detected if already installed but are NOT
36
- automatically installed. Users must install them manually using
37
- pipx or pip to enable these optional features.
38
- """
39
-
40
- def __init__(self, service_name: str, package_name: str):
41
- """
42
- Initialize external MCP service.
43
-
44
- Args:
45
- service_name: Name of the service for MCP
46
- package_name: Python package name to install/run
47
- """
48
- # Import here to avoid circular imports
49
- from claude_mpm.services.mcp_gateway.core.interfaces import MCPToolDefinition
50
-
51
- # Create a basic tool definition for the service
52
- tool_def = MCPToolDefinition(
53
- name=service_name,
54
- description=f"External MCP service: {package_name}",
55
- input_schema={
56
- "type": "object",
57
- "properties": {},
58
- "required": [],
59
- },
60
- )
61
- super().__init__(tool_def)
62
- self.service_name = service_name
63
- self.package_name = package_name
64
- self.process = None
65
- self._is_installed = False
66
-
67
- async def invoke(self, invocation):
68
- """
69
- Invoke method required by BaseToolAdapter interface.
70
-
71
- This base implementation should be overridden by subclasses.
72
- """
73
- # Import here to avoid circular imports
74
- from claude_mpm.services.mcp_gateway.core.interfaces import MCPToolResult
75
-
76
- return MCPToolResult(
77
- success=False,
78
- error="invoke method not implemented in base ExternalMCPService",
79
- execution_time=0.0,
80
- )
81
-
82
- async def initialize(
83
- self, auto_install: bool = False, interactive: bool = False
84
- ) -> bool:
85
- """Initialize the external service.
86
-
87
- NOTE: Auto-installation is disabled by default (v4.9.0+). Users must
88
- manually install external services using pipx or pip.
89
-
90
- Args:
91
- auto_install: Whether to automatically install if not found (default: False)
92
- Deprecated - will be removed in future versions
93
- interactive: Whether to prompt user for installation preferences (default: False)
94
- Only used if auto_install=True
95
- """
96
- try:
97
- # Check if package is installed
98
- self._is_installed = await self._check_installation()
99
-
100
- if not self._is_installed and auto_install:
101
- # This path is deprecated but kept for backward compatibility
102
- self.logger.warning(
103
- f"Auto-installation is deprecated. Please install {self.package_name} manually: "
104
- f"pipx install {self.package_name}"
105
- )
106
- await self._install_package(interactive=interactive)
107
- self._is_installed = await self._check_installation()
108
-
109
- if not self._is_installed:
110
- self.logger.debug(
111
- f"{self.package_name} is not available. "
112
- f"Install manually with: pipx install {self.package_name}"
113
- )
114
- return False
115
-
116
- self.logger.info(f"{self.package_name} is available")
117
- return True
118
-
119
- except Exception as e:
120
- self.logger.error(f"Failed to initialize {self.service_name}: {e}")
121
- return False
122
-
123
- async def _check_installation(self) -> bool:
124
- """Check if the package is installed."""
125
- # First check if importable (faster and more reliable)
126
- import_name = self.package_name.replace("-", "_")
127
- try:
128
- import importlib.util
129
-
130
- spec = importlib.util.find_spec(import_name)
131
- if spec is not None:
132
- return True
133
- except (ImportError, ModuleNotFoundError, ValueError):
134
- pass
135
-
136
- # Fallback: try running as module
137
- try:
138
- result = subprocess.run(
139
- [sys.executable, "-m", import_name, "--help"],
140
- capture_output=True,
141
- text=True,
142
- timeout=5,
143
- check=False,
144
- )
145
- return result.returncode == 0
146
- except (
147
- subprocess.TimeoutExpired,
148
- FileNotFoundError,
149
- subprocess.CalledProcessError,
150
- ):
151
- return False
152
-
153
- async def _install_package(self, interactive: bool = True) -> bool:
154
- """Install the package using pip or pipx.
155
-
156
- Args:
157
- interactive: Whether to prompt user for installation method choice
158
- """
159
- try:
160
- install_method = None
161
-
162
- if interactive:
163
- # Show user-friendly installation prompt
164
- print(f"\n⚠️ {self.package_name} not found", file=sys.stderr)
165
- print(
166
- "This package enables enhanced functionality (optional).",
167
- file=sys.stderr,
168
- )
169
- print("\nInstallation options:", file=sys.stderr)
170
- print(
171
- "1. Install via pip (recommended for this project)", file=sys.stderr
172
- )
173
- print("2. Install via pipx (isolated, system-wide)", file=sys.stderr)
174
- print("3. Skip (continue without this package)", file=sys.stderr)
175
-
176
- try:
177
- choice = input("\nChoose option (1/2/3) [1]: ").strip() or "1"
178
- if choice == "1":
179
- install_method = "pip"
180
- elif choice == "2":
181
- install_method = "pipx"
182
- else:
183
- self.logger.info(
184
- f"Skipping installation of {self.package_name}"
185
- )
186
- return False
187
- except (EOFError, KeyboardInterrupt):
188
- print("\nInstallation cancelled", file=sys.stderr)
189
- return False
190
- else:
191
- # Non-interactive: default to pip
192
- install_method = "pip"
193
-
194
- # Install using selected method
195
- if install_method == "pip":
196
- return await self._install_via_pip()
197
- if install_method == "pipx":
198
- return await self._install_via_pipx()
199
-
200
- return False
201
-
202
- except Exception as e:
203
- self.logger.error(f"Error installing {self.package_name}: {e}")
204
- return False
205
-
206
- async def _install_via_pip(self) -> bool:
207
- """Install package via pip."""
208
- try:
209
- print(f"\n📦 Installing {self.package_name} via pip...", file=sys.stderr)
210
- result = subprocess.run(
211
- [sys.executable, "-m", "pip", "install", self.package_name],
212
- capture_output=True,
213
- text=True,
214
- timeout=120,
215
- check=False,
216
- )
217
-
218
- if result.returncode == 0:
219
- print(f"✓ Successfully installed {self.package_name}", file=sys.stderr)
220
- self.logger.info(f"Successfully installed {self.package_name} via pip")
221
- return True
222
-
223
- error_msg = result.stderr.strip() if result.stderr else "Unknown error"
224
- print(f"✗ Installation failed: {error_msg}", file=sys.stderr)
225
- self.logger.error(f"Failed to install {self.package_name}: {error_msg}")
226
- return False
227
-
228
- except subprocess.TimeoutExpired:
229
- print("✗ Installation timed out", file=sys.stderr)
230
- self.logger.error(f"Installation of {self.package_name} timed out")
231
- return False
232
- except Exception as e:
233
- print(f"✗ Installation error: {e}", file=sys.stderr)
234
- self.logger.error(f"Error installing {self.package_name}: {e}")
235
- return False
236
-
237
- async def _install_via_pipx(self) -> bool:
238
- """Install package via pipx."""
239
- try:
240
- # Check if pipx is available
241
- pipx_check = subprocess.run(
242
- ["pipx", "--version"],
243
- capture_output=True,
244
- text=True,
245
- timeout=5,
246
- check=False,
247
- )
248
-
249
- if pipx_check.returncode != 0:
250
- print("✗ pipx is not installed", file=sys.stderr)
251
- print("Install pipx first: python -m pip install pipx", file=sys.stderr)
252
- self.logger.error("pipx not available for installation")
253
- return False
254
-
255
- print(f"\n📦 Installing {self.package_name} via pipx...", file=sys.stderr)
256
- result = subprocess.run(
257
- ["pipx", "install", self.package_name],
258
- capture_output=True,
259
- text=True,
260
- timeout=120,
261
- check=False,
262
- )
263
-
264
- if result.returncode == 0:
265
- print(f"✓ Successfully installed {self.package_name}", file=sys.stderr)
266
- self.logger.info(f"Successfully installed {self.package_name} via pipx")
267
- return True
268
-
269
- error_msg = result.stderr.strip() if result.stderr else "Unknown error"
270
- print(f"✗ Installation failed: {error_msg}", file=sys.stderr)
271
- self.logger.error(f"Failed to install {self.package_name}: {error_msg}")
272
- return False
273
-
274
- except FileNotFoundError:
275
- print("✗ pipx command not found", file=sys.stderr)
276
- print("Install pipx first: python -m pip install pipx", file=sys.stderr)
277
- self.logger.error("pipx command not found")
278
- return False
279
- except subprocess.TimeoutExpired:
280
- print("✗ Installation timed out", file=sys.stderr)
281
- self.logger.error(f"Installation of {self.package_name} timed out")
282
- return False
283
- except Exception as e:
284
- print(f"✗ Installation error: {e}", file=sys.stderr)
285
- self.logger.error(f"Error installing {self.package_name}: {e}")
286
- return False
287
-
288
- def get_definition(self) -> Dict[str, Any]:
289
- """Get service definition for MCP registration."""
290
- return {
291
- "name": self.service_name,
292
- "description": f"External MCP service: {self.package_name}",
293
- "type": "external_service",
294
- "package": self.package_name,
295
- "installed": self._is_installed,
296
- }
297
-
298
-
299
- class MCPVectorSearchService(ExternalMCPService):
300
- """MCP Vector Search service integration."""
301
-
302
- def __init__(self):
303
- """Initialize MCP Vector Search service."""
304
- super().__init__("mcp-vector-search", "mcp-vector-search")
305
-
306
- def get_definition(self) -> Dict[str, Any]:
307
- """Get tool definition for MCP registration."""
308
- base_def = super().get_definition()
309
- base_def.update(
310
- {
311
- "description": "Semantic code search powered by vector embeddings",
312
- "tools": [
313
- {
314
- "name": "mcp__mcp-vector-search__search_code",
315
- "description": "Search for code using semantic similarity",
316
- "inputSchema": {
317
- "type": "object",
318
- "properties": {
319
- "query": {
320
- "type": "string",
321
- "description": "The search query",
322
- },
323
- "limit": {"type": "integer", "default": 10},
324
- "similarity_threshold": {
325
- "type": "number",
326
- "default": 0.3,
327
- },
328
- "language": {"type": "string"},
329
- "file_extensions": {
330
- "type": "array",
331
- "items": {"type": "string"},
332
- },
333
- "files": {"type": "string"},
334
- "class_name": {"type": "string"},
335
- "function_name": {"type": "string"},
336
- },
337
- "required": ["query"],
338
- },
339
- },
340
- {
341
- "name": "mcp__mcp-vector-search__search_similar",
342
- "description": "Find code similar to a specific file or function",
343
- "inputSchema": {
344
- "type": "object",
345
- "properties": {
346
- "file_path": {
347
- "type": "string",
348
- "description": "Path to the file",
349
- },
350
- "function_name": {"type": "string"},
351
- "limit": {"type": "integer", "default": 10},
352
- "similarity_threshold": {
353
- "type": "number",
354
- "default": 0.3,
355
- },
356
- },
357
- "required": ["file_path"],
358
- },
359
- },
360
- {
361
- "name": "mcp__mcp-vector-search__search_context",
362
- "description": "Search for code based on contextual description",
363
- "inputSchema": {
364
- "type": "object",
365
- "properties": {
366
- "description": {
367
- "type": "string",
368
- "description": "Contextual description",
369
- },
370
- "focus_areas": {
371
- "type": "array",
372
- "items": {"type": "string"},
373
- },
374
- "limit": {"type": "integer", "default": 10},
375
- },
376
- "required": ["description"],
377
- },
378
- },
379
- {
380
- "name": "mcp__mcp-vector-search__get_project_status",
381
- "description": "Get project indexing status and statistics",
382
- "inputSchema": {
383
- "type": "object",
384
- "properties": {},
385
- "required": [],
386
- },
387
- },
388
- {
389
- "name": "mcp__mcp-vector-search__index_project",
390
- "description": "Index or reindex the project codebase",
391
- "inputSchema": {
392
- "type": "object",
393
- "properties": {
394
- "force": {"type": "boolean", "default": False},
395
- "file_extensions": {
396
- "type": "array",
397
- "items": {"type": "string"},
398
- },
399
- },
400
- "required": [],
401
- },
402
- },
403
- ],
404
- }
405
- )
406
- return base_def
407
-
408
- async def invoke(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
409
- """Invoke a tool from mcp-vector-search."""
410
- try:
411
- # Extract the actual tool name (remove prefix)
412
- actual_tool = tool_name.replace("mcp__mcp-vector-search__", "")
413
-
414
- # Prepare the command
415
- cmd = [
416
- sys.executable,
417
- "-m",
418
- "mcp_vector_search",
419
- "--tool",
420
- actual_tool,
421
- "--args",
422
- json.dumps(arguments),
423
- ]
424
-
425
- # Run the command
426
- result = subprocess.run(
427
- cmd,
428
- capture_output=True,
429
- text=True,
430
- timeout=30,
431
- cwd=Path.cwd(),
432
- check=False, # Use current working directory for project context
433
- )
434
-
435
- if result.returncode == 0:
436
- try:
437
- return json.loads(result.stdout)
438
- except json.JSONDecodeError:
439
- return {"result": result.stdout}
440
- else:
441
- return {"error": result.stderr or "Tool invocation failed"}
442
-
443
- except subprocess.TimeoutExpired:
444
- return {"error": "Tool invocation timed out"}
445
- except Exception as e:
446
- return {"error": str(e)}
447
-
448
-
449
- class MCPBrowserService(ExternalMCPService):
450
- """MCP Browser service integration."""
451
-
452
- def __init__(self):
453
- """Initialize MCP Browser service."""
454
- super().__init__("mcp-browser", "mcp-browser")
455
-
456
- def get_definition(self) -> Dict[str, Any]:
457
- """Get tool definition for MCP registration."""
458
- base_def = super().get_definition()
459
- base_def.update(
460
- {
461
- "description": "Web browsing and content extraction capabilities",
462
- "tools": [
463
- {
464
- "name": "mcp__mcp-browser__browse",
465
- "description": "Browse a webpage and extract content",
466
- "inputSchema": {
467
- "type": "object",
468
- "properties": {
469
- "url": {
470
- "type": "string",
471
- "description": "URL to browse",
472
- },
473
- "extract": {
474
- "type": "string",
475
- "description": "What to extract",
476
- },
477
- },
478
- "required": ["url"],
479
- },
480
- },
481
- {
482
- "name": "mcp__mcp-browser__search",
483
- "description": "Search the web",
484
- "inputSchema": {
485
- "type": "object",
486
- "properties": {
487
- "query": {
488
- "type": "string",
489
- "description": "Search query",
490
- },
491
- "num_results": {"type": "integer", "default": 10},
492
- },
493
- "required": ["query"],
494
- },
495
- },
496
- {
497
- "name": "mcp__mcp-browser__screenshot",
498
- "description": "Take a screenshot of a webpage",
499
- "inputSchema": {
500
- "type": "object",
501
- "properties": {
502
- "url": {
503
- "type": "string",
504
- "description": "URL to screenshot",
505
- },
506
- "full_page": {"type": "boolean", "default": False},
507
- },
508
- "required": ["url"],
509
- },
510
- },
511
- ],
512
- }
513
- )
514
- return base_def
515
-
516
- async def invoke(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
517
- """Invoke a tool from mcp-browser."""
518
- try:
519
- # Extract the actual tool name (remove prefix)
520
- actual_tool = tool_name.replace("mcp__mcp-browser__", "")
521
-
522
- # Prepare the command
523
- cmd = [
524
- sys.executable,
525
- "-m",
526
- "mcp_browser",
527
- "--tool",
528
- actual_tool,
529
- "--args",
530
- json.dumps(arguments),
531
- ]
532
-
533
- # Run the command
534
- result = subprocess.run(
535
- cmd, capture_output=True, text=True, timeout=30, check=False
536
- )
537
-
538
- if result.returncode == 0:
539
- try:
540
- return json.loads(result.stdout)
541
- except json.JSONDecodeError:
542
- return {"result": result.stdout}
543
- else:
544
- return {"error": result.stderr or "Tool invocation failed"}
545
-
546
- except subprocess.TimeoutExpired:
547
- return {"error": "Tool invocation timed out"}
548
- except Exception as e:
549
- return {"error": str(e)}
550
-
551
-
552
- class ExternalMCPServiceManager:
553
- """Manager for external MCP services.
554
-
555
- This manager is responsible for detecting (but NOT installing) Python packages
556
- for external MCP services. The actual registration of these services happens
557
- in Claude Code configuration as separate MCP servers.
558
-
559
- IMPORTANT: As of v4.9.0, this manager NO LONGER auto-installs missing services.
560
- Users must manually install external services using pipx or pip:
561
- - pipx install mcp-vector-search
562
- - pipx install mcp-browser
563
- - pipx install kuzu-memory
564
- - pipx install mcp-ticketer
565
-
566
- Note: This class is maintained for backward compatibility and service detection.
567
- The actual tool registration is handled by separate MCP server instances in
568
- Claude Code.
569
- """
570
-
571
- def __init__(self):
572
- """Initialize the service manager."""
573
- self.services: List[ExternalMCPService] = []
574
- self.logger = None
575
-
576
- async def initialize_services(self) -> List[ExternalMCPService]:
577
- """Initialize all external MCP services.
578
-
579
- This method checks if external service packages are already installed
580
- and registers them if available. It does NOT auto-install missing services.
581
-
582
- External MCP services (mcp-vector-search, mcp-browser, kuzu-memory, mcp-ticketer)
583
- must be manually installed by users. This gives users explicit control over
584
- which services they want to use.
585
-
586
- Installation instructions:
587
- - mcp-vector-search: pipx install mcp-vector-search
588
- - mcp-browser: pipx install mcp-browser
589
- - kuzu-memory: pipx install kuzu-memory
590
- - mcp-ticketer: pipx install mcp-ticketer
591
-
592
- Services run as separate MCP servers in Claude Code, not as tools within
593
- the gateway.
594
- """
595
- # Create service instances
596
- # Note: kuzu-memory and mcp-ticketer are configured via MCPConfigManager
597
- # and run as separate MCP servers. They don't need to be included here
598
- # since they're already set up through the MCP config.
599
- services = [MCPVectorSearchService(), MCPBrowserService()]
600
-
601
- # Initialize each service (check if installed, but DO NOT auto-install)
602
- initialized_services = []
603
- for service in services:
604
- try:
605
- # Pass auto_install=False to prevent automatic installation
606
- if await service.initialize(auto_install=False, interactive=False):
607
- initialized_services.append(service)
608
- if self.logger:
609
- self.logger.info(
610
- f"Initialized external service: {service.service_name}"
611
- )
612
- elif self.logger:
613
- self.logger.debug(
614
- f"Service not available (optional): {service.service_name}. "
615
- f"Install manually with: pipx install {service.package_name}"
616
- )
617
- except Exception as e:
618
- if self.logger:
619
- self.logger.error(f"Error initializing {service.service_name}: {e}")
620
-
621
- self.services = initialized_services
622
- return initialized_services
623
-
624
- def get_all_tools(self) -> List[Dict[str, Any]]:
625
- """Get all tool definitions from external services."""
626
- all_tools = []
627
- for service in self.services:
628
- service_def = service.get_definition()
629
- if "tools" in service_def:
630
- all_tools.extend(service_def["tools"])
631
- return all_tools
632
-
633
- async def invoke_tool(
634
- self, tool_name: str, arguments: Dict[str, Any]
635
- ) -> Dict[str, Any]:
636
- """Invoke a tool from any registered external service."""
637
- # Find the service that handles this tool
638
- for service in self.services:
639
- if tool_name.startswith(f"mcp__{service.service_name}__"):
640
- if isinstance(service, (MCPVectorSearchService, MCPBrowserService)):
641
- return await service.invoke(tool_name, arguments)
642
-
643
- return {"error": f"No service found for tool: {tool_name}"}
644
-
645
- async def shutdown(self):
646
- """Shutdown all external services."""
647
- for service in self.services:
648
- try:
649
- await service.shutdown()
650
- except Exception as e:
651
- if self.logger:
652
- self.logger.warning(
653
- f"Error shutting down {service.service_name}: {e}"
654
- )