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.
Files changed (199) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/PM_INSTRUCTIONS.md +390 -28
  3. claude_mpm/agents/templates/data_engineer.json +39 -14
  4. claude_mpm/cli/commands/agent_manager.py +3 -3
  5. claude_mpm/cli/commands/agents.py +2 -2
  6. claude_mpm/cli/commands/aggregate.py +1 -1
  7. claude_mpm/cli/commands/config.py +2 -2
  8. claude_mpm/cli/commands/configure.py +5 -5
  9. claude_mpm/cli/commands/configure_tui.py +7 -7
  10. claude_mpm/cli/commands/dashboard.py +1 -1
  11. claude_mpm/cli/commands/debug.py +5 -5
  12. claude_mpm/cli/commands/mcp.py +1 -1
  13. claude_mpm/cli/commands/mcp_command_router.py +1 -1
  14. claude_mpm/cli/commands/mcp_config.py +7 -10
  15. claude_mpm/cli/commands/mcp_external_commands.py +40 -32
  16. claude_mpm/cli/commands/mcp_install_commands.py +38 -10
  17. claude_mpm/cli/commands/mcp_setup_external.py +143 -102
  18. claude_mpm/cli/commands/monitor.py +2 -2
  19. claude_mpm/cli/commands/mpm_init_handler.py +1 -1
  20. claude_mpm/cli/commands/run.py +46 -2
  21. claude_mpm/cli/commands/search.py +41 -34
  22. claude_mpm/cli/interactive/agent_wizard.py +2 -2
  23. claude_mpm/cli/parsers/mcp_parser.py +1 -3
  24. claude_mpm/cli/parsers/search_parser.py +10 -4
  25. claude_mpm/cli/startup_logging.py +3 -5
  26. claude_mpm/cli/utils.py +1 -1
  27. claude_mpm/core/agent_registry.py +12 -8
  28. claude_mpm/core/agent_session_manager.py +8 -8
  29. claude_mpm/core/api_validator.py +4 -4
  30. claude_mpm/core/base_service.py +10 -10
  31. claude_mpm/core/cache.py +5 -5
  32. claude_mpm/core/config_constants.py +1 -1
  33. claude_mpm/core/container.py +1 -1
  34. claude_mpm/core/error_handler.py +2 -2
  35. claude_mpm/core/file_utils.py +1 -1
  36. claude_mpm/core/framework_loader.py +3 -3
  37. claude_mpm/core/hook_manager.py +8 -6
  38. claude_mpm/core/instruction_reinforcement_hook.py +2 -2
  39. claude_mpm/core/interactive_session.py +1 -1
  40. claude_mpm/core/lazy.py +3 -3
  41. claude_mpm/core/log_manager.py +16 -12
  42. claude_mpm/core/logger.py +16 -11
  43. claude_mpm/core/logging_config.py +4 -2
  44. claude_mpm/core/oneshot_session.py +1 -1
  45. claude_mpm/core/optimized_agent_loader.py +6 -6
  46. claude_mpm/core/output_style_manager.py +1 -1
  47. claude_mpm/core/pm_hook_interceptor.py +3 -3
  48. claude_mpm/core/service_registry.py +1 -1
  49. claude_mpm/core/session_manager.py +11 -9
  50. claude_mpm/core/socketio_pool.py +13 -13
  51. claude_mpm/core/types.py +2 -2
  52. claude_mpm/core/unified_agent_registry.py +2 -2
  53. claude_mpm/core/unified_paths.py +1 -1
  54. claude_mpm/dashboard/analysis_runner.py +4 -4
  55. claude_mpm/dashboard/api/simple_directory.py +1 -1
  56. claude_mpm/generators/agent_profile_generator.py +4 -2
  57. claude_mpm/hooks/base_hook.py +2 -2
  58. claude_mpm/hooks/claude_hooks/connection_pool.py +4 -4
  59. claude_mpm/hooks/claude_hooks/event_handlers.py +12 -12
  60. claude_mpm/hooks/claude_hooks/hook_handler.py +4 -4
  61. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +3 -3
  62. claude_mpm/hooks/claude_hooks/hook_handler_original.py +15 -14
  63. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +4 -4
  64. claude_mpm/hooks/claude_hooks/installer.py +3 -3
  65. claude_mpm/hooks/claude_hooks/memory_integration.py +3 -3
  66. claude_mpm/hooks/claude_hooks/response_tracking.py +3 -3
  67. claude_mpm/hooks/claude_hooks/services/connection_manager.py +5 -5
  68. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +3 -3
  69. claude_mpm/hooks/claude_hooks/services/state_manager.py +8 -7
  70. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +3 -3
  71. claude_mpm/hooks/claude_hooks/tool_analysis.py +2 -2
  72. claude_mpm/hooks/memory_integration_hook.py +1 -1
  73. claude_mpm/hooks/tool_call_interceptor.py +2 -2
  74. claude_mpm/models/agent_session.py +5 -5
  75. claude_mpm/services/__init__.py +1 -1
  76. claude_mpm/services/agent_capabilities_service.py +1 -1
  77. claude_mpm/services/agents/agent_builder.py +3 -3
  78. claude_mpm/services/agents/deployment/agent_deployment.py +2 -1
  79. claude_mpm/services/agents/deployment/agent_discovery_service.py +9 -3
  80. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +7 -5
  81. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +3 -1
  82. claude_mpm/services/agents/deployment/agent_metrics_collector.py +1 -1
  83. claude_mpm/services/agents/deployment/agent_operation_service.py +2 -2
  84. claude_mpm/services/agents/deployment/agent_state_service.py +2 -2
  85. claude_mpm/services/agents/deployment/agent_template_builder.py +1 -1
  86. claude_mpm/services/agents/deployment/agent_versioning.py +1 -1
  87. claude_mpm/services/agents/deployment/deployment_wrapper.py +2 -3
  88. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +1 -1
  89. claude_mpm/services/agents/loading/agent_profile_loader.py +5 -3
  90. claude_mpm/services/agents/loading/base_agent_manager.py +2 -2
  91. claude_mpm/services/agents/local_template_manager.py +6 -6
  92. claude_mpm/services/agents/management/agent_management_service.py +3 -3
  93. claude_mpm/services/agents/memory/content_manager.py +3 -3
  94. claude_mpm/services/agents/memory/memory_format_service.py +2 -2
  95. claude_mpm/services/agents/memory/template_generator.py +3 -3
  96. claude_mpm/services/agents/registry/__init__.py +1 -1
  97. claude_mpm/services/agents/registry/modification_tracker.py +2 -2
  98. claude_mpm/services/async_session_logger.py +3 -3
  99. claude_mpm/services/claude_session_logger.py +4 -4
  100. claude_mpm/services/cli/agent_listing_service.py +1 -1
  101. claude_mpm/services/cli/agent_validation_service.py +1 -0
  102. claude_mpm/services/cli/memory_crud_service.py +11 -6
  103. claude_mpm/services/cli/memory_output_formatter.py +1 -1
  104. claude_mpm/services/cli/session_manager.py +15 -11
  105. claude_mpm/services/cli/unified_dashboard_manager.py +1 -1
  106. claude_mpm/services/core/memory_manager.py +81 -23
  107. claude_mpm/services/core/path_resolver.py +2 -2
  108. claude_mpm/services/diagnostics/checks/installation_check.py +1 -1
  109. claude_mpm/services/event_aggregator.py +4 -2
  110. claude_mpm/services/event_bus/direct_relay.py +5 -3
  111. claude_mpm/services/event_bus/event_bus.py +3 -3
  112. claude_mpm/services/event_bus/relay.py +6 -4
  113. claude_mpm/services/events/consumers/dead_letter.py +5 -3
  114. claude_mpm/services/events/core.py +3 -3
  115. claude_mpm/services/events/producers/hook.py +6 -6
  116. claude_mpm/services/events/producers/system.py +8 -8
  117. claude_mpm/services/exceptions.py +5 -5
  118. claude_mpm/services/framework_claude_md_generator/content_assembler.py +3 -3
  119. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +2 -2
  120. claude_mpm/services/hook_installer_service.py +1 -1
  121. claude_mpm/services/infrastructure/context_preservation.py +6 -4
  122. claude_mpm/services/infrastructure/daemon_manager.py +2 -2
  123. claude_mpm/services/infrastructure/logging.py +2 -2
  124. claude_mpm/services/mcp_config_manager.py +175 -30
  125. claude_mpm/services/mcp_gateway/__init__.py +1 -1
  126. claude_mpm/services/mcp_gateway/auto_configure.py +3 -3
  127. claude_mpm/services/mcp_gateway/config/config_loader.py +1 -1
  128. claude_mpm/services/mcp_gateway/config/configuration.py +1 -1
  129. claude_mpm/services/mcp_gateway/core/base.py +2 -2
  130. claude_mpm/services/mcp_gateway/main.py +21 -7
  131. claude_mpm/services/mcp_gateway/registry/tool_registry.py +10 -8
  132. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +4 -4
  133. claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
  134. claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -3
  135. claude_mpm/services/mcp_gateway/tools/base_adapter.py +15 -15
  136. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +7 -5
  137. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +190 -137
  138. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +5 -5
  139. claude_mpm/services/mcp_gateway/tools/hello_world.py +9 -9
  140. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +16 -16
  141. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +17 -17
  142. claude_mpm/services/memory/builder.py +7 -5
  143. claude_mpm/services/memory/indexed_memory.py +4 -4
  144. claude_mpm/services/memory/optimizer.py +6 -6
  145. claude_mpm/services/memory/router.py +3 -3
  146. claude_mpm/services/monitor/daemon.py +1 -1
  147. claude_mpm/services/monitor/daemon_manager.py +6 -6
  148. claude_mpm/services/monitor/event_emitter.py +2 -2
  149. claude_mpm/services/monitor/handlers/file.py +1 -1
  150. claude_mpm/services/monitor/management/lifecycle.py +1 -1
  151. claude_mpm/services/monitor/server.py +4 -4
  152. claude_mpm/services/monitor_build_service.py +2 -2
  153. claude_mpm/services/port_manager.py +2 -2
  154. claude_mpm/services/response_tracker.py +2 -2
  155. claude_mpm/services/session_management_service.py +3 -2
  156. claude_mpm/services/socketio/client_proxy.py +2 -2
  157. claude_mpm/services/socketio/dashboard_server.py +4 -3
  158. claude_mpm/services/socketio/event_normalizer.py +12 -8
  159. claude_mpm/services/socketio/handlers/base.py +2 -2
  160. claude_mpm/services/socketio/handlers/connection.py +10 -10
  161. claude_mpm/services/socketio/handlers/connection_handler.py +13 -10
  162. claude_mpm/services/socketio/handlers/file.py +1 -1
  163. claude_mpm/services/socketio/handlers/git.py +1 -1
  164. claude_mpm/services/socketio/handlers/hook.py +16 -15
  165. claude_mpm/services/socketio/migration_utils.py +1 -1
  166. claude_mpm/services/socketio/monitor_client.py +5 -5
  167. claude_mpm/services/socketio/server/broadcaster.py +9 -7
  168. claude_mpm/services/socketio/server/connection_manager.py +2 -2
  169. claude_mpm/services/socketio/server/core.py +7 -5
  170. claude_mpm/services/socketio/server/eventbus_integration.py +18 -11
  171. claude_mpm/services/socketio/server/main.py +13 -13
  172. claude_mpm/services/socketio_client_manager.py +4 -4
  173. claude_mpm/services/system_instructions_service.py +2 -2
  174. claude_mpm/services/ticket_services/validation_service.py +1 -1
  175. claude_mpm/services/utility_service.py +5 -2
  176. claude_mpm/services/version_control/branch_strategy.py +2 -2
  177. claude_mpm/services/version_control/git_operations.py +22 -20
  178. claude_mpm/services/version_control/semantic_versioning.py +3 -3
  179. claude_mpm/services/version_control/version_parser.py +7 -5
  180. claude_mpm/services/visualization/mermaid_generator.py +1 -1
  181. claude_mpm/storage/state_storage.py +1 -1
  182. claude_mpm/tools/code_tree_analyzer.py +19 -18
  183. claude_mpm/tools/code_tree_builder.py +2 -2
  184. claude_mpm/tools/code_tree_events.py +10 -8
  185. claude_mpm/tools/socketio_debug.py +3 -3
  186. claude_mpm/utils/agent_dependency_loader.py +2 -2
  187. claude_mpm/utils/dependency_strategies.py +8 -3
  188. claude_mpm/utils/environment_context.py +2 -2
  189. claude_mpm/utils/error_handler.py +2 -2
  190. claude_mpm/utils/file_utils.py +1 -1
  191. claude_mpm/utils/imports.py +1 -1
  192. claude_mpm/utils/log_cleanup.py +21 -7
  193. claude_mpm/validation/agent_validator.py +2 -2
  194. {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/METADATA +1 -1
  195. {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/RECORD +199 -199
  196. {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/WHEEL +0 -0
  197. {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/entry_points.txt +0 -0
  198. {claude_mpm-4.3.12.dist-info → claude_mpm-4.3.13.dist-info}/licenses/LICENSE +0 -0
  199. {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.utcnow().isoformat()
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.utcnow().isoformat()
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.utcnow().isoformat()
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() - (keep_recent_days * 86400)
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.utcnow().isoformat(),
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 .mcp.json configuration file.
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
- mcp_config_path = self.project_root / ".mcp.json"
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, "r") as f:
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
- else:
207
- # Keep existing config if not forcing pipx
208
- if service_name in existing_config.get("mcpServers", {}):
209
- new_config["mcpServers"][service_name] = existing_config[
210
- "mcpServers"
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
- else:
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
- mcp_config_path = self.project_root / ".mcp.json"
239
- if not mcp_config_path.exists():
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(mcp_config_path, "r") as f:
244
- config = json.load(f)
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
- results = {}
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
- result = subprocess.run(
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
- elif installed:
437
+ if installed:
292
438
  return True, f"Successfully installed: {', '.join(installed)}"
293
- else:
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
  )
@@ -143,7 +143,7 @@ class MCPConfigLoader:
143
143
  import json
144
144
 
145
145
  current[key] = json.loads(env_value)
146
- except:
146
+ except Exception:
147
147
  # Fall back to string value
148
148
  current[key] = env_value
149
149
 
@@ -225,7 +225,7 @@ class MCPConfiguration(BaseMCPService, IMCPConfiguration):
225
225
  import json
226
226
 
227
227
  current[key] = json.loads(env_value)
228
- except:
228
+ except Exception:
229
229
  # Fall back to string value
230
230
  current[key] = env_value
231
231
 
@@ -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 = await self.external_services.initialize_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(service, category="external"):
222
- self.logger.info(f"Registered external service: {service.service_name}")
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(f"Failed to register external service: {service.service_name}")
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(f"Error registering {service.service_name}: {e}")
234
+ self.logger.warning(
235
+ f"Error registering {service.service_name}: {e}"
236
+ )
227
237
 
228
- self.logger.info(f"Initialized {len(external_services)} external MCP services")
238
+ self.logger.info(
239
+ f"Initialized {len(external_services)} external MCP services"
240
+ )
229
241
  except Exception as e:
230
- self.logger.warning(f"Failed to initialize external MCP services: {e}")
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
- tool_name
164
- ] = datetime.now().isoformat()
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().isoformat()
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