claude-mpm 4.3.12__py3-none-any.whl → 4.3.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +414 -28
- claude_mpm/agents/templates/data_engineer.json +39 -14
- claude_mpm/agents/templates/engineer.json +11 -3
- 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 +54 -2
- claude_mpm/cli/commands/search.py +41 -34
- claude_mpm/cli/interactive/agent_wizard.py +6 -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 +158 -5
- claude_mpm/cli/utils.py +1 -1
- claude_mpm/core/agent_registry.py +2 -2
- claude_mpm/core/agent_session_manager.py +8 -8
- claude_mpm/core/api_validator.py +6 -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 +3 -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/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/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
- 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/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +8 -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 +7 -5
- claude_mpm/scripts/mcp_server.py +0 -0
- claude_mpm/scripts/start_activity_logging.py +0 -0
- 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 +5 -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/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 +3 -1
- claude_mpm/services/cli/agent_validation_service.py +2 -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/core/memory_manager.py +81 -23
- claude_mpm/services/core/path_resolver.py +1 -1
- 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 +5 -2
- 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 +16 -16
- 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/management/lifecycle.py +3 -1
- claude_mpm/services/monitor/server.py +4 -4
- claude_mpm/services/monitor_build_service.py +2 -2
- claude_mpm/services/port_manager.py +3 -1
- 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 +11 -7
- 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/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 -12
- 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/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 +3 -1
- claude_mpm/storage/state_storage.py +1 -1
- claude_mpm/tools/code_tree_analyzer.py +23 -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 +6 -2
- claude_mpm/utils/dependency_strategies.py +8 -3
- claude_mpm/utils/environment_context.py +1 -1
- claude_mpm/utils/error_handler.py +2 -2
- claude_mpm/utils/file_utils.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.14.dist-info}/METADATA +1 -1
- {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.14.dist-info}/RECORD +204 -191
- {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.14.dist-info}/WHEEL +0 -0
- {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.14.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.14.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.14.dist-info}/top_level.txt +0 -0
|
@@ -11,7 +11,7 @@ WHY separate relay component:
|
|
|
11
11
|
import logging
|
|
12
12
|
import os
|
|
13
13
|
import time
|
|
14
|
-
from datetime import datetime
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
15
|
from typing import Any, Dict, Optional
|
|
16
16
|
|
|
17
17
|
# Socket.IO imports
|
|
@@ -151,7 +151,7 @@ class SocketIORelay:
|
|
|
151
151
|
# Verify connection is still alive
|
|
152
152
|
if self.client.connected:
|
|
153
153
|
return True
|
|
154
|
-
except:
|
|
154
|
+
except Exception:
|
|
155
155
|
pass
|
|
156
156
|
|
|
157
157
|
# Need to create or reconnect
|
|
@@ -187,7 +187,9 @@ class SocketIORelay:
|
|
|
187
187
|
"subtype": (
|
|
188
188
|
event_type.split(".", 1)[1] if "." in event_type else "generic"
|
|
189
189
|
),
|
|
190
|
-
"timestamp": data.get(
|
|
190
|
+
"timestamp": data.get(
|
|
191
|
+
"timestamp", datetime.now(timezone.utc).isoformat()
|
|
192
|
+
),
|
|
191
193
|
"data": data,
|
|
192
194
|
"source": "event_bus",
|
|
193
195
|
},
|
|
@@ -195,7 +197,7 @@ class SocketIORelay:
|
|
|
195
197
|
|
|
196
198
|
# Update statistics
|
|
197
199
|
self.stats["events_relayed"] += 1
|
|
198
|
-
self.stats["last_relay_time"] = datetime.now().isoformat()
|
|
200
|
+
self.stats["last_relay_time"] = datetime.now(timezone.utc).isoformat()
|
|
199
201
|
|
|
200
202
|
if self.debug:
|
|
201
203
|
logger.debug(f"Relayed event to Socket.IO: {event_type}")
|
|
@@ -6,7 +6,7 @@ Handles events that failed processing in other consumers.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
|
-
from datetime import datetime
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Any, Dict, List, Optional
|
|
12
12
|
|
|
@@ -284,7 +284,7 @@ class DeadLetterConsumer(IEventConsumer):
|
|
|
284
284
|
|
|
285
285
|
def _rotate_file(self) -> None:
|
|
286
286
|
"""Rotate to a new output file."""
|
|
287
|
-
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
287
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
|
|
288
288
|
self._current_file = self.output_dir / f"dead-letter-{timestamp}.jsonl"
|
|
289
289
|
self._current_file_size = 0
|
|
290
290
|
self._metrics["files_created"] += 1
|
|
@@ -293,7 +293,9 @@ class DeadLetterConsumer(IEventConsumer):
|
|
|
293
293
|
|
|
294
294
|
async def _cleanup_old_files(self) -> None:
|
|
295
295
|
"""Remove files older than retention period."""
|
|
296
|
-
cutoff_time = datetime.now().timestamp() - (
|
|
296
|
+
cutoff_time = datetime.now(timezone.utc).timestamp() - (
|
|
297
|
+
self.retention_days * 86400
|
|
298
|
+
)
|
|
297
299
|
|
|
298
300
|
for file_path in self.output_dir.glob("dead-letter-*.jsonl"):
|
|
299
301
|
try:
|
|
@@ -12,7 +12,7 @@ import time
|
|
|
12
12
|
import uuid
|
|
13
13
|
from collections import defaultdict, deque
|
|
14
14
|
from dataclasses import dataclass, field
|
|
15
|
-
from datetime import datetime
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
16
|
from enum import Enum
|
|
17
17
|
from typing import Any, Deque, Dict, List, Optional, Set
|
|
18
18
|
|
|
@@ -225,7 +225,7 @@ class EventBus(IEventBus):
|
|
|
225
225
|
|
|
226
226
|
# Add metadata
|
|
227
227
|
if event.metadata:
|
|
228
|
-
event.metadata.published_at = datetime.now()
|
|
228
|
+
event.metadata.published_at = datetime.now(timezone.utc)
|
|
229
229
|
|
|
230
230
|
# Queue event
|
|
231
231
|
self._event_queues[event.priority].append(event)
|
|
@@ -353,7 +353,7 @@ class EventBus(IEventBus):
|
|
|
353
353
|
# Update metrics
|
|
354
354
|
if events_processed > 0:
|
|
355
355
|
self._metrics["events_processed"] += events_processed
|
|
356
|
-
self._metrics["last_event_time"] = datetime.now()
|
|
356
|
+
self._metrics["last_event_time"] = datetime.now(timezone.utc)
|
|
357
357
|
self._metrics["queue_size"] = sum(
|
|
358
358
|
len(q) for q in self._event_queues.values()
|
|
359
359
|
)
|
|
@@ -7,7 +7,7 @@ This replaces direct Socket.IO emission in the hook handler.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import uuid
|
|
10
|
-
from datetime import datetime
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
11
|
from typing import Any, Dict, List, Optional
|
|
12
12
|
|
|
13
13
|
from claude_mpm.core.logging_config import get_logger
|
|
@@ -96,7 +96,7 @@ class HookEventProducer(IEventProducer):
|
|
|
96
96
|
id=str(uuid.uuid4()),
|
|
97
97
|
topic="hook.response",
|
|
98
98
|
type="AssistantResponse",
|
|
99
|
-
timestamp=datetime.now(),
|
|
99
|
+
timestamp=datetime.now(timezone.utc),
|
|
100
100
|
source=self.source_name,
|
|
101
101
|
data=response_data,
|
|
102
102
|
correlation_id=correlation_id,
|
|
@@ -128,7 +128,7 @@ class HookEventProducer(IEventProducer):
|
|
|
128
128
|
id=str(uuid.uuid4()),
|
|
129
129
|
topic="hook.tool",
|
|
130
130
|
type="ToolUse",
|
|
131
|
-
timestamp=datetime.now(),
|
|
131
|
+
timestamp=datetime.now(timezone.utc),
|
|
132
132
|
source=self.source_name,
|
|
133
133
|
data={
|
|
134
134
|
"tool": tool_name,
|
|
@@ -164,7 +164,7 @@ class HookEventProducer(IEventProducer):
|
|
|
164
164
|
id=str(uuid.uuid4()),
|
|
165
165
|
topic="hook.error",
|
|
166
166
|
type="Error",
|
|
167
|
-
timestamp=datetime.now(),
|
|
167
|
+
timestamp=datetime.now(timezone.utc),
|
|
168
168
|
source=self.source_name,
|
|
169
169
|
data={
|
|
170
170
|
"error_type": error_type,
|
|
@@ -200,7 +200,7 @@ class HookEventProducer(IEventProducer):
|
|
|
200
200
|
id=str(uuid.uuid4()),
|
|
201
201
|
topic=f"hook.subagent.{event_type.lower()}",
|
|
202
202
|
type=f"Subagent{event_type}",
|
|
203
|
-
timestamp=datetime.now(),
|
|
203
|
+
timestamp=datetime.now(timezone.utc),
|
|
204
204
|
source=self.source_name,
|
|
205
205
|
data={
|
|
206
206
|
"subagent": subagent_name,
|
|
@@ -255,7 +255,7 @@ class HookEventProducer(IEventProducer):
|
|
|
255
255
|
id=str(uuid.uuid4()),
|
|
256
256
|
topic=topic,
|
|
257
257
|
type=hook_type,
|
|
258
|
-
timestamp=datetime.now(),
|
|
258
|
+
timestamp=datetime.now(timezone.utc),
|
|
259
259
|
source=self.source_name,
|
|
260
260
|
data=hook_data,
|
|
261
261
|
correlation_id=correlation_id,
|
|
@@ -6,7 +6,7 @@ Publishes system-level events to the event bus.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import uuid
|
|
9
|
-
from datetime import datetime
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
10
|
from typing import Any, Dict, List, Optional
|
|
11
11
|
|
|
12
12
|
from claude_mpm.core.logging_config import get_logger
|
|
@@ -100,7 +100,7 @@ class SystemEventProducer(IEventProducer):
|
|
|
100
100
|
id=str(uuid.uuid4()),
|
|
101
101
|
topic="system.lifecycle.startup",
|
|
102
102
|
type="ServiceStartup",
|
|
103
|
-
timestamp=datetime.now(),
|
|
103
|
+
timestamp=datetime.now(timezone.utc),
|
|
104
104
|
source=self.source_name,
|
|
105
105
|
data={
|
|
106
106
|
"service": service_name,
|
|
@@ -133,7 +133,7 @@ class SystemEventProducer(IEventProducer):
|
|
|
133
133
|
id=str(uuid.uuid4()),
|
|
134
134
|
topic="system.lifecycle.shutdown",
|
|
135
135
|
type="ServiceShutdown",
|
|
136
|
-
timestamp=datetime.now(),
|
|
136
|
+
timestamp=datetime.now(timezone.utc),
|
|
137
137
|
source=self.source_name,
|
|
138
138
|
data={
|
|
139
139
|
"service": service_name,
|
|
@@ -168,7 +168,7 @@ class SystemEventProducer(IEventProducer):
|
|
|
168
168
|
id=str(uuid.uuid4()),
|
|
169
169
|
topic="system.health",
|
|
170
170
|
type="HealthStatus",
|
|
171
|
-
timestamp=datetime.now(),
|
|
171
|
+
timestamp=datetime.now(timezone.utc),
|
|
172
172
|
source=self.source_name,
|
|
173
173
|
data={
|
|
174
174
|
"service": service_name,
|
|
@@ -206,7 +206,7 @@ class SystemEventProducer(IEventProducer):
|
|
|
206
206
|
id=str(uuid.uuid4()),
|
|
207
207
|
topic="system.config",
|
|
208
208
|
type="ConfigChange",
|
|
209
|
-
timestamp=datetime.now(),
|
|
209
|
+
timestamp=datetime.now(timezone.utc),
|
|
210
210
|
source=self.source_name,
|
|
211
211
|
data={
|
|
212
212
|
"service": service_name,
|
|
@@ -238,7 +238,7 @@ class SystemEventProducer(IEventProducer):
|
|
|
238
238
|
id=str(uuid.uuid4()),
|
|
239
239
|
topic="system.performance",
|
|
240
240
|
type="PerformanceMetrics",
|
|
241
|
-
timestamp=datetime.now(),
|
|
241
|
+
timestamp=datetime.now(timezone.utc),
|
|
242
242
|
source=self.source_name,
|
|
243
243
|
data={
|
|
244
244
|
"service": service_name,
|
|
@@ -274,7 +274,7 @@ class SystemEventProducer(IEventProducer):
|
|
|
274
274
|
id=str(uuid.uuid4()),
|
|
275
275
|
topic="system.error",
|
|
276
276
|
type="SystemError",
|
|
277
|
-
timestamp=datetime.now(),
|
|
277
|
+
timestamp=datetime.now(timezone.utc),
|
|
278
278
|
source=self.source_name,
|
|
279
279
|
data={
|
|
280
280
|
"service": service_name,
|
|
@@ -311,7 +311,7 @@ class SystemEventProducer(IEventProducer):
|
|
|
311
311
|
id=str(uuid.uuid4()),
|
|
312
312
|
topic="system.warning",
|
|
313
313
|
type="SystemWarning",
|
|
314
|
-
timestamp=datetime.now(),
|
|
314
|
+
timestamp=datetime.now(timezone.utc),
|
|
315
315
|
source=self.source_name,
|
|
316
316
|
data={
|
|
317
317
|
"service": service_name,
|
|
@@ -15,7 +15,7 @@ Design Principles:
|
|
|
15
15
|
|
|
16
16
|
import platform
|
|
17
17
|
import time
|
|
18
|
-
from datetime import datetime
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
19
|
from typing import Any, Dict, List, Optional
|
|
20
20
|
|
|
21
21
|
|
|
@@ -43,7 +43,7 @@ class SocketIOServerError(Exception):
|
|
|
43
43
|
self.message = message
|
|
44
44
|
self.error_code = error_code or self.__class__.__name__.lower()
|
|
45
45
|
self.context = context or {}
|
|
46
|
-
self.timestamp = datetime.
|
|
46
|
+
self.timestamp = datetime.now(timezone.utc).isoformat() + "Z"
|
|
47
47
|
|
|
48
48
|
def to_dict(self) -> Dict[str, Any]:
|
|
49
49
|
"""Convert error to dictionary format for structured logging/handling."""
|
|
@@ -125,9 +125,9 @@ class DaemonConflictError(SocketIOServerError):
|
|
|
125
125
|
)
|
|
126
126
|
|
|
127
127
|
if create_time:
|
|
128
|
-
start_time = datetime.fromtimestamp(
|
|
129
|
-
|
|
130
|
-
)
|
|
128
|
+
start_time = datetime.fromtimestamp(
|
|
129
|
+
create_time, tz=timezone.utc
|
|
130
|
+
).strftime("%Y-%m-%d %H:%M:%S")
|
|
131
131
|
uptime = time.time() - create_time
|
|
132
132
|
lines.append(f" • Started: {start_time} (uptime: {uptime:.0f}s)")
|
|
133
133
|
|
|
@@ -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"
|