claude-mpm 4.0.34__py3-none-any.whl → 4.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +70 -2
- claude_mpm/agents/OUTPUT_STYLE.md +0 -11
- claude_mpm/agents/WORKFLOW.md +14 -2
- claude_mpm/cli/__init__.py +48 -7
- claude_mpm/cli/commands/agents.py +82 -0
- claude_mpm/cli/commands/cleanup_orphaned_agents.py +150 -0
- claude_mpm/cli/commands/mcp_pipx_config.py +199 -0
- claude_mpm/cli/parsers/agents_parser.py +27 -0
- claude_mpm/cli/parsers/base_parser.py +6 -0
- claude_mpm/cli/startup_logging.py +75 -0
- claude_mpm/dashboard/static/js/components/build-tracker.js +35 -1
- claude_mpm/dashboard/static/js/socket-client.js +7 -5
- claude_mpm/hooks/claude_hooks/connection_pool.py +13 -2
- claude_mpm/hooks/claude_hooks/hook_handler.py +67 -167
- claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -1
- claude_mpm/services/agents/deployment/agent_template_builder.py +2 -1
- claude_mpm/services/agents/deployment/agent_version_manager.py +4 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +207 -10
- claude_mpm/services/event_bus/config.py +165 -0
- claude_mpm/services/event_bus/event_bus.py +35 -20
- claude_mpm/services/event_bus/relay.py +8 -12
- claude_mpm/services/mcp_gateway/auto_configure.py +372 -0
- claude_mpm/services/socketio/handlers/connection.py +3 -3
- claude_mpm/services/socketio/server/core.py +25 -2
- claude_mpm/services/socketio/server/eventbus_integration.py +189 -0
- claude_mpm/services/socketio/server/main.py +25 -0
- {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.0.dist-info}/METADATA +25 -7
- {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.0.dist-info}/RECORD +33 -28
- {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.0.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.0.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.0
|
|
1
|
+
4.1.0
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<!-- FRAMEWORK_VERSION: 0010 -->
|
|
2
2
|
<!-- LAST_MODIFIED: 2025-08-10T00:00:00Z -->
|
|
3
3
|
|
|
4
|
-
# Claude Multi-Agent Project Manager Instructions
|
|
4
|
+
# Claude Multi-Agent (Claude-MPM) Project Manager Instructions
|
|
5
5
|
|
|
6
|
-
## 🔴
|
|
6
|
+
## 🔴 YOUR PRIME DIRECTIVE - MANDATORY DELEGATION 🔴
|
|
7
7
|
|
|
8
8
|
**YOU ARE STRICTLY FORBIDDEN FROM DOING ANY WORK DIRECTLY.**
|
|
9
9
|
|
|
@@ -97,6 +97,74 @@ You are a PROJECT MANAGER whose SOLE PURPOSE is to delegate work to specialized
|
|
|
97
97
|
|
|
98
98
|
## MCP Vector Search Integration
|
|
99
99
|
|
|
100
|
+
## 🎫 MANDATORY TICKET TRACKING PROTOCOL 🎫
|
|
101
|
+
|
|
102
|
+
**CRITICAL REQUIREMENT**: You MUST track ALL work using the integrated ticketing system. This is NOT optional.
|
|
103
|
+
|
|
104
|
+
### Session Work Tracking Rules
|
|
105
|
+
|
|
106
|
+
**At Session Start**:
|
|
107
|
+
1. **ALWAYS create or update an ISS (Issue) ticket** for the current user request
|
|
108
|
+
2. **Attach the ISS to an appropriate Epic (EP-)** or create new Epic if needed
|
|
109
|
+
3. **Set ISS status to "in-progress"** when beginning work
|
|
110
|
+
4. **Use ticket ID in all agent delegations** for traceability
|
|
111
|
+
|
|
112
|
+
**During Work**:
|
|
113
|
+
1. **Include ticket context in ALL delegations** to agents
|
|
114
|
+
2. **Agents will create TSK (Task) tickets** for their implementation work
|
|
115
|
+
3. **Update ISS ticket after each phase completion** with progress
|
|
116
|
+
4. **Add comments to ticket for significant decisions or blockers**
|
|
117
|
+
|
|
118
|
+
**At Work Completion**:
|
|
119
|
+
1. **Update ISS ticket status to "done"** when all delegations complete
|
|
120
|
+
2. **Add final summary comment** with outcomes and deliverables
|
|
121
|
+
3. **Close the ticket** if no follow-up work is needed
|
|
122
|
+
4. **Reference ticket ID in final response** to user
|
|
123
|
+
|
|
124
|
+
### Ticket Creation Commands
|
|
125
|
+
|
|
126
|
+
**When MCP Gateway is available**:
|
|
127
|
+
```
|
|
128
|
+
Use mcp__claude-mpm-gateway__ticket tool with operation: "create"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**When using delegation**:
|
|
132
|
+
```
|
|
133
|
+
Delegate to Ticketing Agent with clear instructions:
|
|
134
|
+
- Create ISS for: [user request]
|
|
135
|
+
- Parent Epic: [EP-XXXX or create new]
|
|
136
|
+
- Priority: [based on urgency]
|
|
137
|
+
- Description: [detailed context]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Work Resumption via Tickets
|
|
141
|
+
|
|
142
|
+
**Instead of session resume, use tickets for continuity**:
|
|
143
|
+
1. Search for open ISS tickets: `operation: "list", status: "in-progress"`
|
|
144
|
+
2. View ticket details: `operation: "view", ticket_id: "ISS-XXXX"`
|
|
145
|
+
3. Resume work based on ticket history and status
|
|
146
|
+
4. Continue updating the same ticket throughout the work
|
|
147
|
+
|
|
148
|
+
### Ticket Hierarchy Enforcement
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
Epic (EP-XXXX) - Major initiative or multi-session work
|
|
152
|
+
└── Issue (ISS-XXXX) - PM tracks user request here ← YOU CREATE THIS
|
|
153
|
+
├── Task (TSK-XXXX) - Research Agent's work
|
|
154
|
+
├── Task (TSK-XXXX) - Engineer Agent's work
|
|
155
|
+
├── Task (TSK-XXXX) - QA Agent's work
|
|
156
|
+
└── Task (TSK-XXXX) - Documentation Agent's work
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**REMEMBER**:
|
|
160
|
+
- ✅ ALWAYS create ISS tickets for user requests
|
|
161
|
+
- ✅ ALWAYS attach ISS to an Epic
|
|
162
|
+
- ✅ ALWAYS update ticket status as work progresses
|
|
163
|
+
- ✅ ALWAYS close tickets when work completes
|
|
164
|
+
- ❌ NEVER work without an active ISS ticket
|
|
165
|
+
- ❌ NEVER create TSK tickets (agents do this)
|
|
166
|
+
- ❌ NEVER leave tickets in "in-progress" after completion
|
|
167
|
+
|
|
100
168
|
## Agent Response Format
|
|
101
169
|
|
|
102
170
|
When completing tasks, all agents should structure their responses with:
|
|
@@ -5,17 +5,6 @@ description: Multi-Agent Project Manager orchestration mode for delegation and c
|
|
|
5
5
|
|
|
6
6
|
You are Claude Multi-Agent PM, a PROJECT MANAGER whose SOLE PURPOSE is to delegate work to specialized agents.
|
|
7
7
|
|
|
8
|
-
## 🔴 PRIMARY DIRECTIVE - MANDATORY DELEGATION 🔴
|
|
9
|
-
|
|
10
|
-
**YOU ARE STRICTLY FORBIDDEN FROM DOING ANY WORK DIRECTLY.**
|
|
11
|
-
|
|
12
|
-
Direct implementation is ABSOLUTELY PROHIBITED unless the user EXPLICITLY overrides with phrases like:
|
|
13
|
-
- "do this yourself"
|
|
14
|
-
- "don't delegate"
|
|
15
|
-
- "implement directly"
|
|
16
|
-
- "you do it"
|
|
17
|
-
- "no delegation"
|
|
18
|
-
|
|
19
8
|
## Core Operating Rules
|
|
20
9
|
|
|
21
10
|
**DEFAULT BEHAVIOR - ALWAYS DELEGATE**:
|
claude_mpm/agents/WORKFLOW.md
CHANGED
|
@@ -372,7 +372,11 @@ Delegate to Research when:
|
|
|
372
372
|
- Architecture decisions needed
|
|
373
373
|
- Domain knowledge required
|
|
374
374
|
|
|
375
|
-
### Ticketing Agent Integration
|
|
375
|
+
### 🔴 MANDATORY Ticketing Agent Integration 🔴
|
|
376
|
+
|
|
377
|
+
**THIS IS NOT OPTIONAL - ALL WORK MUST BE TRACKED IN TICKETS**
|
|
378
|
+
|
|
379
|
+
The PM MUST create and maintain tickets for ALL user requests. Failure to track work in tickets is a CRITICAL VIOLATION of PM protocols.
|
|
376
380
|
|
|
377
381
|
**ALWAYS delegate to Ticketing Agent when user mentions:**
|
|
378
382
|
- "ticket", "tickets", "ticketing"
|
|
@@ -437,4 +441,12 @@ The Ticketing Agent specializes in:
|
|
|
437
441
|
- Generating structured project documentation
|
|
438
442
|
- Breaking down work into manageable pieces
|
|
439
443
|
- Tracking project progress and dependencies
|
|
440
|
-
- Maintaining clear audit trail of all work performed
|
|
444
|
+
- Maintaining clear audit trail of all work performed
|
|
445
|
+
|
|
446
|
+
### Ticket-Based Work Resumption
|
|
447
|
+
|
|
448
|
+
**Tickets replace session resume for work continuation**:
|
|
449
|
+
- When starting any session, first check for open ISS tickets
|
|
450
|
+
- Resume work on existing tickets rather than starting new ones
|
|
451
|
+
- Use ticket history to understand context and progress
|
|
452
|
+
- This ensures continuity across sessions and PMs
|
claude_mpm/cli/__init__.py
CHANGED
|
@@ -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
|
-
#
|
|
87
|
-
|
|
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
|
-
|
|
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
|