claude-mpm 4.3.12__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/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 +1 -1
- claude_mpm/cli/commands/mcp_config.py +7 -10
- claude_mpm/cli/commands/mcp_external_commands.py +40 -32
- claude_mpm/cli/commands/mcp_install_commands.py +38 -10
- claude_mpm/cli/commands/mcp_setup_external.py +143 -102
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init_handler.py +1 -1
- claude_mpm/cli/commands/run.py +46 -2
- claude_mpm/cli/commands/search.py +41 -34
- claude_mpm/cli/interactive/agent_wizard.py +2 -2
- claude_mpm/cli/parsers/mcp_parser.py +1 -3
- claude_mpm/cli/parsers/search_parser.py +10 -4
- claude_mpm/cli/startup_logging.py +3 -5
- claude_mpm/cli/utils.py +1 -1
- 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 +2 -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 +2 -1
- claude_mpm/services/agents/deployment/agent_discovery_service.py +9 -3
- 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/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_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 +175 -30
- 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 +1 -1
- claude_mpm/services/mcp_gateway/core/base.py +2 -2
- claude_mpm/services/mcp_gateway/main.py +21 -7
- 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 +190 -137
- 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.12.dist-info → claude_mpm-4.3.13.dist-info}/METADATA +1 -1
- {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/RECORD +199 -199
- {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/WHEEL +0 -0
- {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/top_level.txt +0 -0
|
@@ -7,7 +7,7 @@ Assembles sections and applies template variable substitution.
|
|
|
7
7
|
import hashlib
|
|
8
8
|
import logging
|
|
9
9
|
from collections import OrderedDict
|
|
10
|
-
from datetime import datetime
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
11
|
from typing import Any, Dict, Optional
|
|
12
12
|
|
|
13
13
|
from claude_mpm.services.agents.management import AgentCapabilitiesGenerator
|
|
@@ -35,7 +35,7 @@ class ContentAssembler:
|
|
|
35
35
|
Returns:
|
|
36
36
|
str: 16-character hash of content
|
|
37
37
|
"""
|
|
38
|
-
timestamp = datetime.
|
|
38
|
+
timestamp = datetime.now(timezone.utc).isoformat()
|
|
39
39
|
hash_obj = hashlib.sha256(timestamp.encode())
|
|
40
40
|
return hash_obj.hexdigest()[:16]
|
|
41
41
|
|
|
@@ -176,7 +176,7 @@ class ContentAssembler:
|
|
|
176
176
|
Returns:
|
|
177
177
|
Dict: Metadata for header
|
|
178
178
|
"""
|
|
179
|
-
timestamp = datetime.
|
|
179
|
+
timestamp = datetime.now(timezone.utc).isoformat()
|
|
180
180
|
|
|
181
181
|
return {
|
|
182
182
|
"version": version,
|
|
@@ -5,7 +5,7 @@ This module provides base classes and registry for section generators.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
|
-
from datetime import datetime
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
9
|
from typing import Any, Dict, Optional
|
|
10
10
|
|
|
11
11
|
|
|
@@ -35,7 +35,7 @@ class BaseSectionGenerator(ABC):
|
|
|
35
35
|
|
|
36
36
|
def get_timestamp(self) -> str:
|
|
37
37
|
"""Get current UTC timestamp."""
|
|
38
|
-
return datetime.
|
|
38
|
+
return datetime.now(timezone.utc).isoformat()
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class SectionGeneratorRegistry:
|
|
@@ -24,7 +24,7 @@ class HookInstallerService:
|
|
|
24
24
|
self.claude_dir = Path.home() / ".claude"
|
|
25
25
|
self.settings_file = self.claude_dir / "settings.json"
|
|
26
26
|
|
|
27
|
-
def is_hooks_configured(self) -> bool:
|
|
27
|
+
def is_hooks_configured(self) -> bool: # noqa: PLR0911
|
|
28
28
|
"""Check if hooks are configured in Claude settings.
|
|
29
29
|
|
|
30
30
|
Returns:
|
|
@@ -16,7 +16,7 @@ import gzip
|
|
|
16
16
|
import json
|
|
17
17
|
import shutil
|
|
18
18
|
from dataclasses import dataclass, field
|
|
19
|
-
from datetime import datetime
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
20
|
from typing import Any, Dict, List, Optional
|
|
21
21
|
|
|
22
22
|
import ijson # For streaming JSON parsing
|
|
@@ -203,7 +203,9 @@ class ContextPreservationService(BaseService):
|
|
|
203
203
|
await self._create_backup()
|
|
204
204
|
|
|
205
205
|
# Load and filter conversations
|
|
206
|
-
cutoff_time = datetime.now().timestamp() - (
|
|
206
|
+
cutoff_time = datetime.now(timezone.utc).timestamp() - (
|
|
207
|
+
keep_recent_days * 86400
|
|
208
|
+
)
|
|
207
209
|
|
|
208
210
|
with open(self.claude_json_path) as f:
|
|
209
211
|
data = json.load(f)
|
|
@@ -292,7 +294,7 @@ class ContextPreservationService(BaseService):
|
|
|
292
294
|
try:
|
|
293
295
|
if Path(file_path).exists():
|
|
294
296
|
valid_files.append(file_path)
|
|
295
|
-
except:
|
|
297
|
+
except Exception:
|
|
296
298
|
pass # Invalid path
|
|
297
299
|
|
|
298
300
|
return valid_files
|
|
@@ -516,7 +518,7 @@ class ContextPreservationService(BaseService):
|
|
|
516
518
|
async def _create_backup(self) -> Path:
|
|
517
519
|
"""Create backup of Claude configuration."""
|
|
518
520
|
try:
|
|
519
|
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
521
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
|
520
522
|
backup_name = f"claude_backup_{timestamp}.json"
|
|
521
523
|
|
|
522
524
|
# Compress if large
|
|
@@ -107,7 +107,7 @@ class SocketIODaemonManager:
|
|
|
107
107
|
)
|
|
108
108
|
if response.status_code == 200:
|
|
109
109
|
return response.json()
|
|
110
|
-
except:
|
|
110
|
+
except Exception:
|
|
111
111
|
pass
|
|
112
112
|
return None
|
|
113
113
|
|
|
@@ -265,7 +265,7 @@ class SocketIODaemonManager:
|
|
|
265
265
|
result = sock.connect_ex((self.host, self.port))
|
|
266
266
|
sock.close()
|
|
267
267
|
status_info["port_accessible"] = result == 0
|
|
268
|
-
except:
|
|
268
|
+
except Exception:
|
|
269
269
|
status_info["port_accessible"] = False
|
|
270
270
|
|
|
271
271
|
# Check for conflicts
|
|
@@ -11,7 +11,7 @@ Part of TSK-0046: Service Layer Architecture Reorganization
|
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
13
|
import logging
|
|
14
|
-
from datetime import datetime
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
15
|
from typing import Any, Dict, List, Optional
|
|
16
16
|
|
|
17
17
|
from claude_mpm.core.logger import get_logger
|
|
@@ -92,7 +92,7 @@ class LoggingService(SyncBaseService, IStructuredLogger):
|
|
|
92
92
|
|
|
93
93
|
if self.structured_logging:
|
|
94
94
|
log_entry = {
|
|
95
|
-
"timestamp": datetime.
|
|
95
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
96
96
|
"level": level,
|
|
97
97
|
"message": message,
|
|
98
98
|
"context": context,
|
|
@@ -10,14 +10,25 @@ MCP service installations.
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
|
-
import os
|
|
14
13
|
import subprocess
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from enum import Enum
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
from typing import Dict, Optional, Tuple
|
|
17
18
|
|
|
18
19
|
from ..core.logger import get_logger
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
class ConfigLocation(Enum):
|
|
23
|
+
"""Enumeration of Claude configuration file locations."""
|
|
24
|
+
|
|
25
|
+
CLAUDE_JSON = Path.home() / ".claude.json" # Primary Claude config
|
|
26
|
+
CLAUDE_DESKTOP = (
|
|
27
|
+
Path.home() / ".claude" / "claude_desktop_config.json"
|
|
28
|
+
) # Not used by Claude Code
|
|
29
|
+
PROJECT_MCP = ".mcp.json" # Project-level MCP config (deprecated)
|
|
30
|
+
|
|
31
|
+
|
|
21
32
|
class MCPConfigManager:
|
|
22
33
|
"""Manages MCP service configurations with pipx preference."""
|
|
23
34
|
|
|
@@ -34,6 +45,9 @@ class MCPConfigManager:
|
|
|
34
45
|
self.pipx_base = Path.home() / ".local" / "pipx" / "venvs"
|
|
35
46
|
self.project_root = Path.cwd()
|
|
36
47
|
|
|
48
|
+
# Use the proper Claude config file location
|
|
49
|
+
self.claude_config_path = ConfigLocation.CLAUDE_JSON.value
|
|
50
|
+
|
|
37
51
|
def detect_service_path(self, service_name: str) -> Optional[str]:
|
|
38
52
|
"""
|
|
39
53
|
Detect the best path for an MCP service.
|
|
@@ -161,9 +175,7 @@ class MCPConfigManager:
|
|
|
161
175
|
config["env"] = {}
|
|
162
176
|
elif service_name == "mcp-browser":
|
|
163
177
|
config["args"] = ["mcp"]
|
|
164
|
-
config["env"] = {
|
|
165
|
-
"MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser")
|
|
166
|
-
}
|
|
178
|
+
config["env"] = {"MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser")}
|
|
167
179
|
elif service_name == "mcp-ticketer":
|
|
168
180
|
config["args"] = ["mcp"]
|
|
169
181
|
else:
|
|
@@ -172,9 +184,109 @@ class MCPConfigManager:
|
|
|
172
184
|
|
|
173
185
|
return config
|
|
174
186
|
|
|
187
|
+
def ensure_mcp_services_configured(self) -> Tuple[bool, str]:
|
|
188
|
+
"""
|
|
189
|
+
Ensure MCP services are configured in ~/.claude.json on startup.
|
|
190
|
+
|
|
191
|
+
This method checks if the core MCP services are configured in the
|
|
192
|
+
current project's mcpServers section and automatically adds them if missing.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Tuple of (success, message)
|
|
196
|
+
"""
|
|
197
|
+
updated = False
|
|
198
|
+
added_services = []
|
|
199
|
+
project_key = str(self.project_root)
|
|
200
|
+
|
|
201
|
+
# Load existing Claude config or create minimal structure
|
|
202
|
+
claude_config = {}
|
|
203
|
+
if self.claude_config_path.exists():
|
|
204
|
+
try:
|
|
205
|
+
with open(self.claude_config_path) as f:
|
|
206
|
+
claude_config = json.load(f)
|
|
207
|
+
except Exception as e:
|
|
208
|
+
self.logger.error(f"Error reading {self.claude_config_path}: {e}")
|
|
209
|
+
return False, f"Failed to read Claude config: {e}"
|
|
210
|
+
|
|
211
|
+
# Ensure projects structure exists
|
|
212
|
+
if "projects" not in claude_config:
|
|
213
|
+
claude_config["projects"] = {}
|
|
214
|
+
|
|
215
|
+
if project_key not in claude_config["projects"]:
|
|
216
|
+
claude_config["projects"][project_key] = {
|
|
217
|
+
"allowedTools": [],
|
|
218
|
+
"history": [],
|
|
219
|
+
"mcpContextUris": [],
|
|
220
|
+
"mcpServers": {},
|
|
221
|
+
"enabledMcpjsonServers": [],
|
|
222
|
+
"disabledMcpjsonServers": [],
|
|
223
|
+
"hasTrustDialogAccepted": False,
|
|
224
|
+
"projectOnboardingSeenCount": 0,
|
|
225
|
+
"hasClaudeMdExternalIncludesApproved": False,
|
|
226
|
+
"hasClaudeMdExternalIncludesWarningShown": False,
|
|
227
|
+
}
|
|
228
|
+
updated = True
|
|
229
|
+
|
|
230
|
+
# Get the project's mcpServers section
|
|
231
|
+
project_config = claude_config["projects"][project_key]
|
|
232
|
+
if "mcpServers" not in project_config:
|
|
233
|
+
project_config["mcpServers"] = {}
|
|
234
|
+
updated = True
|
|
235
|
+
|
|
236
|
+
# Check each service and add if missing
|
|
237
|
+
for service_name in self.PIPX_SERVICES:
|
|
238
|
+
if service_name not in project_config["mcpServers"]:
|
|
239
|
+
# Try to detect and configure the service
|
|
240
|
+
service_path = self.detect_service_path(service_name)
|
|
241
|
+
if service_path:
|
|
242
|
+
config = self.generate_service_config(service_name)
|
|
243
|
+
if config:
|
|
244
|
+
project_config["mcpServers"][service_name] = config
|
|
245
|
+
added_services.append(service_name)
|
|
246
|
+
updated = True
|
|
247
|
+
self.logger.debug(
|
|
248
|
+
f"Added MCP service to config: {service_name}"
|
|
249
|
+
)
|
|
250
|
+
else:
|
|
251
|
+
self.logger.debug(
|
|
252
|
+
f"MCP service {service_name} not found for auto-configuration"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Write updated config if changes were made
|
|
256
|
+
if updated:
|
|
257
|
+
try:
|
|
258
|
+
# Create backup if file exists and is large (> 100KB)
|
|
259
|
+
if self.claude_config_path.exists():
|
|
260
|
+
file_size = self.claude_config_path.stat().st_size
|
|
261
|
+
if file_size > 100000: # 100KB
|
|
262
|
+
backup_path = self.claude_config_path.with_suffix(
|
|
263
|
+
f".backup.{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}.json"
|
|
264
|
+
)
|
|
265
|
+
import shutil
|
|
266
|
+
|
|
267
|
+
shutil.copy2(self.claude_config_path, backup_path)
|
|
268
|
+
self.logger.debug(f"Created backup: {backup_path}")
|
|
269
|
+
|
|
270
|
+
# Write updated config
|
|
271
|
+
with open(self.claude_config_path, "w") as f:
|
|
272
|
+
json.dump(claude_config, f, indent=2)
|
|
273
|
+
|
|
274
|
+
if added_services:
|
|
275
|
+
message = (
|
|
276
|
+
f"Auto-configured MCP services: {', '.join(added_services)}"
|
|
277
|
+
)
|
|
278
|
+
# Don't log here - let the caller handle logging to avoid duplicates
|
|
279
|
+
return True, message
|
|
280
|
+
return True, "All MCP services already configured"
|
|
281
|
+
except Exception as e:
|
|
282
|
+
self.logger.error(f"Failed to write Claude config: {e}")
|
|
283
|
+
return False, f"Failed to write configuration: {e}"
|
|
284
|
+
|
|
285
|
+
return True, "All MCP services already configured"
|
|
286
|
+
|
|
175
287
|
def update_mcp_config(self, force_pipx: bool = True) -> Tuple[bool, str]:
|
|
176
288
|
"""
|
|
177
|
-
Update the
|
|
289
|
+
Update the MCP configuration in ~/.claude.json.
|
|
178
290
|
|
|
179
291
|
Args:
|
|
180
292
|
force_pipx: If True, only use pipx installations
|
|
@@ -182,13 +294,27 @@ class MCPConfigManager:
|
|
|
182
294
|
Returns:
|
|
183
295
|
Tuple of (success, message)
|
|
184
296
|
"""
|
|
185
|
-
|
|
297
|
+
# This method now delegates to ensure_mcp_services_configured
|
|
298
|
+
# since we're updating the Claude config directly
|
|
299
|
+
return self.ensure_mcp_services_configured()
|
|
300
|
+
|
|
301
|
+
def update_project_mcp_config(self, force_pipx: bool = True) -> Tuple[bool, str]:
|
|
302
|
+
"""
|
|
303
|
+
Update the .mcp.json configuration file (legacy method).
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
force_pipx: If True, only use pipx installations
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Tuple of (success, message)
|
|
310
|
+
"""
|
|
311
|
+
mcp_config_path = self.project_root / ConfigLocation.PROJECT_MCP.value
|
|
186
312
|
|
|
187
313
|
# Load existing config if it exists
|
|
188
314
|
existing_config = {}
|
|
189
315
|
if mcp_config_path.exists():
|
|
190
316
|
try:
|
|
191
|
-
with open(mcp_config_path
|
|
317
|
+
with open(mcp_config_path) as f:
|
|
192
318
|
existing_config = json.load(f)
|
|
193
319
|
except Exception as e:
|
|
194
320
|
self.logger.error(f"Error reading existing config: {e}")
|
|
@@ -203,12 +329,11 @@ class MCPConfigManager:
|
|
|
203
329
|
new_config["mcpServers"][service_name] = config
|
|
204
330
|
elif force_pipx:
|
|
205
331
|
missing_services.append(service_name)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
][service_name]
|
|
332
|
+
# Keep existing config if not forcing pipx
|
|
333
|
+
elif service_name in existing_config.get("mcpServers", {}):
|
|
334
|
+
new_config["mcpServers"][service_name] = existing_config["mcpServers"][
|
|
335
|
+
service_name
|
|
336
|
+
]
|
|
212
337
|
|
|
213
338
|
# Add any additional services from existing config
|
|
214
339
|
for service_name, config in existing_config.get("mcpServers", {}).items():
|
|
@@ -223,8 +348,7 @@ class MCPConfigManager:
|
|
|
223
348
|
if missing_services:
|
|
224
349
|
message = f"Updated .mcp.json. Missing services (install via pipx): {', '.join(missing_services)}"
|
|
225
350
|
return True, message
|
|
226
|
-
|
|
227
|
-
return True, "Successfully updated .mcp.json with pipx paths"
|
|
351
|
+
return True, "Successfully updated .mcp.json with pipx paths"
|
|
228
352
|
except Exception as e:
|
|
229
353
|
return False, f"Failed to update .mcp.json: {e}"
|
|
230
354
|
|
|
@@ -235,23 +359,45 @@ class MCPConfigManager:
|
|
|
235
359
|
Returns:
|
|
236
360
|
Dict mapping service names to availability status
|
|
237
361
|
"""
|
|
238
|
-
|
|
239
|
-
|
|
362
|
+
project_key = str(self.project_root)
|
|
363
|
+
|
|
364
|
+
# Check Claude config
|
|
365
|
+
if not self.claude_config_path.exists():
|
|
366
|
+
# Also check legacy .mcp.json
|
|
367
|
+
mcp_config_path = self.project_root / ConfigLocation.PROJECT_MCP.value
|
|
368
|
+
if mcp_config_path.exists():
|
|
369
|
+
try:
|
|
370
|
+
with open(mcp_config_path) as f:
|
|
371
|
+
config = json.load(f)
|
|
372
|
+
results = {}
|
|
373
|
+
for service_name, service_config in config.get(
|
|
374
|
+
"mcpServers", {}
|
|
375
|
+
).items():
|
|
376
|
+
command_path = service_config.get("command", "")
|
|
377
|
+
results[service_name] = Path(command_path).exists()
|
|
378
|
+
return results
|
|
379
|
+
except Exception:
|
|
380
|
+
pass
|
|
240
381
|
return {}
|
|
241
382
|
|
|
242
383
|
try:
|
|
243
|
-
with open(
|
|
244
|
-
|
|
384
|
+
with open(self.claude_config_path) as f:
|
|
385
|
+
claude_config = json.load(f)
|
|
386
|
+
|
|
387
|
+
# Get project's MCP servers
|
|
388
|
+
if "projects" in claude_config and project_key in claude_config["projects"]:
|
|
389
|
+
mcp_servers = claude_config["projects"][project_key].get(
|
|
390
|
+
"mcpServers", {}
|
|
391
|
+
)
|
|
392
|
+
results = {}
|
|
393
|
+
for service_name, service_config in mcp_servers.items():
|
|
394
|
+
command_path = service_config.get("command", "")
|
|
395
|
+
results[service_name] = Path(command_path).exists()
|
|
396
|
+
return results
|
|
245
397
|
except Exception as e:
|
|
246
398
|
self.logger.error(f"Error reading config: {e}")
|
|
247
|
-
return {}
|
|
248
399
|
|
|
249
|
-
|
|
250
|
-
for service_name, service_config in config.get("mcpServers", {}).items():
|
|
251
|
-
command_path = service_config.get("command", "")
|
|
252
|
-
results[service_name] = Path(command_path).exists()
|
|
253
|
-
|
|
254
|
-
return results
|
|
400
|
+
return {}
|
|
255
401
|
|
|
256
402
|
def install_missing_services(self) -> Tuple[bool, str]:
|
|
257
403
|
"""
|
|
@@ -274,7 +420,7 @@ class MCPConfigManager:
|
|
|
274
420
|
for service_name in missing:
|
|
275
421
|
try:
|
|
276
422
|
self.logger.info(f"Installing {service_name} via pipx...")
|
|
277
|
-
|
|
423
|
+
subprocess.run(
|
|
278
424
|
["pipx", "install", service_name],
|
|
279
425
|
capture_output=True,
|
|
280
426
|
text=True,
|
|
@@ -288,7 +434,6 @@ class MCPConfigManager:
|
|
|
288
434
|
|
|
289
435
|
if failed:
|
|
290
436
|
return False, f"Failed to install: {', '.join(failed)}"
|
|
291
|
-
|
|
437
|
+
if installed:
|
|
292
438
|
return True, f"Successfully installed: {', '.join(installed)}"
|
|
293
|
-
|
|
294
|
-
return True, "No services needed installation"
|
|
439
|
+
return True, "No services needed installation"
|
|
@@ -26,7 +26,7 @@ __version__ = "0.1.0"
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
# Lazy imports to prevent circular dependencies and improve startup performance
|
|
29
|
-
def __getattr__(name):
|
|
29
|
+
def __getattr__(name): # noqa: PLR0911
|
|
30
30
|
"""Lazy import mechanism for MCP Gateway components."""
|
|
31
31
|
|
|
32
32
|
# Core interfaces and base classes
|
|
@@ -20,7 +20,7 @@ DESIGN DECISIONS:
|
|
|
20
20
|
import json
|
|
21
21
|
import os
|
|
22
22
|
import sys
|
|
23
|
-
from datetime import datetime
|
|
23
|
+
from datetime import datetime, timezone
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
from typing import Any, Dict, Optional
|
|
26
26
|
|
|
@@ -144,7 +144,7 @@ class MCPAutoConfigurator:
|
|
|
144
144
|
prefs = {
|
|
145
145
|
"asked": True,
|
|
146
146
|
"choice": choice,
|
|
147
|
-
"timestamp": datetime.now().isoformat(),
|
|
147
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
try:
|
|
@@ -266,7 +266,7 @@ class MCPAutoConfigurator:
|
|
|
266
266
|
def _create_backup(self) -> Optional[Path]:
|
|
267
267
|
"""Create backup of existing configuration."""
|
|
268
268
|
try:
|
|
269
|
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
269
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
|
270
270
|
backup_path = self.claude_config_path.with_suffix(
|
|
271
271
|
f".backup.{timestamp}.json"
|
|
272
272
|
)
|
|
@@ -279,10 +279,10 @@ class BaseMCPService(BaseService):
|
|
|
279
279
|
healthy: Whether service is healthy
|
|
280
280
|
details: Additional health details
|
|
281
281
|
"""
|
|
282
|
-
from datetime import datetime
|
|
282
|
+
from datetime import datetime, timezone
|
|
283
283
|
|
|
284
284
|
self._health_status["healthy"] = healthy
|
|
285
|
-
self._health_status["last_check"] = datetime.now().isoformat()
|
|
285
|
+
self._health_status["last_check"] = datetime.now(timezone.utc).isoformat()
|
|
286
286
|
|
|
287
287
|
if details:
|
|
288
288
|
self._health_status["details"].update(details)
|
|
@@ -213,21 +213,35 @@ class MCPGatewayOrchestrator:
|
|
|
213
213
|
try:
|
|
214
214
|
self.logger.info("Initializing external MCP services...")
|
|
215
215
|
self.external_services = ExternalMCPServiceManager()
|
|
216
|
-
external_services =
|
|
216
|
+
external_services = (
|
|
217
|
+
await self.external_services.initialize_services()
|
|
218
|
+
)
|
|
217
219
|
|
|
218
220
|
if external_services and self.registry:
|
|
219
221
|
for service in external_services:
|
|
220
222
|
try:
|
|
221
|
-
if self.registry.register_tool(
|
|
222
|
-
|
|
223
|
+
if self.registry.register_tool(
|
|
224
|
+
service, category="external"
|
|
225
|
+
):
|
|
226
|
+
self.logger.info(
|
|
227
|
+
f"Registered external service: {service.service_name}"
|
|
228
|
+
)
|
|
223
229
|
else:
|
|
224
|
-
self.logger.warning(
|
|
230
|
+
self.logger.warning(
|
|
231
|
+
f"Failed to register external service: {service.service_name}"
|
|
232
|
+
)
|
|
225
233
|
except Exception as e:
|
|
226
|
-
self.logger.warning(
|
|
234
|
+
self.logger.warning(
|
|
235
|
+
f"Error registering {service.service_name}: {e}"
|
|
236
|
+
)
|
|
227
237
|
|
|
228
|
-
self.logger.info(
|
|
238
|
+
self.logger.info(
|
|
239
|
+
f"Initialized {len(external_services)} external MCP services"
|
|
240
|
+
)
|
|
229
241
|
except Exception as e:
|
|
230
|
-
self.logger.warning(
|
|
242
|
+
self.logger.warning(
|
|
243
|
+
f"Failed to initialize external MCP services: {e}"
|
|
244
|
+
)
|
|
231
245
|
self.external_services = None
|
|
232
246
|
|
|
233
247
|
# Initialize communication handler with fallback
|
|
@@ -11,7 +11,7 @@ Part of ISS-0035: MCP Server Implementation - Core Server and Tool Registry
|
|
|
11
11
|
import asyncio
|
|
12
12
|
import re
|
|
13
13
|
import traceback
|
|
14
|
-
from datetime import datetime
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
15
|
from threading import RLock
|
|
16
16
|
from typing import Any, Dict, List, Optional, Set
|
|
17
17
|
|
|
@@ -159,9 +159,9 @@ class ToolRegistry(BaseMCPService, IMCPToolRegistry):
|
|
|
159
159
|
|
|
160
160
|
# Update metrics
|
|
161
161
|
self._metrics["total_tools"] = len(self._adapters)
|
|
162
|
-
self._metrics["registration_time"][
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
self._metrics["registration_time"][tool_name] = datetime.now(
|
|
163
|
+
timezone.utc
|
|
164
|
+
).isoformat()
|
|
165
165
|
self._metrics["invocations"][tool_name] = 0
|
|
166
166
|
self._metrics["errors"][tool_name] = 0
|
|
167
167
|
|
|
@@ -272,7 +272,7 @@ class ToolRegistry(BaseMCPService, IMCPToolRegistry):
|
|
|
272
272
|
error handling, metrics tracking, and validation.
|
|
273
273
|
"""
|
|
274
274
|
tool_name = invocation.tool_name
|
|
275
|
-
start_time = datetime.now()
|
|
275
|
+
start_time = datetime.now(timezone.utc)
|
|
276
276
|
|
|
277
277
|
try:
|
|
278
278
|
self.log_info(f"Invoking tool: {tool_name}")
|
|
@@ -300,7 +300,7 @@ class ToolRegistry(BaseMCPService, IMCPToolRegistry):
|
|
|
300
300
|
result = await adapter.invoke(invocation)
|
|
301
301
|
|
|
302
302
|
# Calculate execution time
|
|
303
|
-
execution_time = (datetime.now() - start_time).total_seconds()
|
|
303
|
+
execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
|
|
304
304
|
result.execution_time = execution_time
|
|
305
305
|
|
|
306
306
|
# Update metrics
|
|
@@ -308,7 +308,9 @@ class ToolRegistry(BaseMCPService, IMCPToolRegistry):
|
|
|
308
308
|
self._metrics["invocations"][tool_name] = (
|
|
309
309
|
self._metrics["invocations"].get(tool_name, 0) + 1
|
|
310
310
|
)
|
|
311
|
-
self._metrics["last_invocation"][tool_name] = datetime.now(
|
|
311
|
+
self._metrics["last_invocation"][tool_name] = datetime.now(
|
|
312
|
+
timezone.utc
|
|
313
|
+
).isoformat()
|
|
312
314
|
|
|
313
315
|
if not result.success:
|
|
314
316
|
self._metrics["errors"][tool_name] = (
|
|
@@ -331,7 +333,7 @@ class ToolRegistry(BaseMCPService, IMCPToolRegistry):
|
|
|
331
333
|
self._metrics["errors"].get(tool_name, 0) + 1
|
|
332
334
|
)
|
|
333
335
|
|
|
334
|
-
execution_time = (datetime.now() - start_time).total_seconds()
|
|
336
|
+
execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
|
|
335
337
|
|
|
336
338
|
return MCPToolResult(
|
|
337
339
|
success=False, error=error_msg, execution_time=execution_time
|
|
@@ -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
|
|