claude-mpm 4.5.8__py3-none-any.whl → 4.5.12__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 (226) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +20 -5
  3. claude_mpm/agents/agent_loader.py +19 -2
  4. claude_mpm/agents/base_agent_loader.py +5 -5
  5. claude_mpm/agents/frontmatter_validator.py +4 -4
  6. claude_mpm/agents/templates/agent-manager.json +3 -3
  7. claude_mpm/agents/templates/agentic-coder-optimizer.json +3 -3
  8. claude_mpm/agents/templates/api_qa.json +1 -1
  9. claude_mpm/agents/templates/clerk-ops.json +3 -3
  10. claude_mpm/agents/templates/code_analyzer.json +3 -3
  11. claude_mpm/agents/templates/dart_engineer.json +294 -0
  12. claude_mpm/agents/templates/data_engineer.json +3 -3
  13. claude_mpm/agents/templates/documentation.json +2 -2
  14. claude_mpm/agents/templates/engineer.json +2 -2
  15. claude_mpm/agents/templates/gcp_ops_agent.json +2 -2
  16. claude_mpm/agents/templates/imagemagick.json +1 -1
  17. claude_mpm/agents/templates/local_ops_agent.json +319 -41
  18. claude_mpm/agents/templates/memory_manager.json +2 -2
  19. claude_mpm/agents/templates/nextjs_engineer.json +2 -2
  20. claude_mpm/agents/templates/ops.json +2 -2
  21. claude_mpm/agents/templates/php-engineer.json +1 -1
  22. claude_mpm/agents/templates/project_organizer.json +1 -1
  23. claude_mpm/agents/templates/prompt-engineer.json +6 -4
  24. claude_mpm/agents/templates/python_engineer.json +2 -2
  25. claude_mpm/agents/templates/qa.json +1 -1
  26. claude_mpm/agents/templates/react_engineer.json +3 -3
  27. claude_mpm/agents/templates/refactoring_engineer.json +3 -3
  28. claude_mpm/agents/templates/research.json +2 -2
  29. claude_mpm/agents/templates/security.json +2 -2
  30. claude_mpm/agents/templates/ticketing.json +2 -2
  31. claude_mpm/agents/templates/typescript_engineer.json +2 -2
  32. claude_mpm/agents/templates/vercel_ops_agent.json +2 -2
  33. claude_mpm/agents/templates/version_control.json +2 -2
  34. claude_mpm/agents/templates/web_qa.json +6 -6
  35. claude_mpm/agents/templates/web_ui.json +3 -3
  36. claude_mpm/cli/__init__.py +49 -19
  37. claude_mpm/cli/commands/agent_manager.py +3 -3
  38. claude_mpm/cli/commands/agents.py +6 -6
  39. claude_mpm/cli/commands/aggregate.py +4 -4
  40. claude_mpm/cli/commands/analyze.py +2 -2
  41. claude_mpm/cli/commands/analyze_code.py +1 -1
  42. claude_mpm/cli/commands/cleanup.py +3 -3
  43. claude_mpm/cli/commands/config.py +2 -2
  44. claude_mpm/cli/commands/configure.py +605 -21
  45. claude_mpm/cli/commands/dashboard.py +1 -1
  46. claude_mpm/cli/commands/debug.py +3 -3
  47. claude_mpm/cli/commands/doctor.py +1 -1
  48. claude_mpm/cli/commands/mcp.py +7 -7
  49. claude_mpm/cli/commands/mcp_command_router.py +1 -1
  50. claude_mpm/cli/commands/mcp_config.py +2 -2
  51. claude_mpm/cli/commands/mcp_external_commands.py +2 -2
  52. claude_mpm/cli/commands/mcp_install_commands.py +3 -3
  53. claude_mpm/cli/commands/mcp_pipx_config.py +2 -2
  54. claude_mpm/cli/commands/mcp_setup_external.py +3 -3
  55. claude_mpm/cli/commands/monitor.py +1 -1
  56. claude_mpm/cli/commands/mpm_init_handler.py +1 -1
  57. claude_mpm/cli/interactive/agent_wizard.py +1 -1
  58. claude_mpm/cli/parsers/configure_parser.py +5 -0
  59. claude_mpm/cli/parsers/search_parser.py +1 -1
  60. claude_mpm/cli/shared/argument_patterns.py +2 -2
  61. claude_mpm/cli/shared/base_command.py +1 -1
  62. claude_mpm/cli/startup_logging.py +4 -4
  63. claude_mpm/config/experimental_features.py +4 -4
  64. claude_mpm/config/socketio_config.py +2 -2
  65. claude_mpm/core/__init__.py +53 -17
  66. claude_mpm/core/agent_session_manager.py +2 -2
  67. claude_mpm/core/api_validator.py +3 -3
  68. claude_mpm/core/base_service.py +10 -1
  69. claude_mpm/core/cache.py +2 -2
  70. claude_mpm/core/config.py +5 -5
  71. claude_mpm/core/config_aliases.py +4 -4
  72. claude_mpm/core/config_constants.py +1 -1
  73. claude_mpm/core/error_handler.py +1 -1
  74. claude_mpm/core/file_utils.py +5 -5
  75. claude_mpm/core/framework/formatters/capability_generator.py +5 -5
  76. claude_mpm/core/framework/loaders/agent_loader.py +1 -1
  77. claude_mpm/core/framework/processors/metadata_processor.py +1 -1
  78. claude_mpm/core/framework/processors/template_processor.py +3 -3
  79. claude_mpm/core/framework_loader.py +2 -2
  80. claude_mpm/core/log_manager.py +11 -4
  81. claude_mpm/core/logger.py +2 -2
  82. claude_mpm/core/optimized_startup.py +1 -1
  83. claude_mpm/core/output_style_manager.py +1 -1
  84. claude_mpm/core/service_registry.py +2 -2
  85. claude_mpm/core/session_manager.py +3 -3
  86. claude_mpm/core/shared/config_loader.py +1 -1
  87. claude_mpm/core/socketio_pool.py +2 -2
  88. claude_mpm/core/unified_agent_registry.py +2 -2
  89. claude_mpm/core/unified_config.py +6 -6
  90. claude_mpm/core/unified_paths.py +2 -2
  91. claude_mpm/dashboard/api/simple_directory.py +1 -1
  92. claude_mpm/generators/agent_profile_generator.py +1 -1
  93. claude_mpm/hooks/claude_hooks/event_handlers.py +2 -2
  94. claude_mpm/hooks/claude_hooks/installer.py +9 -9
  95. claude_mpm/hooks/claude_hooks/response_tracking.py +16 -11
  96. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +16 -13
  97. claude_mpm/hooks/claude_hooks/tool_analysis.py +2 -2
  98. claude_mpm/hooks/memory_integration_hook.py +1 -1
  99. claude_mpm/hooks/validation_hooks.py +1 -1
  100. claude_mpm/init.py +4 -4
  101. claude_mpm/models/agent_session.py +1 -1
  102. claude_mpm/scripts/socketio_daemon.py +5 -5
  103. claude_mpm/services/__init__.py +145 -161
  104. claude_mpm/services/agent_capabilities_service.py +1 -1
  105. claude_mpm/services/agents/agent_builder.py +4 -4
  106. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +1 -1
  107. claude_mpm/services/agents/deployment/agent_metrics_collector.py +1 -1
  108. claude_mpm/services/agents/deployment/agent_record_service.py +3 -3
  109. claude_mpm/services/agents/deployment/deployment_config_loader.py +21 -0
  110. claude_mpm/services/agents/deployment/deployment_wrapper.py +1 -1
  111. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +2 -2
  112. claude_mpm/services/agents/loading/agent_profile_loader.py +2 -2
  113. claude_mpm/services/agents/loading/base_agent_manager.py +12 -2
  114. claude_mpm/services/agents/local_template_manager.py +5 -5
  115. claude_mpm/services/agents/registry/deployed_agent_discovery.py +1 -1
  116. claude_mpm/services/agents/registry/modification_tracker.py +19 -11
  117. claude_mpm/services/async_session_logger.py +3 -3
  118. claude_mpm/services/claude_session_logger.py +4 -4
  119. claude_mpm/services/cli/agent_listing_service.py +3 -3
  120. claude_mpm/services/cli/agent_validation_service.py +1 -1
  121. claude_mpm/services/cli/session_manager.py +2 -2
  122. claude_mpm/services/core/path_resolver.py +1 -1
  123. claude_mpm/services/diagnostics/checks/agent_check.py +1 -1
  124. claude_mpm/services/diagnostics/checks/claude_code_check.py +2 -2
  125. claude_mpm/services/diagnostics/checks/common_issues_check.py +3 -3
  126. claude_mpm/services/diagnostics/checks/configuration_check.py +2 -2
  127. claude_mpm/services/diagnostics/checks/installation_check.py +1 -1
  128. claude_mpm/services/diagnostics/checks/mcp_check.py +1 -1
  129. claude_mpm/services/diagnostics/checks/mcp_services_check.py +9 -9
  130. claude_mpm/services/diagnostics/checks/monitor_check.py +1 -1
  131. claude_mpm/services/diagnostics/doctor_reporter.py +1 -1
  132. claude_mpm/services/event_aggregator.py +1 -1
  133. claude_mpm/services/event_bus/event_bus.py +7 -2
  134. claude_mpm/services/events/consumers/dead_letter.py +2 -2
  135. claude_mpm/services/framework_claude_md_generator/__init__.py +1 -1
  136. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +3 -3
  137. claude_mpm/services/framework_claude_md_generator/version_manager.py +1 -1
  138. claude_mpm/services/hook_installer_service.py +7 -7
  139. claude_mpm/services/infrastructure/context_preservation.py +7 -7
  140. claude_mpm/services/infrastructure/daemon_manager.py +5 -5
  141. claude_mpm/services/mcp_config_manager.py +169 -48
  142. claude_mpm/services/mcp_gateway/__init__.py +98 -94
  143. claude_mpm/services/mcp_gateway/auto_configure.py +5 -5
  144. claude_mpm/services/mcp_gateway/config/config_loader.py +2 -2
  145. claude_mpm/services/mcp_gateway/config/configuration.py +3 -3
  146. claude_mpm/services/mcp_gateway/core/process_pool.py +3 -3
  147. claude_mpm/services/mcp_gateway/core/singleton_manager.py +2 -2
  148. claude_mpm/services/mcp_gateway/core/startup_verification.py +1 -1
  149. claude_mpm/services/mcp_gateway/main.py +1 -1
  150. claude_mpm/services/mcp_gateway/registry/service_registry.py +4 -2
  151. claude_mpm/services/mcp_gateway/registry/tool_registry.py +2 -1
  152. claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
  153. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +1 -1
  154. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +1 -1
  155. claude_mpm/services/mcp_gateway/tools/hello_world.py +1 -1
  156. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +5 -5
  157. claude_mpm/services/mcp_gateway/utils/update_preferences.py +2 -2
  158. claude_mpm/services/mcp_service_verifier.py +1 -1
  159. claude_mpm/services/memory/builder.py +1 -1
  160. claude_mpm/services/memory/cache/shared_prompt_cache.py +2 -1
  161. claude_mpm/services/memory/indexed_memory.py +3 -3
  162. claude_mpm/services/monitor/daemon.py +1 -1
  163. claude_mpm/services/monitor/daemon_manager.py +9 -9
  164. claude_mpm/services/monitor/event_emitter.py +1 -1
  165. claude_mpm/services/monitor/handlers/file.py +1 -1
  166. claude_mpm/services/monitor/handlers/hooks.py +3 -3
  167. claude_mpm/services/monitor/management/lifecycle.py +7 -7
  168. claude_mpm/services/monitor/server.py +2 -2
  169. claude_mpm/services/orphan_detection.py +788 -0
  170. claude_mpm/services/port_manager.py +2 -2
  171. claude_mpm/services/project/analyzer.py +3 -3
  172. claude_mpm/services/project/archive_manager.py +13 -13
  173. claude_mpm/services/project/dependency_analyzer.py +4 -4
  174. claude_mpm/services/project/documentation_manager.py +4 -4
  175. claude_mpm/services/project/enhanced_analyzer.py +8 -8
  176. claude_mpm/services/project/registry.py +4 -4
  177. claude_mpm/services/project_port_allocator.py +597 -0
  178. claude_mpm/services/response_tracker.py +1 -1
  179. claude_mpm/services/session_management_service.py +1 -1
  180. claude_mpm/services/session_manager.py +6 -4
  181. claude_mpm/services/socketio/event_normalizer.py +1 -1
  182. claude_mpm/services/socketio/handlers/code_analysis.py +14 -12
  183. claude_mpm/services/socketio/handlers/file.py +1 -1
  184. claude_mpm/services/socketio/migration_utils.py +1 -1
  185. claude_mpm/services/socketio/server/core.py +1 -1
  186. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +1 -1
  187. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +4 -4
  188. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +1 -1
  189. claude_mpm/services/unified/config_strategies/config_schema.py +4 -4
  190. claude_mpm/services/unified/config_strategies/context_strategy.py +6 -6
  191. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +10 -10
  192. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +5 -5
  193. claude_mpm/services/unified/config_strategies/unified_config_service.py +8 -8
  194. claude_mpm/services/unified/config_strategies/validation_strategy.py +15 -15
  195. claude_mpm/services/unified/deployment_strategies/base.py +4 -4
  196. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +15 -15
  197. claude_mpm/services/unified/deployment_strategies/local.py +9 -9
  198. claude_mpm/services/unified/deployment_strategies/utils.py +9 -9
  199. claude_mpm/services/unified/deployment_strategies/vercel.py +7 -7
  200. claude_mpm/services/unified/unified_config.py +5 -5
  201. claude_mpm/services/unified/unified_deployment.py +2 -2
  202. claude_mpm/services/utility_service.py +1 -1
  203. claude_mpm/services/version_control/conflict_resolution.py +2 -2
  204. claude_mpm/services/version_control/git_operations.py +3 -3
  205. claude_mpm/services/version_control/semantic_versioning.py +13 -13
  206. claude_mpm/services/version_control/version_parser.py +1 -1
  207. claude_mpm/storage/state_storage.py +12 -13
  208. claude_mpm/tools/code_tree_analyzer.py +5 -5
  209. claude_mpm/tools/code_tree_builder.py +4 -4
  210. claude_mpm/tools/socketio_debug.py +1 -1
  211. claude_mpm/utils/agent_dependency_loader.py +4 -4
  212. claude_mpm/utils/common.py +2 -2
  213. claude_mpm/utils/config_manager.py +3 -3
  214. claude_mpm/utils/dependency_cache.py +2 -2
  215. claude_mpm/utils/dependency_strategies.py +6 -6
  216. claude_mpm/utils/file_utils.py +11 -11
  217. claude_mpm/utils/log_cleanup.py +1 -1
  218. claude_mpm/utils/path_operations.py +1 -1
  219. claude_mpm/validation/agent_validator.py +2 -2
  220. claude_mpm/validation/frontmatter_validator.py +1 -1
  221. {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/METADATA +1 -1
  222. {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/RECORD +226 -223
  223. {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/WHEEL +0 -0
  224. {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/entry_points.txt +0 -0
  225. {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/licenses/LICENSE +0 -0
  226. {claude_mpm-4.5.8.dist-info → claude_mpm-4.5.12.dist-info}/top_level.txt +0 -0
@@ -80,7 +80,7 @@ class SocketIODaemonManager:
80
80
  return False
81
81
 
82
82
  try:
83
- with open(self.pid_file) as f:
83
+ with self.pid_file.open() as f:
84
84
  pid = int(f.read().strip())
85
85
 
86
86
  # Check if process exists and is running
@@ -135,7 +135,7 @@ class SocketIODaemonManager:
135
135
  pid = os.fork()
136
136
  if pid > 0:
137
137
  # Parent process - save PID and exit
138
- with open(self.pid_file, "w") as f:
138
+ with self.pid_file.open("w") as f:
139
139
  f.write(str(pid))
140
140
  logger.info(f"Socket.IO server started as daemon (PID: {pid})")
141
141
  return True
@@ -155,7 +155,7 @@ class SocketIODaemonManager:
155
155
  os.umask(0)
156
156
 
157
157
  # Redirect stdout/stderr to log file
158
- with open(self.log_file, "a") as log:
158
+ with self.log_file.open("a") as log:
159
159
  os.dup2(log.fileno(), sys.stdout.fileno())
160
160
  os.dup2(log.fileno(), sys.stderr.fileno())
161
161
 
@@ -199,7 +199,7 @@ class SocketIODaemonManager:
199
199
  return False
200
200
 
201
201
  try:
202
- with open(self.pid_file) as f:
202
+ with self.pid_file.open() as f:
203
203
  pid = int(f.read().strip())
204
204
 
205
205
  logger.info(f"Stopping Socket.IO server (PID: {pid})")
@@ -254,7 +254,7 @@ class SocketIODaemonManager:
254
254
  }
255
255
 
256
256
  if status_info["running"]:
257
- with open(self.pid_file) as f:
257
+ with self.pid_file.open() as f:
258
258
  status_info["pid"] = int(f.read().strip())
259
259
 
260
260
  # Check port accessibility
@@ -44,7 +44,9 @@ class MCPConfigManager:
44
44
  # Known missing dependencies for MCP services that pipx doesn't handle automatically
45
45
  # Maps service names to list of missing dependencies that need injection
46
46
  SERVICE_MISSING_DEPENDENCIES = {
47
- "mcp-ticketer": ["gql"], # mcp-ticketer v0.1.8+ needs gql but doesn't declare it
47
+ "mcp-ticketer": [
48
+ "gql"
49
+ ], # mcp-ticketer v0.1.8+ needs gql but doesn't declare it
48
50
  # Add more services here as needed, e.g.:
49
51
  # "another-service": ["dep1", "dep2"],
50
52
  }
@@ -80,15 +82,75 @@ class MCPConfigManager:
80
82
  },
81
83
  }
82
84
 
83
- def __init__(self):
84
- """Initialize the MCP configuration manager."""
85
+ def __init__(self, config=None):
86
+ """Initialize the MCP configuration manager.
87
+
88
+ Args:
89
+ config: Optional Config object for filtering services
90
+ """
85
91
  self.logger = get_logger(__name__)
86
92
  self.pipx_base = Path.home() / ".local" / "pipx" / "venvs"
87
93
  self.project_root = Path.cwd()
88
94
 
95
+ # Validate config type if provided
96
+ if config is not None:
97
+ from ..core.config import Config
98
+
99
+ if not isinstance(config, Config):
100
+ self.logger.warning(
101
+ f"Invalid config type provided to MCPConfigManager: "
102
+ f"{type(config).__name__}. Expected Config. "
103
+ f"Proceeding with config=None (all services enabled)."
104
+ )
105
+ config = None
106
+
107
+ self.config = config
108
+
89
109
  # Use the proper Claude config file location
90
110
  self.claude_config_path = ConfigLocation.CLAUDE_JSON.value
91
111
 
112
+ def should_enable_service(self, service_name: str) -> bool:
113
+ """
114
+ Check if an MCP service should be enabled based on startup configuration.
115
+
116
+ Args:
117
+ service_name: Name of the MCP service
118
+
119
+ Returns:
120
+ True if the service should be enabled, False otherwise
121
+ """
122
+ # If no config provided, enable all services by default
123
+ if self.config is None:
124
+ return True
125
+
126
+ # Import Config here to avoid circular import at module level
127
+ from ..core.config import Config
128
+
129
+ # Validate config type
130
+ if not isinstance(self.config, Config):
131
+ self.logger.warning(
132
+ f"Invalid config type: {type(self.config).__name__}, "
133
+ f"expected Config. Enabling all services by default."
134
+ )
135
+ return True
136
+
137
+ # Get startup configuration
138
+ enabled_services = self.config.get("startup.enabled_mcp_services", None)
139
+
140
+ # If no startup preferences configured, enable all services
141
+ if enabled_services is None:
142
+ return True
143
+
144
+ # Check if this service is in the enabled list
145
+ is_enabled = service_name in enabled_services
146
+
147
+ if not is_enabled:
148
+ self.logger.debug(
149
+ f"MCP service '{service_name}' disabled by startup configuration"
150
+ )
151
+
152
+ return is_enabled
153
+
92
154
  def detect_service_path(self, service_name: str) -> Optional[str]:
93
155
  """
94
156
  Detect the best path for an MCP service.
@@ -137,7 +199,7 @@ class MCPConfigManager:
137
199
  f"Found kuzu-memory with MCP support at {path}"
138
200
  )
139
201
  return path
140
- except:
202
+ except (subprocess.SubprocessError, subprocess.TimeoutExpired, OSError):
141
203
  pass
142
204
 
143
205
  # If no MCP-capable version found, log warning but return None
@@ -509,7 +571,7 @@ class MCPConfigManager:
509
571
  if result.returncode == 0 or "version" in result.stdout.lower():
510
572
  use_pipx_run = True
511
573
  self.logger.debug(f"Will use 'pipx run' for {service_name}")
512
- except:
574
+ except (subprocess.SubprocessError, subprocess.TimeoutExpired, OSError):
513
575
  pass
514
576
 
515
577
  # Try uvx if pipx run not available
@@ -525,7 +587,7 @@ class MCPConfigManager:
525
587
  if result.returncode == 0 or "version" in result.stdout.lower():
526
588
  use_uvx = True
527
589
  self.logger.debug(f"Will use 'uvx' for {service_name}")
528
- except:
590
+ except (subprocess.SubprocessError, subprocess.TimeoutExpired, OSError):
529
591
  pass
530
592
 
531
593
  # If neither work, try to find direct path
@@ -657,7 +719,7 @@ class MCPConfigManager:
657
719
  claude_config = {}
658
720
  if self.claude_config_path.exists():
659
721
  try:
660
- with open(self.claude_config_path) as f:
722
+ with self.claude_config_path.open() as f:
661
723
  claude_config = json.load(f)
662
724
  except Exception as e:
663
725
  self.logger.error(f"Error reading {self.claude_config_path}: {e}")
@@ -668,10 +730,8 @@ class MCPConfigManager:
668
730
  claude_config["projects"] = {}
669
731
  updated = True
670
732
 
671
- # Fix any corrupted MCP service installations first
672
- fix_success, fix_message = self.fix_mcp_service_issues()
673
- if not fix_success:
674
- self.logger.warning(f"Some MCP services could not be fixed: {fix_message}")
733
+ # Note: fix_mcp_service_issues() is already called during CLI initialization
734
+ # Calling it here would duplicate the service health checks
675
735
 
676
736
  # Process ALL projects in the config, not just current one
677
737
  projects_to_update = list(claude_config.get("projects", {}).keys())
@@ -704,19 +764,10 @@ class MCPConfigManager:
704
764
  project_config["mcpServers"] = {}
705
765
  updated = True
706
766
 
707
- # Check and fix each service configuration
708
- for service_name in self.PIPX_SERVICES:
709
- # Get the correct static configuration with project-specific paths
710
- correct_config = self.get_static_service_config(
711
- service_name, project_key
712
- )
713
-
714
- if not correct_config:
715
- self.logger.warning(
716
- f"No static config available for {service_name}"
717
- )
718
- continue
767
+ # Check and fix each service configuration - now filtered by startup config
768
+ services_to_configure = self.get_filtered_services()
719
769
 
770
+ for service_name, correct_config in services_to_configure.items():
720
771
  # Check if service exists and has correct configuration
721
772
  existing_config = project_config["mcpServers"].get(service_name)
722
773
 
@@ -742,6 +793,29 @@ class MCPConfigManager:
742
793
  f"Updated MCP service config for {service_name} in project {Path(project_key).name}"
743
794
  )
744
795
 
796
+ # Remove disabled services from configuration
797
+ if self.config is not None:
798
+ # Import Config here to avoid circular import
799
+ from ..core.config import Config
800
+
801
+ if isinstance(self.config, Config):
802
+ enabled_services = self.config.get(
803
+ "startup.enabled_mcp_services", None
804
+ )
805
+ if enabled_services is not None:
806
+ # Remove services that are not in the enabled list
807
+ services_to_remove = []
808
+ for service_name in project_config["mcpServers"]:
809
+ if service_name not in enabled_services:
810
+ services_to_remove.append(service_name)
811
+
812
+ for service_name in services_to_remove:
813
+ del project_config["mcpServers"][service_name]
814
+ updated = True
815
+ self.logger.debug(
816
+ f"Removed disabled service {service_name} from project {Path(project_key).name}"
817
+ )
818
+
745
819
  # Write updated config if changes were made
746
820
  if updated:
747
821
  try:
@@ -758,7 +832,7 @@ class MCPConfigManager:
758
832
  self.logger.debug(f"Created backup: {backup_path}")
759
833
 
760
834
  # Write updated config
761
- with open(self.claude_config_path, "w") as f:
835
+ with self.claude_config_path.open("w") as f:
762
836
  json.dump(claude_config, f, indent=2)
763
837
 
764
838
  messages = []
@@ -810,7 +884,7 @@ class MCPConfigManager:
810
884
  existing_config = {}
811
885
  if mcp_config_path.exists():
812
886
  try:
813
- with open(mcp_config_path) as f:
887
+ with mcp_config_path.open() as f:
814
888
  existing_config = json.load(f)
815
889
  except Exception as e:
816
890
  self.logger.error(f"Error reading existing config: {e}")
@@ -838,7 +912,7 @@ class MCPConfigManager:
838
912
 
839
913
  # Write the updated configuration
840
914
  try:
841
- with open(mcp_config_path, "w") as f:
915
+ with mcp_config_path.open("w") as f:
842
916
  json.dump(new_config, f, indent=2)
843
917
 
844
918
  if missing_services:
@@ -863,7 +937,7 @@ class MCPConfigManager:
863
937
  mcp_config_path = self.project_root / ConfigLocation.PROJECT_MCP.value
864
938
  if mcp_config_path.exists():
865
939
  try:
866
- with open(mcp_config_path) as f:
940
+ with mcp_config_path.open() as f:
867
941
  config = json.load(f)
868
942
  results = {}
869
943
  for service_name, service_config in config.get(
@@ -877,7 +951,7 @@ class MCPConfigManager:
877
951
  return {}
878
952
 
879
953
  try:
880
- with open(self.claude_config_path) as f:
954
+ with self.claude_config_path.open() as f:
881
955
  claude_config = json.load(f)
882
956
 
883
957
  # Get project's MCP servers
@@ -953,7 +1027,9 @@ class MCPConfigManager:
953
1027
  if result.returncode == 0:
954
1028
  # Inject any missing dependencies if needed
955
1029
  if service_name in self.SERVICE_MISSING_DEPENDENCIES:
956
- self.logger.debug(f"Injecting missing dependencies for newly installed {service_name}...")
1030
+ self.logger.debug(
1031
+ f"Injecting missing dependencies for newly installed {service_name}..."
1032
+ )
957
1033
  self._inject_missing_dependencies(service_name)
958
1034
 
959
1035
  # Verify installation worked
@@ -1063,18 +1139,19 @@ class MCPConfigManager:
1063
1139
  Returns:
1064
1140
  Tuple of (success, message)
1065
1141
  """
1066
- self.logger.info("🔍 Checking MCP services for issues...")
1067
-
1068
1142
  services_to_fix = []
1069
1143
  fixed_services = []
1070
1144
  failed_services = []
1071
1145
 
1072
1146
  # Check each service for issues
1073
1147
  for service_name in self.PIPX_SERVICES:
1148
+ self.logger.info(f"🔍 Checking {service_name} for issues...")
1074
1149
  issue_type = self._detect_service_issue(service_name)
1075
1150
  if issue_type:
1076
1151
  services_to_fix.append((service_name, issue_type))
1077
- self.logger.debug(f"Found issue with {service_name}: {issue_type}")
1152
+ self.logger.info(f" ⚠️ Found issue with {service_name}: {issue_type}")
1153
+ else:
1154
+ self.logger.debug(f" ✅ {service_name} is functioning correctly")
1078
1155
 
1079
1156
  if not services_to_fix:
1080
1157
  return True, "All MCP services are functioning correctly"
@@ -1118,14 +1195,20 @@ class MCPConfigManager:
1118
1195
  issue_after_injection = self._detect_service_issue(service_name)
1119
1196
  if issue_after_injection is None:
1120
1197
  fixed_services.append(f"{service_name} (dependencies injected)")
1121
- self.logger.info(f" ✅ Fixed {service_name} with dependency injection")
1198
+ self.logger.info(
1199
+ f" ✅ Fixed {service_name} with dependency injection"
1200
+ )
1122
1201
  continue # Move to next service
1123
1202
 
1124
1203
  # If injection alone didn't work, try full reinstall
1125
- self.logger.info(" Dependency injection insufficient, trying full reinstall...")
1204
+ self.logger.info(
1205
+ " Dependency injection insufficient, trying full reinstall..."
1206
+ )
1126
1207
  success = self._auto_reinstall_mcp_service(service_name)
1127
1208
  if success:
1128
- fixed_services.append(f"{service_name} (auto-reinstalled with dependencies)")
1209
+ fixed_services.append(
1210
+ f"{service_name} (auto-reinstalled with dependencies)"
1211
+ )
1129
1212
  else:
1130
1213
  # Provide specific manual fix for known services
1131
1214
  if service_name == "mcp-ticketer":
@@ -1164,7 +1247,12 @@ class MCPConfigManager:
1164
1247
  for failed in failed_services:
1165
1248
  service = failed.split(" ")[0]
1166
1249
  if service in self.SERVICE_MISSING_DEPENDENCIES:
1167
- deps = " ".join([f"&& pipx inject {service} {dep}" for dep in self.SERVICE_MISSING_DEPENDENCIES[service]])
1250
+ deps = " ".join(
1251
+ [
1252
+ f"&& pipx inject {service} {dep}"
1253
+ for dep in self.SERVICE_MISSING_DEPENDENCIES[service]
1254
+ ]
1255
+ )
1168
1256
  message += f"\n • {service}: pipx uninstall {service} && pipx install {service} {deps}"
1169
1257
  else:
1170
1258
  message += f"\n • {service}: pipx uninstall {service} && pipx install {service}"
@@ -1191,7 +1279,9 @@ class MCPConfigManager:
1191
1279
  if pipx_venv_bin.exists():
1192
1280
  # Test the installed version directly (has injected dependencies)
1193
1281
  # This avoids using pipx run which downloads a fresh cache copy without dependencies
1194
- self.logger.debug(f"Testing {service_name} from installed pipx venv: {pipx_venv_bin}")
1282
+ self.logger.debug(
1283
+ f" Testing {service_name} from installed pipx venv: {pipx_venv_bin}"
1284
+ )
1195
1285
  result = subprocess.run(
1196
1286
  [str(pipx_venv_bin), "--help"],
1197
1287
  capture_output=True,
@@ -1225,19 +1315,25 @@ class MCPConfigManager:
1225
1315
  or "help" in combined_output
1226
1316
  or result.returncode in [0, 1]
1227
1317
  ):
1228
- self.logger.debug(f"{service_name} is working correctly")
1318
+ self.logger.debug(
1319
+ f" {service_name} is working correctly (installed in venv)"
1320
+ )
1229
1321
  return None # Service is working
1230
1322
 
1231
1323
  # Unknown issue
1232
1324
  if result.returncode not in [0, 1]:
1233
- self.logger.debug(f"{service_name} returned unexpected exit code: {result.returncode}")
1325
+ self.logger.debug(
1326
+ f"{service_name} returned unexpected exit code: {result.returncode}"
1327
+ )
1234
1328
  return "unknown_error"
1235
1329
 
1236
1330
  return None # Default to working if no issues detected
1237
1331
 
1238
1332
  # Service not installed in pipx venv - use pipx run for detection
1239
1333
  # Note: pipx run uses cache which may not have injected dependencies
1240
- self.logger.debug(f"Testing {service_name} via pipx run (not installed in venv)")
1334
+ self.logger.debug(
1335
+ f" Testing {service_name} via pipx run (not installed in venv)"
1336
+ )
1241
1337
  result = subprocess.run(
1242
1338
  ["pipx", "run", service_name, "--help"],
1243
1339
  capture_output=True,
@@ -1265,7 +1361,9 @@ class MCPConfigManager:
1265
1361
  ):
1266
1362
  # Don't report missing_dependency for cache version - it may be missing injected deps
1267
1363
  # Just report that service needs to be installed properly
1268
- self.logger.debug(f"{service_name} has import errors in pipx run cache - needs proper installation")
1364
+ self.logger.debug(
1365
+ f"{service_name} has import errors in pipx run cache - needs proper installation"
1366
+ )
1269
1367
  return "not_installed"
1270
1368
 
1271
1369
  # Path issues
@@ -1331,7 +1429,9 @@ class MCPConfigManager:
1331
1429
  if install_result.returncode == 0:
1332
1430
  # Inject any missing dependencies if needed
1333
1431
  if service_name in self.SERVICE_MISSING_DEPENDENCIES:
1334
- self.logger.debug(f"Injecting missing dependencies for {service_name}...")
1432
+ self.logger.debug(
1433
+ f"Injecting missing dependencies for {service_name}..."
1434
+ )
1335
1435
  self._inject_missing_dependencies(service_name)
1336
1436
 
1337
1437
  # Verify the reinstall worked
@@ -1394,12 +1494,13 @@ class MCPConfigManager:
1394
1494
  if result.returncode == 0:
1395
1495
  self.logger.info(f" ✅ Successfully injected {dep}")
1396
1496
  # Check if already injected (pipx will complain if package already exists)
1397
- elif "already satisfied" in result.stderr.lower() or "already installed" in result.stderr.lower():
1497
+ elif (
1498
+ "already satisfied" in result.stderr.lower()
1499
+ or "already installed" in result.stderr.lower()
1500
+ ):
1398
1501
  self.logger.debug(f" {dep} already present in {service_name}")
1399
1502
  else:
1400
- self.logger.error(
1401
- f" Failed to inject {dep}: {result.stderr}"
1402
- )
1503
+ self.logger.error(f" Failed to inject {dep}: {result.stderr}")
1403
1504
  all_successful = False
1404
1505
 
1405
1506
  except subprocess.TimeoutExpired:
@@ -1467,7 +1568,9 @@ class MCPConfigManager:
1467
1568
 
1468
1569
  # Inject any missing dependencies that pipx doesn't handle automatically
1469
1570
  if service_name in self.SERVICE_MISSING_DEPENDENCIES:
1470
- self.logger.info(f" → Fixing missing dependencies for {service_name}...")
1571
+ self.logger.info(
1572
+ f" → Fixing missing dependencies for {service_name}..."
1573
+ )
1471
1574
  if not self._inject_missing_dependencies(service_name):
1472
1575
  self.logger.warning(
1473
1576
  f"Failed to inject all dependencies for {service_name}, but continuing..."
@@ -1535,7 +1638,7 @@ class MCPConfigManager:
1535
1638
  if result.returncode == 0 or "version" in result.stdout.lower():
1536
1639
  self.logger.debug(f"{service_name} accessible via 'pipx run'")
1537
1640
  return True
1538
- except:
1641
+ except (subprocess.SubprocessError, subprocess.TimeoutExpired, OSError):
1539
1642
  pass
1540
1643
  return False
1541
1644
 
@@ -1608,3 +1711,21 @@ class MCPConfigManager:
1608
1711
 
1609
1712
  # For other services, try pipx run
1610
1713
  return None
1714
+
1715
+ def get_filtered_services(self) -> Dict[str, Dict]:
1716
+ """Get all MCP service configurations filtered by startup configuration.
1717
+
1718
+ Returns:
1719
+ Dictionary of service configurations, filtered based on startup settings
1720
+ """
1721
+ filtered_services = {}
1722
+
1723
+ for service_name in self.STATIC_MCP_CONFIGS:
1724
+ if self.should_enable_service(service_name):
1725
+ # Get the actual service configuration with proper paths
1726
+ service_config = self.get_static_service_config(service_name)
1727
+ if service_config:
1728
+ filtered_services[service_name] = service_config
1729
+ # Removed noisy debug logging that was called multiple times per startup
1730
+
1731
+ return filtered_services
@@ -26,100 +26,104 @@ __version__ = "0.1.0"
26
26
 
27
27
 
28
28
  # Lazy imports to prevent circular dependencies and improve startup performance
29
- def __getattr__(name): # noqa: PLR0911
30
- """Lazy import mechanism for MCP Gateway components."""
31
-
32
- # Core interfaces and base classes
33
- if name == "IMCPGateway":
34
- from .core.interfaces import IMCPGateway
35
-
36
- return IMCPGateway
37
- if name == "IMCPToolRegistry":
38
- from .core.interfaces import IMCPToolRegistry
39
-
40
- return IMCPToolRegistry
41
- if name == "IMCPConfiguration":
42
- from .core.interfaces import IMCPConfiguration
43
-
44
- return IMCPConfiguration
45
- if name == "IMCPToolAdapter":
46
- from .core.interfaces import IMCPToolAdapter
47
-
48
- return IMCPToolAdapter
49
- if name == "BaseMCPService":
50
- from .core.base import BaseMCPService
51
-
52
- return BaseMCPService
53
-
54
- # Gateway implementations
55
- if name == "MCPGateway":
56
- from .server.mcp_gateway import MCPGateway
57
-
58
- return MCPGateway
59
- if name == "StdioHandler":
60
- from .server.stdio_handler import StdioHandler
61
-
62
- return StdioHandler
63
- if name == "AlternativeStdioHandler":
64
- from .server.stdio_handler import AlternativeStdioHandler
65
-
66
- return AlternativeStdioHandler
67
-
68
- # Tool registry and adapters
69
- if name == "ToolRegistry":
70
- from .registry.tool_registry import ToolRegistry
71
-
72
- return ToolRegistry
73
- if name == "BaseToolAdapter":
74
- from .tools.base_adapter import BaseToolAdapter
75
-
76
- return BaseToolAdapter
77
- if name == "EchoToolAdapter":
78
- from .tools.base_adapter import EchoToolAdapter
79
-
80
- return EchoToolAdapter
81
- if name == "CalculatorToolAdapter":
82
- from .tools.base_adapter import CalculatorToolAdapter
83
-
84
- return CalculatorToolAdapter
85
- if name == "SystemInfoToolAdapter":
86
- from .tools.base_adapter import SystemInfoToolAdapter
87
-
88
- return SystemInfoToolAdapter
89
-
90
- # Configuration management
91
- if name == "MCPConfiguration":
92
- from .config.configuration import MCPConfiguration
93
-
94
- return MCPConfiguration
95
- if name == "MCPConfigLoader":
96
- from .config.config_loader import MCPConfigLoader
97
-
98
- return MCPConfigLoader
99
-
100
- # Service registry
101
- if name == "MCPServiceRegistry":
102
- from .registry.service_registry import MCPServiceRegistry
103
-
104
- return MCPServiceRegistry
105
-
106
- # Exceptions
107
- if name == "MCPException":
108
- from .core.exceptions import MCPException
109
-
110
- return MCPException
111
- if name == "MCPConfigurationError":
112
- from .core.exceptions import MCPConfigurationError
113
-
114
- return MCPConfigurationError
115
- if name == "MCPToolNotFoundError":
116
- from .core.exceptions import MCPToolNotFoundError
117
-
118
- return MCPToolNotFoundError
119
- if name == "MCPServerError":
120
- from .core.exceptions import MCPServerError
121
-
122
- return MCPServerError
29
+ def __getattr__(name):
30
+ """Lazy import mechanism for MCP Gateway components using dictionary-based mapping."""
31
+ from importlib import import_module
32
+
33
+ # Dictionary mapping: name -> (module_path, attribute_name)
34
+ _LAZY_IMPORTS = {
35
+ # Core interfaces and base classes
36
+ "IMCPGateway": (
37
+ "claude_mpm.services.mcp_gateway.core.interfaces",
38
+ "IMCPGateway",
39
+ ),
40
+ "IMCPToolRegistry": (
41
+ "claude_mpm.services.mcp_gateway.core.interfaces",
42
+ "IMCPToolRegistry",
43
+ ),
44
+ "IMCPConfiguration": (
45
+ "claude_mpm.services.mcp_gateway.core.interfaces",
46
+ "IMCPConfiguration",
47
+ ),
48
+ "IMCPToolAdapter": (
49
+ "claude_mpm.services.mcp_gateway.core.interfaces",
50
+ "IMCPToolAdapter",
51
+ ),
52
+ "BaseMCPService": (
53
+ "claude_mpm.services.mcp_gateway.core.base",
54
+ "BaseMCPService",
55
+ ),
56
+ # Gateway implementations
57
+ "MCPGateway": (
58
+ "claude_mpm.services.mcp_gateway.server.mcp_gateway",
59
+ "MCPGateway",
60
+ ),
61
+ "StdioHandler": (
62
+ "claude_mpm.services.mcp_gateway.server.stdio_handler",
63
+ "StdioHandler",
64
+ ),
65
+ "AlternativeStdioHandler": (
66
+ "claude_mpm.services.mcp_gateway.server.stdio_handler",
67
+ "AlternativeStdioHandler",
68
+ ),
69
+ # Tool registry and adapters
70
+ "ToolRegistry": (
71
+ "claude_mpm.services.mcp_gateway.registry.tool_registry",
72
+ "ToolRegistry",
73
+ ),
74
+ "BaseToolAdapter": (
75
+ "claude_mpm.services.mcp_gateway.tools.base_adapter",
76
+ "BaseToolAdapter",
77
+ ),
78
+ "EchoToolAdapter": (
79
+ "claude_mpm.services.mcp_gateway.tools.base_adapter",
80
+ "EchoToolAdapter",
81
+ ),
82
+ "CalculatorToolAdapter": (
83
+ "claude_mpm.services.mcp_gateway.tools.base_adapter",
84
+ "CalculatorToolAdapter",
85
+ ),
86
+ "SystemInfoToolAdapter": (
87
+ "claude_mpm.services.mcp_gateway.tools.base_adapter",
88
+ "SystemInfoToolAdapter",
89
+ ),
90
+ # Configuration management
91
+ "MCPConfiguration": (
92
+ "claude_mpm.services.mcp_gateway.config.configuration",
93
+ "MCPConfiguration",
94
+ ),
95
+ "MCPConfigLoader": (
96
+ "claude_mpm.services.mcp_gateway.config.config_loader",
97
+ "MCPConfigLoader",
98
+ ),
99
+ # Service registry
100
+ "MCPServiceRegistry": (
101
+ "claude_mpm.services.mcp_gateway.registry.service_registry",
102
+ "MCPServiceRegistry",
103
+ ),
104
+ # Exceptions
105
+ "MCPException": (
106
+ "claude_mpm.services.mcp_gateway.core.exceptions",
107
+ "MCPException",
108
+ ),
109
+ "MCPConfigurationError": (
110
+ "claude_mpm.services.mcp_gateway.core.exceptions",
111
+ "MCPConfigurationError",
112
+ ),
113
+ "MCPToolNotFoundError": (
114
+ "claude_mpm.services.mcp_gateway.core.exceptions",
115
+ "MCPToolNotFoundError",
116
+ ),
117
+ "MCPServerError": (
118
+ "claude_mpm.services.mcp_gateway.core.exceptions",
119
+ "MCPServerError",
120
+ ),
121
+ }
122
+
123
+ if name in _LAZY_IMPORTS:
124
+ module_path, attr_name = _LAZY_IMPORTS[name]
125
+ module = import_module(module_path)
126
+ return getattr(module, attr_name)
123
127
 
124
128
  raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
125
129