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
@@ -16,7 +16,7 @@ import asyncio
16
16
  import contextlib
17
17
  import json
18
18
  import traceback
19
- from datetime import datetime
19
+ from datetime import datetime, timezone
20
20
  from typing import Any, Callable, Dict, List, Optional, Union
21
21
 
22
22
  # Import from the official MCP package
@@ -148,7 +148,7 @@ class MCPGateway(BaseMCPService, IMCPGateway):
148
148
  invocation = MCPToolInvocation(
149
149
  tool_name=name,
150
150
  parameters=arguments,
151
- request_id=f"req_{datetime.now().timestamp()}",
151
+ request_id=f"req_{datetime.now(timezone.utc).timestamp()}",
152
152
  )
153
153
 
154
154
  try:
@@ -212,7 +212,7 @@ class MCPGateway(BaseMCPService, IMCPGateway):
212
212
  self.log_warning("No tool registry set - server will have no tools")
213
213
 
214
214
  # Initialize metrics
215
- self._metrics["start_time"] = datetime.now().isoformat()
215
+ self._metrics["start_time"] = datetime.now(timezone.utc).isoformat()
216
216
 
217
217
  # Update capabilities based on registry
218
218
  if self._tool_registry:
@@ -287,7 +287,7 @@ class MCPGateway(BaseMCPService, IMCPGateway):
287
287
  try:
288
288
  # Update metrics
289
289
  self._metrics["requests_handled"] += 1
290
- self._metrics["last_request_time"] = datetime.now().isoformat()
290
+ self._metrics["last_request_time"] = datetime.now(timezone.utc).isoformat()
291
291
 
292
292
  # Extract request details
293
293
  method = request.get("method", "")
@@ -139,7 +139,7 @@ class StdioHandler(BaseMCPService, IMCPCommunication):
139
139
  self._metrics["errors"] += 1
140
140
  raise
141
141
 
142
- async def receive_message(self) -> Optional[Dict[str, Any]]:
142
+ async def receive_message(self) -> Optional[Dict[str, Any]]: # noqa: PLR0911
143
143
  """
144
144
  Receive a message from the MCP client via stdin.
145
145
 
@@ -17,6 +17,7 @@ import logging
17
17
  import os
18
18
  import sys
19
19
  import time
20
+ from datetime import timezone
20
21
  from typing import Any, Dict
21
22
 
22
23
  # Import MCP SDK components
@@ -172,7 +173,7 @@ class SimpleMCPServer:
172
173
 
173
174
  async def _summarize_content(
174
175
  self, content: str, style: str, max_length: int
175
- ) -> str:
176
+ ) -> str: # noqa: PLR0911
176
177
  """
177
178
  Summarize text content based on style and length constraints.
178
179
 
@@ -214,7 +215,7 @@ class SimpleMCPServer:
214
215
  # Default to brief
215
216
  return self._create_brief_summary(sentences, max_length)
216
217
 
217
- def _create_brief_summary(self, sentences: list[str], max_length: int) -> str:
218
+ def _create_brief_summary(self, sentences: list[str], max_length: int) -> str: # noqa: PLR0911
218
219
  """Create a brief summary by selecting most important sentences."""
219
220
  if not sentences:
220
221
  return ""
@@ -562,7 +563,7 @@ class SimpleMCPServer:
562
563
  f"Python: {sys.version.split()[0]}\n"
563
564
  f"Working Directory: {os.getcwd()}\n"
564
565
  f"Server: {self.name} v{self.version}\n"
565
- f"Timestamp: {datetime.datetime.now().isoformat()}\n"
566
+ f"Timestamp: {datetime.datetime.now(timezone.utc).isoformat()}\n"
566
567
  f"Tools Available: status, document_summarizer{', ticket' if self.unified_ticket_tool else ''}"
567
568
  )
568
569
  else:
@@ -8,7 +8,7 @@ Part of ISS-0035: MCP Server Implementation - Core Server and Tool Registry
8
8
  """
9
9
 
10
10
  from abc import ABC
11
- from datetime import datetime
11
+ from datetime import datetime, timezone
12
12
  from typing import Any, Dict
13
13
 
14
14
  from claude_mpm.services.mcp_gateway.core.base import BaseMCPService
@@ -195,7 +195,7 @@ class BaseToolAdapter(BaseMCPService, IMCPToolAdapter, ABC):
195
195
  self._metrics["average_execution_time"] = (
196
196
  self._metrics["total_execution_time"] / self._metrics["invocations"]
197
197
  )
198
- self._metrics["last_invocation"] = datetime.now().isoformat()
198
+ self._metrics["last_invocation"] = datetime.now(timezone.utc).isoformat()
199
199
 
200
200
 
201
201
  class EchoToolAdapter(BaseToolAdapter):
@@ -235,7 +235,7 @@ class EchoToolAdapter(BaseToolAdapter):
235
235
  Returns:
236
236
  Tool execution result with echoed message
237
237
  """
238
- start_time = datetime.now()
238
+ start_time = datetime.now(timezone.utc)
239
239
 
240
240
  try:
241
241
  # Get parameters
@@ -246,7 +246,7 @@ class EchoToolAdapter(BaseToolAdapter):
246
246
  result = message.upper() if uppercase else message
247
247
 
248
248
  # Calculate execution time
249
- execution_time = (datetime.now() - start_time).total_seconds()
249
+ execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
250
250
 
251
251
  # Update metrics
252
252
  self._update_metrics(True, execution_time)
@@ -259,7 +259,7 @@ class EchoToolAdapter(BaseToolAdapter):
259
259
  )
260
260
 
261
261
  except Exception as e:
262
- execution_time = (datetime.now() - start_time).total_seconds()
262
+ execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
263
263
  self._update_metrics(False, execution_time)
264
264
  self._metrics["last_error"] = str(e)
265
265
 
@@ -321,7 +321,7 @@ class CalculatorToolAdapter(BaseToolAdapter):
321
321
  Returns:
322
322
  Tool execution result with calculation
323
323
  """
324
- start_time = datetime.now()
324
+ start_time = datetime.now(timezone.utc)
325
325
 
326
326
  try:
327
327
  # Get parameters
@@ -348,7 +348,7 @@ class CalculatorToolAdapter(BaseToolAdapter):
348
348
  raise ValueError(f"Unknown operation: {operation}")
349
349
 
350
350
  # Calculate execution time
351
- execution_time = (datetime.now() - start_time).total_seconds()
351
+ execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
352
352
 
353
353
  # Update metrics
354
354
  self._update_metrics(True, execution_time)
@@ -361,7 +361,7 @@ class CalculatorToolAdapter(BaseToolAdapter):
361
361
  )
362
362
 
363
363
  except Exception as e:
364
- execution_time = (datetime.now() - start_time).total_seconds()
364
+ execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
365
365
  self._update_metrics(False, execution_time)
366
366
  self._metrics["last_error"] = str(e)
367
367
 
@@ -408,7 +408,7 @@ class SystemInfoToolAdapter(BaseToolAdapter):
408
408
  Returns:
409
409
  Tool execution result with system information
410
410
  """
411
- start_time = datetime.now()
411
+ start_time = datetime.now(timezone.utc)
412
412
 
413
413
  try:
414
414
  import platform
@@ -443,15 +443,15 @@ class SystemInfoToolAdapter(BaseToolAdapter):
443
443
  }
444
444
  elif info_type == "time":
445
445
  result = {
446
- "current": datetime.now().isoformat(),
447
- "timestamp": datetime.now().timestamp(),
448
- "timezone": str(datetime.now().astimezone().tzinfo),
446
+ "current": datetime.now(timezone.utc).isoformat(),
447
+ "timestamp": datetime.now(timezone.utc).timestamp(),
448
+ "timezone": str(datetime.now(timezone.utc).astimezone().tzinfo),
449
449
  }
450
450
  else:
451
451
  raise ValueError(f"Unknown info type: {info_type}")
452
452
 
453
453
  # Calculate execution time
454
- execution_time = (datetime.now() - start_time).total_seconds()
454
+ execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
455
455
 
456
456
  # Update metrics
457
457
  self._update_metrics(True, execution_time)
@@ -465,7 +465,7 @@ class SystemInfoToolAdapter(BaseToolAdapter):
465
465
 
466
466
  except ImportError as e:
467
467
  # Handle missing psutil dependency gracefully
468
- execution_time = (datetime.now() - start_time).total_seconds()
468
+ execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
469
469
  self._update_metrics(False, execution_time)
470
470
 
471
471
  return MCPToolResult(
@@ -474,7 +474,7 @@ class SystemInfoToolAdapter(BaseToolAdapter):
474
474
  execution_time=execution_time,
475
475
  )
476
476
  except Exception as e:
477
- execution_time = (datetime.now() - start_time).total_seconds()
477
+ execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
478
478
  self._update_metrics(False, execution_time)
479
479
  self._metrics["last_error"] = str(e)
480
480
 
@@ -13,7 +13,7 @@ import mimetypes
13
13
  import os
14
14
  import re
15
15
  from collections import OrderedDict
16
- from datetime import datetime
16
+ from datetime import datetime, timezone
17
17
  from pathlib import Path
18
18
  from typing import Any, Dict, List, Optional, Tuple
19
19
 
@@ -605,7 +605,7 @@ class DocumentSummarizerTool(BaseToolAdapter):
605
605
  Returns:
606
606
  Tool execution result with summary
607
607
  """
608
- start_time = datetime.now()
608
+ start_time = datetime.now(timezone.utc)
609
609
 
610
610
  try:
611
611
  # Get parameters
@@ -628,7 +628,9 @@ class DocumentSummarizerTool(BaseToolAdapter):
628
628
  cached_result = self._cache.get(cache_key)
629
629
  if cached_result:
630
630
  cache_hit = True
631
- execution_time = (datetime.now() - start_time).total_seconds()
631
+ execution_time = (
632
+ datetime.now(timezone.utc) - start_time
633
+ ).total_seconds()
632
634
  self._update_metrics(True, execution_time)
633
635
 
634
636
  return MCPToolResult(
@@ -709,7 +711,7 @@ class DocumentSummarizerTool(BaseToolAdapter):
709
711
  self._cache.put(cache_key, result.copy(), summary_size)
710
712
 
711
713
  # Calculate execution time
712
- execution_time = (datetime.now() - start_time).total_seconds()
714
+ execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
713
715
 
714
716
  # Update metrics
715
717
  self._update_metrics(True, execution_time)
@@ -730,7 +732,7 @@ class DocumentSummarizerTool(BaseToolAdapter):
730
732
  )
731
733
 
732
734
  except Exception as e:
733
- execution_time = (datetime.now() - start_time).total_seconds()
735
+ execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
734
736
  self._update_metrics(False, execution_time)
735
737
  self._metrics["last_error"] = str(e)
736
738
 
@@ -0,0 +1,443 @@
1
+ """
2
+ External MCP Services Integration
3
+ ==================================
4
+
5
+ Manages installation and basic setup of external MCP services like mcp-vector-search
6
+ and mcp-browser. These services run as separate MCP servers in Claude Desktop,
7
+ not as part of the Claude MPM MCP Gateway.
8
+
9
+ Note: As of the latest architecture, external services are registered as separate
10
+ MCP servers in Claude Desktop configuration, not as tools within the gateway.
11
+ """
12
+
13
+ import json
14
+ import subprocess
15
+ import sys
16
+ from pathlib import Path
17
+ from typing import Any, Dict, List
18
+
19
+ from claude_mpm.services.mcp_gateway.tools.base_adapter import BaseMCPToolAdapter
20
+
21
+
22
+ class ExternalMCPService(BaseMCPToolAdapter):
23
+ """Base class for external MCP service integration."""
24
+
25
+ def __init__(self, service_name: str, package_name: str):
26
+ """
27
+ Initialize external MCP service.
28
+
29
+ Args:
30
+ service_name: Name of the service for MCP
31
+ package_name: Python package name to install/run
32
+ """
33
+ super().__init__()
34
+ self.service_name = service_name
35
+ self.package_name = package_name
36
+ self.process = None
37
+ self._is_installed = False
38
+
39
+ async def initialize(self) -> bool:
40
+ """Initialize the external service."""
41
+ try:
42
+ # Check if package is installed
43
+ self._is_installed = await self._check_installation()
44
+
45
+ if not self._is_installed:
46
+ self.logger.warning(
47
+ f"{self.package_name} not installed, attempting installation..."
48
+ )
49
+ await self._install_package()
50
+ self._is_installed = await self._check_installation()
51
+
52
+ if not self._is_installed:
53
+ self.logger.error(f"Failed to install {self.package_name}")
54
+ return False
55
+
56
+ self.logger.info(f"{self.package_name} is available")
57
+ return True
58
+
59
+ except Exception as e:
60
+ self.logger.error(f"Failed to initialize {self.service_name}: {e}")
61
+ return False
62
+
63
+ async def _check_installation(self) -> bool:
64
+ """Check if the package is installed."""
65
+ try:
66
+ result = subprocess.run(
67
+ [sys.executable, "-m", self.package_name.replace("-", "_"), "--help"],
68
+ capture_output=True,
69
+ text=True,
70
+ timeout=5,
71
+ check=False,
72
+ )
73
+ return result.returncode == 0
74
+ except (
75
+ subprocess.TimeoutExpired,
76
+ FileNotFoundError,
77
+ subprocess.CalledProcessError,
78
+ ):
79
+ return False
80
+
81
+ async def _install_package(self) -> bool:
82
+ """Install the package using pip."""
83
+ try:
84
+ self.logger.info(f"Installing {self.package_name}...")
85
+ result = subprocess.run(
86
+ [sys.executable, "-m", "pip", "install", self.package_name],
87
+ capture_output=True,
88
+ text=True,
89
+ timeout=30,
90
+ check=False,
91
+ )
92
+
93
+ if result.returncode == 0:
94
+ self.logger.info(f"Successfully installed {self.package_name}")
95
+ return True
96
+ self.logger.error(f"Failed to install {self.package_name}: {result.stderr}")
97
+ return False
98
+
99
+ except Exception as e:
100
+ self.logger.error(f"Error installing {self.package_name}: {e}")
101
+ return False
102
+
103
+ def get_definition(self) -> Dict[str, Any]:
104
+ """Get service definition for MCP registration."""
105
+ return {
106
+ "name": self.service_name,
107
+ "description": f"External MCP service: {self.package_name}",
108
+ "type": "external_service",
109
+ "package": self.package_name,
110
+ "installed": self._is_installed,
111
+ }
112
+
113
+
114
+ class MCPVectorSearchService(ExternalMCPService):
115
+ """MCP Vector Search service integration."""
116
+
117
+ def __init__(self):
118
+ """Initialize MCP Vector Search service."""
119
+ super().__init__("mcp-vector-search", "mcp-vector-search")
120
+
121
+ def get_definition(self) -> Dict[str, Any]:
122
+ """Get tool definition for MCP registration."""
123
+ base_def = super().get_definition()
124
+ base_def.update(
125
+ {
126
+ "description": "Semantic code search powered by vector embeddings",
127
+ "tools": [
128
+ {
129
+ "name": "mcp__mcp-vector-search__search_code",
130
+ "description": "Search for code using semantic similarity",
131
+ "inputSchema": {
132
+ "type": "object",
133
+ "properties": {
134
+ "query": {
135
+ "type": "string",
136
+ "description": "The search query",
137
+ },
138
+ "limit": {"type": "integer", "default": 10},
139
+ "similarity_threshold": {
140
+ "type": "number",
141
+ "default": 0.3,
142
+ },
143
+ "language": {"type": "string"},
144
+ "file_extensions": {
145
+ "type": "array",
146
+ "items": {"type": "string"},
147
+ },
148
+ "files": {"type": "string"},
149
+ "class_name": {"type": "string"},
150
+ "function_name": {"type": "string"},
151
+ },
152
+ "required": ["query"],
153
+ },
154
+ },
155
+ {
156
+ "name": "mcp__mcp-vector-search__search_similar",
157
+ "description": "Find code similar to a specific file or function",
158
+ "inputSchema": {
159
+ "type": "object",
160
+ "properties": {
161
+ "file_path": {
162
+ "type": "string",
163
+ "description": "Path to the file",
164
+ },
165
+ "function_name": {"type": "string"},
166
+ "limit": {"type": "integer", "default": 10},
167
+ "similarity_threshold": {
168
+ "type": "number",
169
+ "default": 0.3,
170
+ },
171
+ },
172
+ "required": ["file_path"],
173
+ },
174
+ },
175
+ {
176
+ "name": "mcp__mcp-vector-search__search_context",
177
+ "description": "Search for code based on contextual description",
178
+ "inputSchema": {
179
+ "type": "object",
180
+ "properties": {
181
+ "description": {
182
+ "type": "string",
183
+ "description": "Contextual description",
184
+ },
185
+ "focus_areas": {
186
+ "type": "array",
187
+ "items": {"type": "string"},
188
+ },
189
+ "limit": {"type": "integer", "default": 10},
190
+ },
191
+ "required": ["description"],
192
+ },
193
+ },
194
+ {
195
+ "name": "mcp__mcp-vector-search__get_project_status",
196
+ "description": "Get project indexing status and statistics",
197
+ "inputSchema": {
198
+ "type": "object",
199
+ "properties": {},
200
+ "required": [],
201
+ },
202
+ },
203
+ {
204
+ "name": "mcp__mcp-vector-search__index_project",
205
+ "description": "Index or reindex the project codebase",
206
+ "inputSchema": {
207
+ "type": "object",
208
+ "properties": {
209
+ "force": {"type": "boolean", "default": False},
210
+ "file_extensions": {
211
+ "type": "array",
212
+ "items": {"type": "string"},
213
+ },
214
+ },
215
+ "required": [],
216
+ },
217
+ },
218
+ ],
219
+ }
220
+ )
221
+ return base_def
222
+
223
+ async def invoke(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
224
+ """Invoke a tool from mcp-vector-search."""
225
+ try:
226
+ # Extract the actual tool name (remove prefix)
227
+ actual_tool = tool_name.replace("mcp__mcp-vector-search__", "")
228
+
229
+ # Prepare the command
230
+ cmd = [
231
+ sys.executable,
232
+ "-m",
233
+ "mcp_vector_search",
234
+ "--tool",
235
+ actual_tool,
236
+ "--args",
237
+ json.dumps(arguments),
238
+ ]
239
+
240
+ # Run the command
241
+ result = subprocess.run(
242
+ cmd,
243
+ capture_output=True,
244
+ text=True,
245
+ timeout=30,
246
+ cwd=Path.cwd(),
247
+ check=False, # Use current working directory for project context
248
+ )
249
+
250
+ if result.returncode == 0:
251
+ try:
252
+ return json.loads(result.stdout)
253
+ except json.JSONDecodeError:
254
+ return {"result": result.stdout}
255
+ else:
256
+ return {"error": result.stderr or "Tool invocation failed"}
257
+
258
+ except subprocess.TimeoutExpired:
259
+ return {"error": "Tool invocation timed out"}
260
+ except Exception as e:
261
+ return {"error": str(e)}
262
+
263
+
264
+ class MCPBrowserService(ExternalMCPService):
265
+ """MCP Browser service integration."""
266
+
267
+ def __init__(self):
268
+ """Initialize MCP Browser service."""
269
+ super().__init__("mcp-browser", "mcp-browser")
270
+
271
+ def get_definition(self) -> Dict[str, Any]:
272
+ """Get tool definition for MCP registration."""
273
+ base_def = super().get_definition()
274
+ base_def.update(
275
+ {
276
+ "description": "Web browsing and content extraction capabilities",
277
+ "tools": [
278
+ {
279
+ "name": "mcp__mcp-browser__browse",
280
+ "description": "Browse a webpage and extract content",
281
+ "inputSchema": {
282
+ "type": "object",
283
+ "properties": {
284
+ "url": {
285
+ "type": "string",
286
+ "description": "URL to browse",
287
+ },
288
+ "extract": {
289
+ "type": "string",
290
+ "description": "What to extract",
291
+ },
292
+ },
293
+ "required": ["url"],
294
+ },
295
+ },
296
+ {
297
+ "name": "mcp__mcp-browser__search",
298
+ "description": "Search the web",
299
+ "inputSchema": {
300
+ "type": "object",
301
+ "properties": {
302
+ "query": {
303
+ "type": "string",
304
+ "description": "Search query",
305
+ },
306
+ "num_results": {"type": "integer", "default": 10},
307
+ },
308
+ "required": ["query"],
309
+ },
310
+ },
311
+ {
312
+ "name": "mcp__mcp-browser__screenshot",
313
+ "description": "Take a screenshot of a webpage",
314
+ "inputSchema": {
315
+ "type": "object",
316
+ "properties": {
317
+ "url": {
318
+ "type": "string",
319
+ "description": "URL to screenshot",
320
+ },
321
+ "full_page": {"type": "boolean", "default": False},
322
+ },
323
+ "required": ["url"],
324
+ },
325
+ },
326
+ ],
327
+ }
328
+ )
329
+ return base_def
330
+
331
+ async def invoke(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
332
+ """Invoke a tool from mcp-browser."""
333
+ try:
334
+ # Extract the actual tool name (remove prefix)
335
+ actual_tool = tool_name.replace("mcp__mcp-browser__", "")
336
+
337
+ # Prepare the command
338
+ cmd = [
339
+ sys.executable,
340
+ "-m",
341
+ "mcp_browser",
342
+ "--tool",
343
+ actual_tool,
344
+ "--args",
345
+ json.dumps(arguments),
346
+ ]
347
+
348
+ # Run the command
349
+ result = subprocess.run(
350
+ cmd, capture_output=True, text=True, timeout=30, check=False
351
+ )
352
+
353
+ if result.returncode == 0:
354
+ try:
355
+ return json.loads(result.stdout)
356
+ except json.JSONDecodeError:
357
+ return {"result": result.stdout}
358
+ else:
359
+ return {"error": result.stderr or "Tool invocation failed"}
360
+
361
+ except subprocess.TimeoutExpired:
362
+ return {"error": "Tool invocation timed out"}
363
+ except Exception as e:
364
+ return {"error": str(e)}
365
+
366
+
367
+ class ExternalMCPServiceManager:
368
+ """Manager for external MCP services.
369
+
370
+ This manager is responsible for checking and installing Python packages
371
+ for external MCP services. The actual registration of these services
372
+ happens in Claude Desktop configuration as separate MCP servers.
373
+
374
+ Note: This class is maintained for backward compatibility and package
375
+ management. The actual tool registration is handled by separate MCP
376
+ server instances in Claude Desktop.
377
+ """
378
+
379
+ def __init__(self):
380
+ """Initialize the service manager."""
381
+ self.services: List[ExternalMCPService] = []
382
+ self.logger = None
383
+
384
+ async def initialize_services(self) -> List[ExternalMCPService]:
385
+ """Initialize all external MCP services.
386
+
387
+ This method checks if external service packages are installed
388
+ and attempts to install them if missing. It does NOT register
389
+ them as tools in the gateway - they run as separate MCP servers.
390
+ """
391
+ # Create service instances
392
+ services = [MCPVectorSearchService(), MCPBrowserService()]
393
+
394
+ # Initialize each service
395
+ initialized_services = []
396
+ for service in services:
397
+ try:
398
+ if await service.initialize():
399
+ initialized_services.append(service)
400
+ if self.logger:
401
+ self.logger.info(
402
+ f"Initialized external service: {service.service_name}"
403
+ )
404
+ elif self.logger:
405
+ self.logger.warning(f"Failed to initialize: {service.service_name}")
406
+ except Exception as e:
407
+ if self.logger:
408
+ self.logger.error(f"Error initializing {service.service_name}: {e}")
409
+
410
+ self.services = initialized_services
411
+ return initialized_services
412
+
413
+ def get_all_tools(self) -> List[Dict[str, Any]]:
414
+ """Get all tool definitions from external services."""
415
+ all_tools = []
416
+ for service in self.services:
417
+ service_def = service.get_definition()
418
+ if "tools" in service_def:
419
+ all_tools.extend(service_def["tools"])
420
+ return all_tools
421
+
422
+ async def invoke_tool(
423
+ self, tool_name: str, arguments: Dict[str, Any]
424
+ ) -> Dict[str, Any]:
425
+ """Invoke a tool from any registered external service."""
426
+ # Find the service that handles this tool
427
+ for service in self.services:
428
+ if tool_name.startswith(f"mcp__{service.service_name}__"):
429
+ if isinstance(service, (MCPVectorSearchService, MCPBrowserService)):
430
+ return await service.invoke(tool_name, arguments)
431
+
432
+ return {"error": f"No service found for tool: {tool_name}"}
433
+
434
+ async def shutdown(self):
435
+ """Shutdown all external services."""
436
+ for service in self.services:
437
+ try:
438
+ await service.shutdown()
439
+ except Exception as e:
440
+ if self.logger:
441
+ self.logger.warning(
442
+ f"Error shutting down {service.service_name}: {e}"
443
+ )