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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +390 -28
- claude_mpm/agents/templates/data_engineer.json +39 -14
- claude_mpm/agents/templates/research.json +20 -8
- claude_mpm/agents/templates/web_qa.json +25 -10
- claude_mpm/cli/__init__.py +1 -0
- claude_mpm/cli/commands/agent_manager.py +3 -3
- claude_mpm/cli/commands/agents.py +2 -2
- claude_mpm/cli/commands/aggregate.py +1 -1
- claude_mpm/cli/commands/config.py +2 -2
- claude_mpm/cli/commands/configure.py +5 -5
- claude_mpm/cli/commands/configure_tui.py +7 -7
- claude_mpm/cli/commands/dashboard.py +1 -1
- claude_mpm/cli/commands/debug.py +5 -5
- claude_mpm/cli/commands/mcp.py +1 -1
- claude_mpm/cli/commands/mcp_command_router.py +12 -1
- claude_mpm/cli/commands/mcp_config.py +154 -0
- claude_mpm/cli/commands/mcp_external_commands.py +249 -0
- claude_mpm/cli/commands/mcp_install_commands.py +93 -24
- claude_mpm/cli/commands/mcp_setup_external.py +870 -0
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init_handler.py +1 -1
- claude_mpm/cli/commands/run.py +114 -0
- claude_mpm/cli/commands/search.py +292 -0
- claude_mpm/cli/interactive/agent_wizard.py +2 -2
- claude_mpm/cli/parsers/base_parser.py +13 -0
- claude_mpm/cli/parsers/mcp_parser.py +15 -0
- claude_mpm/cli/parsers/run_parser.py +5 -0
- claude_mpm/cli/parsers/search_parser.py +245 -0
- claude_mpm/cli/startup_logging.py +3 -5
- claude_mpm/cli/utils.py +1 -1
- claude_mpm/constants.py +1 -0
- claude_mpm/core/agent_registry.py +12 -8
- claude_mpm/core/agent_session_manager.py +8 -8
- claude_mpm/core/api_validator.py +4 -4
- claude_mpm/core/base_service.py +10 -10
- claude_mpm/core/cache.py +5 -5
- claude_mpm/core/config_constants.py +1 -1
- claude_mpm/core/container.py +1 -1
- claude_mpm/core/error_handler.py +2 -2
- claude_mpm/core/file_utils.py +1 -1
- claude_mpm/core/framework_loader.py +3 -3
- claude_mpm/core/hook_manager.py +8 -6
- claude_mpm/core/instruction_reinforcement_hook.py +2 -2
- claude_mpm/core/interactive_session.py +1 -1
- claude_mpm/core/lazy.py +3 -3
- claude_mpm/core/log_manager.py +16 -12
- claude_mpm/core/logger.py +16 -11
- claude_mpm/core/logging_config.py +4 -2
- claude_mpm/core/oneshot_session.py +1 -1
- claude_mpm/core/optimized_agent_loader.py +6 -6
- claude_mpm/core/output_style_manager.py +1 -1
- claude_mpm/core/pm_hook_interceptor.py +3 -3
- claude_mpm/core/service_registry.py +1 -1
- claude_mpm/core/session_manager.py +11 -9
- claude_mpm/core/socketio_pool.py +13 -13
- claude_mpm/core/types.py +2 -2
- claude_mpm/core/unified_agent_registry.py +9 -2
- claude_mpm/core/unified_paths.py +1 -1
- claude_mpm/dashboard/analysis_runner.py +4 -4
- claude_mpm/dashboard/api/simple_directory.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +4 -2
- claude_mpm/hooks/base_hook.py +2 -2
- claude_mpm/hooks/claude_hooks/connection_pool.py +4 -4
- claude_mpm/hooks/claude_hooks/event_handlers.py +12 -12
- claude_mpm/hooks/claude_hooks/hook_handler.py +4 -4
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +3 -3
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +15 -14
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +4 -4
- claude_mpm/hooks/claude_hooks/installer.py +3 -3
- claude_mpm/hooks/claude_hooks/memory_integration.py +3 -3
- claude_mpm/hooks/claude_hooks/response_tracking.py +3 -3
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +5 -5
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +3 -3
- claude_mpm/hooks/claude_hooks/services/state_manager.py +8 -7
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +3 -3
- claude_mpm/hooks/claude_hooks/tool_analysis.py +2 -2
- claude_mpm/hooks/memory_integration_hook.py +1 -1
- claude_mpm/hooks/tool_call_interceptor.py +2 -2
- claude_mpm/models/agent_session.py +5 -5
- claude_mpm/services/__init__.py +1 -1
- claude_mpm/services/agent_capabilities_service.py +1 -1
- claude_mpm/services/agents/agent_builder.py +3 -3
- claude_mpm/services/agents/deployment/agent_deployment.py +29 -13
- claude_mpm/services/agents/deployment/agent_discovery_service.py +22 -6
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +7 -5
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +3 -1
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +1 -1
- claude_mpm/services/agents/deployment/agent_operation_service.py +2 -2
- claude_mpm/services/agents/deployment/agent_state_service.py +2 -2
- claude_mpm/services/agents/deployment/agent_template_builder.py +1 -1
- claude_mpm/services/agents/deployment/agent_versioning.py +1 -1
- claude_mpm/services/agents/deployment/deployment_wrapper.py +2 -3
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +6 -4
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +1 -1
- claude_mpm/services/agents/loading/agent_profile_loader.py +5 -3
- claude_mpm/services/agents/loading/base_agent_manager.py +2 -2
- claude_mpm/services/agents/local_template_manager.py +6 -6
- claude_mpm/services/agents/management/agent_management_service.py +3 -3
- claude_mpm/services/agents/memory/content_manager.py +3 -3
- claude_mpm/services/agents/memory/memory_format_service.py +2 -2
- claude_mpm/services/agents/memory/template_generator.py +3 -3
- claude_mpm/services/agents/registry/__init__.py +1 -1
- claude_mpm/services/agents/registry/modification_tracker.py +2 -2
- claude_mpm/services/async_session_logger.py +3 -3
- claude_mpm/services/claude_session_logger.py +4 -4
- claude_mpm/services/cli/agent_cleanup_service.py +5 -0
- claude_mpm/services/cli/agent_listing_service.py +1 -1
- claude_mpm/services/cli/agent_validation_service.py +1 -0
- claude_mpm/services/cli/memory_crud_service.py +11 -6
- claude_mpm/services/cli/memory_output_formatter.py +1 -1
- claude_mpm/services/cli/session_manager.py +15 -11
- claude_mpm/services/cli/unified_dashboard_manager.py +1 -1
- claude_mpm/services/core/memory_manager.py +81 -23
- claude_mpm/services/core/path_resolver.py +2 -2
- claude_mpm/services/diagnostics/checks/installation_check.py +1 -1
- claude_mpm/services/event_aggregator.py +4 -2
- claude_mpm/services/event_bus/direct_relay.py +5 -3
- claude_mpm/services/event_bus/event_bus.py +3 -3
- claude_mpm/services/event_bus/relay.py +6 -4
- claude_mpm/services/events/consumers/dead_letter.py +5 -3
- claude_mpm/services/events/core.py +3 -3
- claude_mpm/services/events/producers/hook.py +6 -6
- claude_mpm/services/events/producers/system.py +8 -8
- claude_mpm/services/exceptions.py +5 -5
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +3 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +2 -2
- claude_mpm/services/hook_installer_service.py +1 -1
- claude_mpm/services/infrastructure/context_preservation.py +6 -4
- claude_mpm/services/infrastructure/daemon_manager.py +2 -2
- claude_mpm/services/infrastructure/logging.py +2 -2
- claude_mpm/services/mcp_config_manager.py +439 -0
- claude_mpm/services/mcp_gateway/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/auto_configure.py +3 -3
- claude_mpm/services/mcp_gateway/config/config_loader.py +1 -1
- claude_mpm/services/mcp_gateway/config/configuration.py +18 -1
- claude_mpm/services/mcp_gateway/core/base.py +2 -2
- claude_mpm/services/mcp_gateway/main.py +52 -0
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +10 -8
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +4 -4
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
- claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -3
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +15 -15
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +7 -5
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +443 -0
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +5 -5
- claude_mpm/services/mcp_gateway/tools/hello_world.py +9 -9
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +16 -16
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +17 -17
- claude_mpm/services/memory/builder.py +7 -5
- claude_mpm/services/memory/indexed_memory.py +4 -4
- claude_mpm/services/memory/optimizer.py +6 -6
- claude_mpm/services/memory/router.py +3 -3
- claude_mpm/services/monitor/daemon.py +1 -1
- claude_mpm/services/monitor/daemon_manager.py +6 -6
- claude_mpm/services/monitor/event_emitter.py +2 -2
- claude_mpm/services/monitor/handlers/file.py +1 -1
- claude_mpm/services/monitor/management/lifecycle.py +1 -1
- claude_mpm/services/monitor/server.py +4 -4
- claude_mpm/services/monitor_build_service.py +2 -2
- claude_mpm/services/port_manager.py +2 -2
- claude_mpm/services/response_tracker.py +2 -2
- claude_mpm/services/session_management_service.py +3 -2
- claude_mpm/services/socketio/client_proxy.py +2 -2
- claude_mpm/services/socketio/dashboard_server.py +4 -3
- claude_mpm/services/socketio/event_normalizer.py +12 -8
- claude_mpm/services/socketio/handlers/base.py +2 -2
- claude_mpm/services/socketio/handlers/connection.py +10 -10
- claude_mpm/services/socketio/handlers/connection_handler.py +13 -10
- claude_mpm/services/socketio/handlers/file.py +1 -1
- claude_mpm/services/socketio/handlers/git.py +1 -1
- claude_mpm/services/socketio/handlers/hook.py +16 -15
- claude_mpm/services/socketio/migration_utils.py +1 -1
- claude_mpm/services/socketio/monitor_client.py +5 -5
- claude_mpm/services/socketio/server/broadcaster.py +9 -7
- claude_mpm/services/socketio/server/connection_manager.py +2 -2
- claude_mpm/services/socketio/server/core.py +7 -5
- claude_mpm/services/socketio/server/eventbus_integration.py +18 -11
- claude_mpm/services/socketio/server/main.py +13 -13
- claude_mpm/services/socketio_client_manager.py +4 -4
- claude_mpm/services/system_instructions_service.py +2 -2
- claude_mpm/services/ticket_services/validation_service.py +1 -1
- claude_mpm/services/utility_service.py +5 -2
- claude_mpm/services/version_control/branch_strategy.py +2 -2
- claude_mpm/services/version_control/git_operations.py +22 -20
- claude_mpm/services/version_control/semantic_versioning.py +3 -3
- claude_mpm/services/version_control/version_parser.py +7 -5
- claude_mpm/services/visualization/mermaid_generator.py +1 -1
- claude_mpm/storage/state_storage.py +1 -1
- claude_mpm/tools/code_tree_analyzer.py +19 -18
- claude_mpm/tools/code_tree_builder.py +2 -2
- claude_mpm/tools/code_tree_events.py +10 -8
- claude_mpm/tools/socketio_debug.py +3 -3
- claude_mpm/utils/agent_dependency_loader.py +2 -2
- claude_mpm/utils/dependency_strategies.py +8 -3
- claude_mpm/utils/environment_context.py +2 -2
- claude_mpm/utils/error_handler.py +2 -2
- claude_mpm/utils/file_utils.py +1 -1
- claude_mpm/utils/imports.py +1 -1
- claude_mpm/utils/log_cleanup.py +21 -7
- claude_mpm/validation/agent_validator.py +2 -2
- {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/METADATA +4 -1
- {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/RECORD +207 -200
- {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/WHEEL +0 -0
- {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/licenses/LICENSE +0 -0
- {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 = (
|
|
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
|
+
)
|