claude-mpm 4.0.34__py3-none-any.whl → 4.1.1__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 (35) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +70 -2
  3. claude_mpm/agents/OUTPUT_STYLE.md +0 -11
  4. claude_mpm/agents/WORKFLOW.md +14 -2
  5. claude_mpm/agents/templates/web_qa.json +85 -58
  6. claude_mpm/agents/templates/web_ui.json +3 -3
  7. claude_mpm/cli/__init__.py +48 -7
  8. claude_mpm/cli/commands/agents.py +82 -0
  9. claude_mpm/cli/commands/cleanup_orphaned_agents.py +150 -0
  10. claude_mpm/cli/commands/mcp_pipx_config.py +199 -0
  11. claude_mpm/cli/parsers/agents_parser.py +27 -0
  12. claude_mpm/cli/parsers/base_parser.py +6 -0
  13. claude_mpm/cli/startup_logging.py +75 -0
  14. claude_mpm/dashboard/static/js/components/build-tracker.js +35 -1
  15. claude_mpm/dashboard/static/js/socket-client.js +7 -5
  16. claude_mpm/hooks/claude_hooks/connection_pool.py +13 -2
  17. claude_mpm/hooks/claude_hooks/hook_handler.py +67 -167
  18. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -1
  19. claude_mpm/services/agents/deployment/agent_template_builder.py +2 -1
  20. claude_mpm/services/agents/deployment/agent_version_manager.py +4 -1
  21. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +207 -10
  22. claude_mpm/services/event_bus/config.py +165 -0
  23. claude_mpm/services/event_bus/event_bus.py +35 -20
  24. claude_mpm/services/event_bus/relay.py +8 -12
  25. claude_mpm/services/mcp_gateway/auto_configure.py +372 -0
  26. claude_mpm/services/socketio/handlers/connection.py +3 -3
  27. claude_mpm/services/socketio/server/core.py +25 -2
  28. claude_mpm/services/socketio/server/eventbus_integration.py +189 -0
  29. claude_mpm/services/socketio/server/main.py +25 -0
  30. {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.1.dist-info}/METADATA +25 -7
  31. {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.1.dist-info}/RECORD +35 -30
  32. {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.1.dist-info}/WHEEL +0 -0
  33. {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.1.dist-info}/entry_points.txt +0 -0
  34. {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.1.dist-info}/licenses/LICENSE +0 -0
  35. {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.1.dist-info}/top_level.txt +0 -0
@@ -83,15 +83,26 @@ def main(argv: Optional[list] = None):
83
83
  # Initialize or update project registry
84
84
  _initialize_project_registry()
85
85
 
86
- # Verify MCP Gateway configuration on startup (non-blocking)
87
- _verify_mcp_gateway_startup()
88
-
89
- # Create parser with version
86
+ # Parse args early to check if we should skip auto-configuration
87
+ # (for commands like --version, --help, etc.)
90
88
  parser = create_parser(version=__version__)
91
-
92
- # Preprocess and parse arguments
93
89
  processed_argv = preprocess_args(argv)
94
90
  args = parser.parse_args(processed_argv)
91
+
92
+ # Skip auto-configuration for certain commands
93
+ skip_auto_config_commands = ["--version", "-v", "--help", "-h"]
94
+ # sys is already imported at module level (line 16), use it directly
95
+ should_skip_auto_config = (
96
+ any(cmd in (processed_argv or sys.argv[1:]) for cmd in skip_auto_config_commands)
97
+ or (hasattr(args, 'command') and args.command in ["info", "doctor", "config", "mcp"]) # Info, diagnostic, and MCP commands
98
+ )
99
+
100
+ if not should_skip_auto_config:
101
+ # Check for MCP auto-configuration (pipx installations)
102
+ _check_mcp_auto_configuration()
103
+
104
+ # Verify MCP Gateway configuration on startup (non-blocking)
105
+ _verify_mcp_gateway_startup()
95
106
 
96
107
  # Set up logging
97
108
  # Special case: For MCP start command, we need minimal logging to avoid stdout interference
@@ -101,7 +112,7 @@ def main(argv: Optional[list] = None):
101
112
  ):
102
113
  # For MCP server, configure minimal stderr-only logging
103
114
  import logging
104
- import sys
115
+ # sys is already imported at module level
105
116
 
106
117
  # Only log errors to stderr for MCP server
107
118
  if not getattr(args, "test", False) and not getattr(
@@ -176,6 +187,36 @@ def _initialize_project_registry():
176
187
  # Continue execution - registry failure shouldn't block startup
177
188
 
178
189
 
190
+ def _check_mcp_auto_configuration():
191
+ """
192
+ Check and potentially auto-configure MCP for pipx installations.
193
+
194
+ WHY: Users installing via pipx should have MCP work out-of-the-box with
195
+ minimal friction. This function offers one-time auto-configuration with
196
+ user consent.
197
+
198
+ DESIGN DECISION: This is blocking but quick - it only runs once and has
199
+ a 10-second timeout. We want to catch users on first run for the best
200
+ experience.
201
+ """
202
+ try:
203
+ from ..services.mcp_gateway.auto_configure import check_and_configure_mcp
204
+
205
+ # This function handles all the logic:
206
+ # - Checks if already configured
207
+ # - Checks if pipx installation
208
+ # - Checks if already asked before
209
+ # - Prompts user if needed
210
+ # - Configures if user agrees
211
+ check_and_configure_mcp()
212
+
213
+ except Exception as e:
214
+ # Non-critical - log but don't fail
215
+ from ..core.logger import get_logger
216
+ logger = get_logger("cli")
217
+ logger.debug(f"MCP auto-configuration check failed: {e}")
218
+
219
+
179
220
  def _verify_mcp_gateway_startup():
180
221
  """
181
222
  Verify MCP Gateway configuration on startup and pre-warm MCP services.
@@ -71,6 +71,7 @@ class AgentsCommand(AgentCommand):
71
71
  "deps-install": self._install_agent_dependencies,
72
72
  "deps-list": self._list_agent_dependencies,
73
73
  "deps-fix": self._fix_agent_dependencies,
74
+ "cleanup-orphaned": self._cleanup_orphaned_agents,
74
75
  }
75
76
 
76
77
  if args.agents_command in command_map:
@@ -469,6 +470,87 @@ class AgentsCommand(AgentCommand):
469
470
  except Exception as e:
470
471
  self.logger.error(f"Error fixing dependencies: {e}", exc_info=True)
471
472
  return CommandResult.error_result(f"Error fixing dependencies: {e}")
473
+
474
+ def _cleanup_orphaned_agents(self, args) -> CommandResult:
475
+ """Clean up orphaned agents that don't have templates."""
476
+ try:
477
+ from ...services.agents.deployment.multi_source_deployment_service import (
478
+ MultiSourceAgentDeploymentService
479
+ )
480
+
481
+ # Determine agents directory
482
+ if hasattr(args, 'agents_dir') and args.agents_dir:
483
+ agents_dir = args.agents_dir
484
+ else:
485
+ # Check for project-level .claude/agents first
486
+ project_agents_dir = Path.cwd() / ".claude" / "agents"
487
+ if project_agents_dir.exists():
488
+ agents_dir = project_agents_dir
489
+ else:
490
+ # Fall back to user home directory
491
+ agents_dir = Path.home() / ".claude" / "agents"
492
+
493
+ if not agents_dir.exists():
494
+ return CommandResult.success_result(f"Agents directory not found: {agents_dir}")
495
+
496
+ # Initialize service
497
+ service = MultiSourceAgentDeploymentService()
498
+
499
+ # Determine if we're doing a dry run
500
+ dry_run = getattr(args, 'dry_run', True)
501
+ if hasattr(args, 'force') and args.force:
502
+ dry_run = False
503
+
504
+ # Perform cleanup
505
+ results = service.cleanup_orphaned_agents(agents_dir, dry_run=dry_run)
506
+
507
+ output_format = getattr(args, 'format', 'text')
508
+ quiet = getattr(args, 'quiet', False)
509
+
510
+ if output_format in ['json', 'yaml']:
511
+ return CommandResult.success_result(
512
+ f"Found {len(results.get('orphaned', []))} orphaned agents",
513
+ data=results
514
+ )
515
+ else:
516
+ # Text output
517
+ if not results.get("orphaned"):
518
+ print("✅ No orphaned agents found")
519
+ return CommandResult.success_result("No orphaned agents found")
520
+
521
+ if not quiet:
522
+ print(f"\nFound {len(results['orphaned'])} orphaned agent(s):")
523
+ for orphan in results["orphaned"]:
524
+ print(f" - {orphan['name']} v{orphan['version']}")
525
+
526
+ if dry_run:
527
+ print(
528
+ f"\n📝 This was a dry run. Use --force to actually remove "
529
+ f"{len(results['orphaned'])} orphaned agent(s)"
530
+ )
531
+ else:
532
+ if results.get("removed"):
533
+ print(
534
+ f"\n✅ Successfully removed {len(results['removed'])} orphaned agent(s)"
535
+ )
536
+
537
+ if results.get("errors"):
538
+ print(f"\n❌ Encountered {len(results['errors'])} error(s):")
539
+ for error in results["errors"]:
540
+ print(f" - {error}")
541
+ return CommandResult.error_result(
542
+ f"Cleanup completed with {len(results['errors'])} errors",
543
+ data=results
544
+ )
545
+
546
+ return CommandResult.success_result(
547
+ f"Cleanup {'preview' if dry_run else 'completed'}",
548
+ data=results
549
+ )
550
+
551
+ except Exception as e:
552
+ self.logger.error(f"Error during cleanup: {e}", exc_info=True)
553
+ return CommandResult.error_result(f"Error during cleanup: {e}")
472
554
 
473
555
 
474
556
  def manage_agents(args):
@@ -0,0 +1,150 @@
1
+ """CLI command to clean up orphaned agents without templates.
2
+
3
+ This command helps manage deployed agents that no longer have corresponding
4
+ templates, which can happen when agents are removed from the system or when
5
+ switching between different agent sources.
6
+ """
7
+
8
+ import argparse
9
+ from pathlib import Path
10
+ from typing import Optional
11
+
12
+ from claude_mpm.core.logging_config import get_logger
13
+ from claude_mpm.services.agents.deployment.multi_source_deployment_service import (
14
+ MultiSourceAgentDeploymentService
15
+ )
16
+
17
+
18
+ def add_parser(subparsers: argparse._SubParsersAction) -> None:
19
+ """Add the cleanup-orphaned-agents command parser.
20
+
21
+ Args:
22
+ subparsers: The subparsers object from argparse
23
+ """
24
+ parser = subparsers.add_parser(
25
+ "cleanup-orphaned-agents",
26
+ help="Clean up orphaned agents that don't have templates",
27
+ description=(
28
+ "Detect and optionally remove deployed agents that no longer have "
29
+ "corresponding templates. This can happen when agents are removed "
30
+ "from the system or when switching between agent sources."
31
+ ),
32
+ )
33
+
34
+ parser.add_argument(
35
+ "--agents-dir",
36
+ type=Path,
37
+ help="Directory containing deployed agents (default: .claude/agents/)",
38
+ )
39
+
40
+ parser.add_argument(
41
+ "--dry-run",
42
+ action="store_true",
43
+ default=True,
44
+ help="Only show what would be removed without actually removing (default)",
45
+ )
46
+
47
+ parser.add_argument(
48
+ "--force",
49
+ action="store_true",
50
+ help="Actually remove orphaned agents (disables dry-run)",
51
+ )
52
+
53
+ parser.add_argument(
54
+ "--quiet",
55
+ action="store_true",
56
+ help="Only show summary, not individual agents",
57
+ )
58
+
59
+ parser.set_defaults(func=cleanup_orphaned_agents)
60
+
61
+
62
+ def cleanup_orphaned_agents(args: argparse.Namespace) -> int:
63
+ """Clean up orphaned agents.
64
+
65
+ Args:
66
+ args: Command line arguments
67
+
68
+ Returns:
69
+ Exit code (0 for success, non-zero for errors)
70
+ """
71
+ logger = get_logger(__name__)
72
+
73
+ # Determine agents directory
74
+ if args.agents_dir:
75
+ agents_dir = args.agents_dir
76
+ else:
77
+ # Check for project-level .claude/agents first
78
+ project_agents_dir = Path.cwd() / ".claude" / "agents"
79
+ if project_agents_dir.exists():
80
+ agents_dir = project_agents_dir
81
+ else:
82
+ # Fall back to user home directory
83
+ agents_dir = Path.home() / ".claude" / "agents"
84
+
85
+ if not agents_dir.exists():
86
+ logger.info(f"Agents directory not found: {agents_dir}")
87
+ return 0
88
+
89
+ logger.info(f"Checking for orphaned agents in: {agents_dir}")
90
+
91
+ # Initialize service
92
+ service = MultiSourceAgentDeploymentService()
93
+
94
+ # Determine if we're doing a dry run
95
+ dry_run = args.dry_run and not args.force
96
+
97
+ try:
98
+ # Perform cleanup
99
+ results = service.cleanup_orphaned_agents(agents_dir, dry_run=dry_run)
100
+
101
+ # Handle results
102
+ if not results["orphaned"]:
103
+ logger.info("✅ No orphaned agents found")
104
+ return 0
105
+
106
+ if not args.quiet:
107
+ logger.info(f"\nFound {len(results['orphaned'])} orphaned agent(s):")
108
+ for orphan in results["orphaned"]:
109
+ logger.info(f" - {orphan['name']} v{orphan['version']}")
110
+
111
+ if dry_run:
112
+ logger.info(
113
+ f"\n📝 This was a dry run. Use --force to actually remove "
114
+ f"{len(results['orphaned'])} orphaned agent(s)"
115
+ )
116
+ else:
117
+ if results["removed"]:
118
+ logger.info(
119
+ f"\n✅ Successfully removed {len(results['removed'])} orphaned agent(s)"
120
+ )
121
+
122
+ if results["errors"]:
123
+ logger.error(f"\n❌ Encountered {len(results['errors'])} error(s):")
124
+ for error in results["errors"]:
125
+ logger.error(f" - {error}")
126
+ return 1
127
+
128
+ return 0
129
+
130
+ except Exception as e:
131
+ logger.error(f"Error during cleanup: {e}")
132
+ return 1
133
+
134
+
135
+ # For backward compatibility
136
+ def main(args: Optional[argparse.Namespace] = None) -> int:
137
+ """Main entry point for the command.
138
+
139
+ Args:
140
+ args: Command line arguments
141
+
142
+ Returns:
143
+ Exit code
144
+ """
145
+ if args is None:
146
+ parser = argparse.ArgumentParser()
147
+ add_parser(parser.add_subparsers())
148
+ args = parser.parse_args()
149
+
150
+ return cleanup_orphaned_agents(args)
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ MCP configuration command for pipx installations.
4
+
5
+ This module provides a CLI command to configure MCP for users who installed
6
+ claude-mpm via pipx.
7
+ """
8
+
9
+ import json
10
+ import os
11
+ import platform
12
+ import subprocess
13
+ import sys
14
+ from pathlib import Path
15
+ from typing import Optional, Dict, Any
16
+
17
+ from claude_mpm.core.logger import get_logger
18
+
19
+ logger = get_logger(__name__)
20
+
21
+
22
+ def find_claude_config_path() -> Path:
23
+ """Find the Claude Code configuration file path."""
24
+ system = platform.system()
25
+
26
+ if system == "Darwin": # macOS
27
+ config_path = Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json"
28
+ elif system == "Windows":
29
+ appdata = os.environ.get("APPDATA")
30
+ if appdata:
31
+ config_path = Path(appdata) / "Claude" / "claude_desktop_config.json"
32
+ else:
33
+ config_path = Path.home() / "AppData" / "Roaming" / "Claude" / "claude_desktop_config.json"
34
+ else: # Linux and others
35
+ config_path = Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
36
+
37
+ return config_path
38
+
39
+
40
+ def check_pipx_installation() -> bool:
41
+ """Check if claude-mpm is installed via pipx."""
42
+ try:
43
+ # Check if running from pipx
44
+ if "pipx" in sys.executable.lower():
45
+ return True
46
+
47
+ # Check pipx list
48
+ result = subprocess.run(
49
+ ["pipx", "list", "--json"],
50
+ capture_output=True,
51
+ text=True,
52
+ timeout=5
53
+ )
54
+ if result.returncode == 0:
55
+ pipx_data = json.loads(result.stdout)
56
+ return "claude-mpm" in pipx_data.get("venvs", {})
57
+ except Exception:
58
+ pass
59
+
60
+ return False
61
+
62
+
63
+ def create_mcp_config() -> Dict[str, Any]:
64
+ """Create MCP configuration for pipx installation."""
65
+ return {
66
+ "mcpServers": {
67
+ "claude-mpm-gateway": {
68
+ "command": "claude-mpm-mcp"
69
+ }
70
+ }
71
+ }
72
+
73
+
74
+ def configure_mcp_for_pipx(args) -> int:
75
+ """
76
+ Configure MCP for pipx installation.
77
+
78
+ Args:
79
+ args: Command line arguments
80
+
81
+ Returns:
82
+ Exit code (0 for success, 1 for failure)
83
+ """
84
+ print("Claude MPM - MCP Configuration for pipx")
85
+ print("=" * 40)
86
+
87
+ # Check if this is a pipx installation
88
+ if not check_pipx_installation():
89
+ print("\n⚠️ This doesn't appear to be a pipx installation")
90
+ print("This command is specifically for pipx users.")
91
+ print("\nFor other installation methods, see:")
92
+ print(" docs/MCP_SETUP.md")
93
+
94
+ if not args.force:
95
+ return 1
96
+ print("\n--force flag detected, continuing anyway...")
97
+
98
+ # Find Claude config
99
+ config_path = find_claude_config_path()
100
+ print(f"\n📁 Claude config path: {config_path}")
101
+
102
+ # Load existing config
103
+ existing_config = {}
104
+ if config_path.exists():
105
+ try:
106
+ with open(config_path, 'r') as f:
107
+ existing_config = json.load(f)
108
+ print("✅ Existing config loaded")
109
+ except json.JSONDecodeError:
110
+ print("⚠️ Config exists but is invalid JSON")
111
+ if not args.force:
112
+ print("Use --force to overwrite")
113
+ return 1
114
+ else:
115
+ print("📝 Config will be created")
116
+ config_path.parent.mkdir(parents=True, exist_ok=True)
117
+
118
+ # Check for existing MCP config
119
+ if "mcpServers" in existing_config and "claude-mpm-gateway" in existing_config["mcpServers"]:
120
+ print("\n⚠️ claude-mpm-gateway is already configured")
121
+ if not args.force:
122
+ print("Use --force to overwrite")
123
+ return 0
124
+ print("Overwriting existing configuration...")
125
+
126
+ # Create and merge config
127
+ mcp_config = create_mcp_config()
128
+ existing_config.update(mcp_config)
129
+
130
+ # Show what will be written
131
+ if not args.quiet:
132
+ print("\n📝 Configuration to write:")
133
+ print(json.dumps(mcp_config, indent=2))
134
+
135
+ # Write config
136
+ if not args.dry_run:
137
+ try:
138
+ with open(config_path, 'w') as f:
139
+ json.dump(existing_config, f, indent=2)
140
+ print(f"\n✅ Configuration written to: {config_path}")
141
+ except Exception as e:
142
+ print(f"\n❌ Failed to write config: {e}")
143
+ return 1
144
+ else:
145
+ print("\n--dry-run: Configuration not written")
146
+
147
+ # Test the command
148
+ print("\n🧪 Testing claude-mpm-mcp command...")
149
+ try:
150
+ result = subprocess.run(
151
+ ["which", "claude-mpm-mcp"],
152
+ capture_output=True,
153
+ text=True,
154
+ timeout=2
155
+ )
156
+ if result.returncode == 0:
157
+ print(f"✅ Command found: {result.stdout.strip()}")
158
+ else:
159
+ print("⚠️ Command not found in PATH")
160
+ print(" Ensure pipx bin directory is in your PATH")
161
+ except Exception as e:
162
+ print(f"⚠️ Could not test command: {e}")
163
+
164
+ print("\n✨ Next steps:")
165
+ print("1. Restart Claude Code")
166
+ print("2. Look for the MCP icon in the interface")
167
+ print("3. Try using @claude-mpm-gateway in a conversation")
168
+ print("\nFor more help, see: docs/MCP_PIPX_SETUP.md")
169
+
170
+ return 0
171
+
172
+
173
+ def add_parser(subparsers):
174
+ """Add the mcp-pipx-config command parser."""
175
+ parser = subparsers.add_parser(
176
+ 'mcp-pipx-config',
177
+ help='Configure MCP for pipx installation',
178
+ description='Configure MCP Gateway for Claude Code when installed via pipx'
179
+ )
180
+
181
+ parser.add_argument(
182
+ '--force',
183
+ action='store_true',
184
+ help='Force configuration even if not pipx or already configured'
185
+ )
186
+
187
+ parser.add_argument(
188
+ '--dry-run',
189
+ action='store_true',
190
+ help='Show what would be done without making changes'
191
+ )
192
+
193
+ parser.add_argument(
194
+ '--quiet',
195
+ action='store_true',
196
+ help='Suppress non-essential output'
197
+ )
198
+
199
+ parser.set_defaults(func=configure_mcp_for_pipx)
@@ -132,5 +132,32 @@ def add_agents_subparser(subparsers) -> argparse.ArgumentParser:
132
132
  default=3,
133
133
  help="Maximum retry attempts per package (default: 3)",
134
134
  )
135
+
136
+ # Cleanup orphaned agents
137
+ cleanup_orphaned_parser = agents_subparsers.add_parser(
138
+ "cleanup-orphaned",
139
+ help="Clean up orphaned agents that don't have templates"
140
+ )
141
+ cleanup_orphaned_parser.add_argument(
142
+ "--agents-dir",
143
+ type=Path,
144
+ help="Directory containing deployed agents (default: .claude/agents/)",
145
+ )
146
+ cleanup_orphaned_parser.add_argument(
147
+ "--dry-run",
148
+ action="store_true",
149
+ default=True,
150
+ help="Only show what would be removed without actually removing (default)",
151
+ )
152
+ cleanup_orphaned_parser.add_argument(
153
+ "--force",
154
+ action="store_true",
155
+ help="Actually remove orphaned agents (disables dry-run)",
156
+ )
157
+ cleanup_orphaned_parser.add_argument(
158
+ "--quiet",
159
+ action="store_true",
160
+ help="Only show summary, not individual agents",
161
+ )
135
162
 
136
163
  return agents_parser
@@ -338,6 +338,12 @@ def create_parser(
338
338
  from ..commands.cleanup import add_cleanup_parser
339
339
 
340
340
  add_cleanup_parser(subparsers)
341
+
342
+ # MCP pipx configuration command
343
+ if hasattr(CLICommands, "MCP_PIPX_CONFIG") or True: # Always add for now
344
+ from ..commands.mcp_pipx_config import add_parser as add_mcp_pipx_parser
345
+
346
+ add_mcp_pipx_parser(subparsers)
341
347
 
342
348
  from ..commands.doctor import add_doctor_parser
343
349
 
@@ -66,8 +66,10 @@ class StartupStatusLogger:
66
66
  self.logger.info(f"MCP Server: {config_status['servers_count']} server(s) configured")
67
67
  else:
68
68
  self.logger.info("MCP Server: No servers configured")
69
+ self._log_mcp_setup_hint()
69
70
  else:
70
71
  self.logger.info("MCP Server: No configuration found in ~/.claude.json")
72
+ self._log_mcp_setup_hint()
71
73
 
72
74
  # Check for claude-mpm MCP gateway status
73
75
  gateway_status = self._check_mcp_gateway_status()
@@ -75,6 +77,9 @@ class StartupStatusLogger:
75
77
  self.logger.info("MCP Gateway: Claude MPM gateway configured")
76
78
  else:
77
79
  self.logger.info("MCP Gateway: Claude MPM gateway not configured")
80
+ # Check if this is a pipx installation that could benefit from auto-config
81
+ if self._is_pipx_installation() and not self._has_auto_config_preference():
82
+ self.logger.info("MCP Gateway: Auto-configuration available for pipx users")
78
83
 
79
84
  except Exception as e:
80
85
  self.logger.warning(f"MCP Server: Status check failed - {e}")
@@ -293,6 +298,76 @@ class StartupStatusLogger:
293
298
  result["error"] = str(e)
294
299
 
295
300
  return result
301
+
302
+ def _is_pipx_installation(self) -> bool:
303
+ """Check if this is a pipx installation."""
304
+ try:
305
+ # Check if running from pipx
306
+ if "pipx" in sys.executable.lower():
307
+ return True
308
+
309
+ # Check module path
310
+ import claude_mpm
311
+ module_path = Path(claude_mpm.__file__).parent
312
+ if "pipx" in str(module_path):
313
+ return True
314
+ except Exception:
315
+ pass
316
+
317
+ return False
318
+
319
+ def _has_auto_config_preference(self) -> bool:
320
+ """Check if user has already been asked about auto-configuration."""
321
+ try:
322
+ from ..config.paths import paths
323
+ preference_file = paths.claude_mpm_dir_hidden / "mcp_auto_config_preference.json"
324
+ return preference_file.exists()
325
+ except Exception:
326
+ return False
327
+
328
+ def _log_mcp_setup_hint(self) -> None:
329
+ """Log helpful hints for MCP setup."""
330
+ # Check if installed via pipx
331
+ is_pipx = self._check_pipx_installation()
332
+
333
+ if is_pipx:
334
+ self.logger.info("💡 TIP: It looks like you installed claude-mpm via pipx")
335
+ self.logger.info(" To configure MCP for Claude Code with pipx:")
336
+ self.logger.info(" 1. Run: python3 scripts/configure_mcp_pipx.py")
337
+ self.logger.info(" 2. Or see: docs/MCP_PIPX_SETUP.md for manual setup")
338
+ self.logger.info(" 3. Restart Claude Code after configuration")
339
+ else:
340
+ self.logger.info("💡 TIP: To enable MCP integration with Claude Code:")
341
+ self.logger.info(" 1. See docs/MCP_SETUP.md for setup instructions")
342
+ self.logger.info(" 2. Run: claude-mpm doctor --check mcp to verify")
343
+ self.logger.info(" 3. Restart Claude Code after configuration")
344
+
345
+ def _check_pipx_installation(self) -> bool:
346
+ """Check if claude-mpm was installed via pipx."""
347
+ try:
348
+ # Check if running from a pipx venv
349
+ if "pipx" in sys.executable.lower():
350
+ return True
351
+
352
+ # Check if claude-mpm-mcp command exists and is from pipx
353
+ mcp_cmd = shutil.which("claude-mpm-mcp")
354
+ if mcp_cmd and "pipx" in mcp_cmd.lower():
355
+ return True
356
+
357
+ # Try to check pipx list
358
+ result = subprocess.run(
359
+ ["pipx", "list"],
360
+ capture_output=True,
361
+ text=True,
362
+ timeout=2
363
+ )
364
+ if result.returncode == 0 and "claude-mpm" in result.stdout:
365
+ return True
366
+
367
+ except Exception:
368
+ pass
369
+
370
+ return False
296
371
 
297
372
 
298
373
  def setup_startup_logging(project_root: Optional[Path] = None) -> Path: