claude-mpm 4.0.19__py3-none-any.whl → 4.0.22__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/BUILD_NUMBER +1 -1
- claude_mpm/VERSION +1 -1
- claude_mpm/__main__.py +4 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +38 -2
- claude_mpm/agents/INSTRUCTIONS.md +74 -0
- claude_mpm/agents/OUTPUT_STYLE.md +84 -0
- claude_mpm/agents/WORKFLOW.md +308 -4
- claude_mpm/agents/agents_metadata.py +52 -0
- claude_mpm/agents/base_agent_loader.py +75 -19
- claude_mpm/agents/templates/__init__.py +4 -0
- claude_mpm/agents/templates/api_qa.json +206 -0
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +24 -16
- claude_mpm/agents/templates/ticketing.json +18 -5
- claude_mpm/agents/templates/vercel_ops_agent.json +281 -0
- claude_mpm/agents/templates/vercel_ops_instructions.md +582 -0
- claude_mpm/cli/__init__.py +23 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/commands/mcp_command_router.py +87 -1
- claude_mpm/cli/commands/mcp_install_commands.py +207 -26
- claude_mpm/cli/commands/memory.py +32 -5
- claude_mpm/cli/commands/run.py +33 -6
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/parsers/mcp_parser.py +23 -0
- claude_mpm/cli/parsers/run_parser.py +5 -0
- claude_mpm/cli/utils.py +17 -4
- claude_mpm/constants.py +1 -0
- claude_mpm/core/base_service.py +8 -2
- claude_mpm/core/config.py +122 -32
- claude_mpm/core/framework_loader.py +385 -34
- claude_mpm/core/interactive_session.py +77 -12
- claude_mpm/core/oneshot_session.py +7 -1
- claude_mpm/core/output_style_manager.py +468 -0
- claude_mpm/core/unified_paths.py +190 -21
- claude_mpm/hooks/claude_hooks/hook_handler.py +91 -16
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +3 -0
- claude_mpm/init.py +1 -0
- claude_mpm/scripts/socketio_daemon.py +67 -7
- claude_mpm/scripts/socketio_daemon_hardened.py +897 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +216 -10
- claude_mpm/services/agents/deployment/agent_template_builder.py +37 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +65 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +441 -0
- claude_mpm/services/agents/memory/__init__.py +0 -2
- claude_mpm/services/agents/memory/agent_memory_manager.py +577 -44
- claude_mpm/services/agents/memory/content_manager.py +144 -14
- claude_mpm/services/agents/memory/template_generator.py +7 -354
- claude_mpm/services/mcp_gateway/server/stdio_server.py +61 -169
- claude_mpm/services/memory_hook_service.py +62 -4
- claude_mpm/services/runner_configuration_service.py +5 -9
- claude_mpm/services/socketio/server/broadcaster.py +32 -1
- claude_mpm/services/socketio/server/core.py +4 -0
- claude_mpm/services/socketio/server/main.py +23 -4
- claude_mpm/services/subprocess_launcher_service.py +5 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/METADATA +1 -1
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/RECORD +60 -54
- claude_mpm/services/agents/memory/analyzer.py +0 -430
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/top_level.txt +0 -0
|
@@ -5,6 +5,8 @@ Extracted from mcp.py to reduce complexity and improve maintainability.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
8
10
|
from typing import Any
|
|
9
11
|
|
|
10
12
|
from ...constants import MCPCommands
|
|
@@ -43,6 +45,9 @@ class MCPCommandRouter:
|
|
|
43
45
|
elif args.mcp_command == MCPCommands.CONFIG.value:
|
|
44
46
|
return self._manage_config(args)
|
|
45
47
|
|
|
48
|
+
elif args.mcp_command == MCPCommands.SERVER.value:
|
|
49
|
+
return self._run_server(args)
|
|
50
|
+
|
|
46
51
|
elif args.mcp_command == "cleanup":
|
|
47
52
|
return self._cleanup_locks(args)
|
|
48
53
|
|
|
@@ -115,11 +120,91 @@ class MCPCommandRouter:
|
|
|
115
120
|
handler = MCPServerCommands(self.logger)
|
|
116
121
|
return handler.cleanup_locks(args)
|
|
117
122
|
|
|
123
|
+
def _run_server(self, args) -> int:
|
|
124
|
+
"""Run server command handler - direct server execution."""
|
|
125
|
+
try:
|
|
126
|
+
self.logger.info("Starting MCP server directly via CLI command")
|
|
127
|
+
|
|
128
|
+
# Import the server components
|
|
129
|
+
from claude_mpm.services.mcp_gateway.server.stdio_server import SimpleMCPServer
|
|
130
|
+
|
|
131
|
+
# Create server instance
|
|
132
|
+
server = SimpleMCPServer(name="claude-mpm-gateway", version="1.0.0")
|
|
133
|
+
|
|
134
|
+
if args.test:
|
|
135
|
+
self.logger.info("Running in test mode")
|
|
136
|
+
print("🧪 Starting MCP server in test mode...", file=sys.stderr)
|
|
137
|
+
print(" This will run the server with stdio communication.", file=sys.stderr)
|
|
138
|
+
print(" Press Ctrl+C to stop.\n", file=sys.stderr)
|
|
139
|
+
|
|
140
|
+
# Run the server, handling event loop properly
|
|
141
|
+
try:
|
|
142
|
+
# Check if there's already an event loop running
|
|
143
|
+
loop = asyncio.get_running_loop()
|
|
144
|
+
# If we get here, there's already a loop running
|
|
145
|
+
# We need to run in a subprocess to avoid conflicts
|
|
146
|
+
import subprocess
|
|
147
|
+
import json
|
|
148
|
+
|
|
149
|
+
# Create a simple script to run the server
|
|
150
|
+
script_content = f'''
|
|
151
|
+
import asyncio
|
|
152
|
+
import sys
|
|
153
|
+
import os
|
|
154
|
+
sys.path.insert(0, "{os.path.join(os.path.dirname(__file__), '..', '..', '..')}")
|
|
155
|
+
|
|
156
|
+
async def main():
|
|
157
|
+
from claude_mpm.services.mcp_gateway.server.stdio_server import SimpleMCPServer
|
|
158
|
+
server = SimpleMCPServer(name="claude-mpm-gateway", version="1.0.0")
|
|
159
|
+
await server.run()
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
asyncio.run(main())
|
|
163
|
+
'''
|
|
164
|
+
|
|
165
|
+
# Write the script to a temporary file
|
|
166
|
+
import tempfile
|
|
167
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
|
|
168
|
+
f.write(script_content)
|
|
169
|
+
temp_script = f.name
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
# Run the server in a subprocess
|
|
173
|
+
result = subprocess.run([sys.executable, temp_script])
|
|
174
|
+
return result.returncode
|
|
175
|
+
finally:
|
|
176
|
+
# Clean up the temporary file
|
|
177
|
+
os.unlink(temp_script)
|
|
178
|
+
|
|
179
|
+
except RuntimeError:
|
|
180
|
+
# No event loop running, we can use asyncio.run
|
|
181
|
+
async def run_async():
|
|
182
|
+
await server.run()
|
|
183
|
+
|
|
184
|
+
asyncio.run(run_async())
|
|
185
|
+
return 0
|
|
186
|
+
|
|
187
|
+
except ImportError as e:
|
|
188
|
+
self.logger.error(f"Failed to import MCP server: {e}")
|
|
189
|
+
print(f"❌ Error: Could not import MCP server components: {e}", file=sys.stderr)
|
|
190
|
+
print("\nMake sure the MCP package is installed:", file=sys.stderr)
|
|
191
|
+
print(" pip install mcp", file=sys.stderr)
|
|
192
|
+
return 1
|
|
193
|
+
except KeyboardInterrupt:
|
|
194
|
+
self.logger.info("MCP server interrupted")
|
|
195
|
+
print("\n🛑 MCP server stopped", file=sys.stderr)
|
|
196
|
+
return 0
|
|
197
|
+
except Exception as e:
|
|
198
|
+
self.logger.error(f"Server error: {e}")
|
|
199
|
+
print(f"❌ Error running server: {e}", file=sys.stderr)
|
|
200
|
+
return 1
|
|
201
|
+
|
|
118
202
|
def _show_help(self):
|
|
119
203
|
"""Show available MCP commands."""
|
|
120
204
|
print("\nAvailable MCP commands:")
|
|
121
205
|
print(" install - Install and configure MCP Gateway")
|
|
122
206
|
print(" start - Start the MCP Gateway server (stdio mode)")
|
|
207
|
+
print(" server - Run the MCP Gateway server directly")
|
|
123
208
|
print(" stop - Stop the MCP Gateway server")
|
|
124
209
|
print(" status - Show server and tool status")
|
|
125
210
|
print(" tools - List and manage registered tools")
|
|
@@ -132,8 +217,9 @@ class MCPCommandRouter:
|
|
|
132
217
|
print("\nExamples:")
|
|
133
218
|
print(" claude-mpm mcp install")
|
|
134
219
|
print(" claude-mpm mcp start # Run server (for Claude Code)")
|
|
220
|
+
print(" claude-mpm mcp server # Run server directly")
|
|
221
|
+
print(" claude-mpm mcp server --test # Test mode with debug output")
|
|
135
222
|
print(" claude-mpm mcp start --instructions # Show setup instructions")
|
|
136
|
-
print(" claude-mpm mcp start --test # Test mode with debug output")
|
|
137
223
|
print(" claude-mpm mcp tools")
|
|
138
224
|
print(" claude-mpm mcp register my-tool")
|
|
139
225
|
print(" claude-mpm mcp test my-tool")
|
|
@@ -18,17 +18,17 @@ class MCPInstallCommands:
|
|
|
18
18
|
|
|
19
19
|
def install_gateway(self, args):
|
|
20
20
|
"""Install and configure MCP gateway.
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
WHY: This command installs the MCP package dependencies and configures
|
|
23
|
-
Claude Desktop to use the MCP gateway server.
|
|
24
|
-
|
|
23
|
+
Claude Desktop to use the MCP gateway server directly via the CLI command.
|
|
24
|
+
|
|
25
25
|
DESIGN DECISION: We handle both package installation and configuration
|
|
26
|
-
in one command for user convenience.
|
|
26
|
+
in one command for user convenience, using the new direct CLI approach.
|
|
27
27
|
"""
|
|
28
28
|
self.logger.info("MCP gateway installation command called")
|
|
29
29
|
print("📦 Installing and Configuring MCP Gateway")
|
|
30
30
|
print("=" * 50)
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
# Step 1: Install MCP package if needed
|
|
33
33
|
print("\n1️⃣ Checking MCP package installation...")
|
|
34
34
|
try:
|
|
@@ -43,35 +43,216 @@ class MCPInstallCommands:
|
|
|
43
43
|
print(f"❌ Error installing MCP package: {e}")
|
|
44
44
|
print("\nPlease install manually with: pip install mcp")
|
|
45
45
|
return 1
|
|
46
|
-
|
|
47
|
-
# Step 2:
|
|
46
|
+
|
|
47
|
+
# Step 2: Configure Claude Desktop with the new CLI command
|
|
48
48
|
print("\n2️⃣ Configuring Claude Desktop...")
|
|
49
|
-
project_root = Path(__file__).parent.parent.parent.parent.parent
|
|
50
|
-
config_script = project_root / "scripts" / "configure_mcp_server.py"
|
|
51
|
-
|
|
52
|
-
if not config_script.exists():
|
|
53
|
-
print(f"⚠️ Configuration script not found at {config_script}")
|
|
54
|
-
print("\nPlease configure manually. See:")
|
|
55
|
-
print(" claude-mpm mcp start --instructions")
|
|
56
|
-
return 1
|
|
57
|
-
|
|
58
49
|
try:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
cwd=str(project_root)
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
if result.returncode == 0:
|
|
50
|
+
success = self._configure_claude_desktop(args.force)
|
|
51
|
+
if success:
|
|
65
52
|
print("✅ Configuration completed successfully")
|
|
66
53
|
print("\n🎉 MCP Gateway is ready to use!")
|
|
67
54
|
print("\nNext steps:")
|
|
68
55
|
print("1. Restart Claude Desktop")
|
|
69
|
-
print("2.
|
|
56
|
+
print("2. Test the server: claude-mpm mcp server --test")
|
|
57
|
+
print("3. Check status: claude-mpm mcp status")
|
|
70
58
|
return 0
|
|
71
59
|
else:
|
|
72
|
-
print("❌ Configuration
|
|
60
|
+
print("❌ Configuration failed")
|
|
73
61
|
return 1
|
|
74
|
-
|
|
62
|
+
|
|
75
63
|
except Exception as e:
|
|
76
|
-
print(f"❌ Error
|
|
64
|
+
print(f"❌ Error during configuration: {e}")
|
|
77
65
|
return 1
|
|
66
|
+
|
|
67
|
+
def _configure_claude_desktop(self, force=False):
|
|
68
|
+
"""Configure Claude Desktop to use the MCP gateway via CLI command.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
force: Whether to overwrite existing configuration
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
bool: True if configuration was successful
|
|
75
|
+
"""
|
|
76
|
+
import json
|
|
77
|
+
import platform
|
|
78
|
+
from pathlib import Path
|
|
79
|
+
from datetime import datetime
|
|
80
|
+
|
|
81
|
+
# Determine Claude Desktop config path based on platform
|
|
82
|
+
config_path = self._get_claude_config_path()
|
|
83
|
+
if not config_path:
|
|
84
|
+
print("❌ Could not determine Claude Desktop configuration path")
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
print(f" Configuration path: {config_path}")
|
|
88
|
+
|
|
89
|
+
# Load existing configuration or create new one
|
|
90
|
+
config = self._load_or_create_config(config_path, force)
|
|
91
|
+
if config is None:
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
# Configure the claude-mpm-gateway server using the CLI command
|
|
95
|
+
claude_mpm_path = self._find_claude_mpm_executable()
|
|
96
|
+
if not claude_mpm_path:
|
|
97
|
+
print("❌ Could not find claude-mpm executable")
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
mcp_config = {
|
|
101
|
+
"command": claude_mpm_path,
|
|
102
|
+
"args": ["mcp", "server"],
|
|
103
|
+
"env": {
|
|
104
|
+
"PYTHONPATH": str(Path(__file__).parent.parent.parent.parent),
|
|
105
|
+
"MCP_MODE": "production"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Update configuration
|
|
110
|
+
if "mcpServers" not in config:
|
|
111
|
+
config["mcpServers"] = {}
|
|
112
|
+
|
|
113
|
+
config["mcpServers"]["claude-mpm-gateway"] = mcp_config
|
|
114
|
+
|
|
115
|
+
print("\n✅ Configured claude-mpm-gateway server:")
|
|
116
|
+
print(f" Command: {mcp_config['command']}")
|
|
117
|
+
print(f" Args: {mcp_config['args']}")
|
|
118
|
+
print(f" Environment variables: {list(mcp_config['env'].keys())}")
|
|
119
|
+
|
|
120
|
+
# Save configuration
|
|
121
|
+
return self._save_config(config, config_path)
|
|
122
|
+
|
|
123
|
+
def _get_claude_config_path(self):
|
|
124
|
+
"""Get the Claude Desktop configuration file path based on platform.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Path or None: Path to Claude Desktop config file
|
|
128
|
+
"""
|
|
129
|
+
import platform
|
|
130
|
+
from pathlib import Path
|
|
131
|
+
|
|
132
|
+
system = platform.system()
|
|
133
|
+
|
|
134
|
+
if system == "Darwin": # macOS
|
|
135
|
+
return Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json"
|
|
136
|
+
elif system == "Windows":
|
|
137
|
+
return Path.home() / "AppData" / "Roaming" / "Claude" / "claude_desktop_config.json"
|
|
138
|
+
elif system == "Linux":
|
|
139
|
+
return Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
|
|
140
|
+
else:
|
|
141
|
+
print(f"❌ Unsupported platform: {system}")
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
def _find_claude_mpm_executable(self):
|
|
145
|
+
"""Find the claude-mpm executable path.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
str or None: Path to claude-mpm executable
|
|
149
|
+
"""
|
|
150
|
+
import shutil
|
|
151
|
+
import sys
|
|
152
|
+
|
|
153
|
+
# Try to find claude-mpm in PATH
|
|
154
|
+
claude_mpm_path = shutil.which("claude-mpm")
|
|
155
|
+
if claude_mpm_path:
|
|
156
|
+
return claude_mpm_path
|
|
157
|
+
|
|
158
|
+
# If not in PATH, try using python -m claude_mpm
|
|
159
|
+
# This works if claude-mpm is installed in the current Python environment
|
|
160
|
+
try:
|
|
161
|
+
import claude_mpm
|
|
162
|
+
return f"{sys.executable} -m claude_mpm"
|
|
163
|
+
except ImportError:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
# Last resort: try relative to current script
|
|
167
|
+
project_root = Path(__file__).parent.parent.parent.parent.parent
|
|
168
|
+
local_script = project_root / "scripts" / "claude-mpm"
|
|
169
|
+
if local_script.exists():
|
|
170
|
+
return str(local_script)
|
|
171
|
+
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
def _load_or_create_config(self, config_path, force=False):
|
|
175
|
+
"""Load existing configuration or create a new one.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
config_path: Path to configuration file
|
|
179
|
+
force: Whether to overwrite existing configuration
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
dict or None: Configuration dictionary
|
|
183
|
+
"""
|
|
184
|
+
import json
|
|
185
|
+
from datetime import datetime
|
|
186
|
+
|
|
187
|
+
config = {}
|
|
188
|
+
|
|
189
|
+
if config_path.exists():
|
|
190
|
+
if not force:
|
|
191
|
+
# Check if claude-mpm-gateway already exists
|
|
192
|
+
try:
|
|
193
|
+
with open(config_path, 'r') as f:
|
|
194
|
+
existing_config = json.load(f)
|
|
195
|
+
|
|
196
|
+
if (existing_config.get("mcpServers", {}).get("claude-mpm-gateway") and
|
|
197
|
+
not force):
|
|
198
|
+
print("⚠️ claude-mpm-gateway is already configured")
|
|
199
|
+
response = input("Do you want to overwrite it? (y/N): ").strip().lower()
|
|
200
|
+
if response not in ['y', 'yes']:
|
|
201
|
+
print("❌ Configuration cancelled")
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
config = existing_config
|
|
205
|
+
|
|
206
|
+
except (json.JSONDecodeError, IOError) as e:
|
|
207
|
+
print(f"⚠️ Error reading existing config: {e}")
|
|
208
|
+
print("Creating backup and starting fresh...")
|
|
209
|
+
|
|
210
|
+
# Create backup
|
|
211
|
+
backup_path = config_path.with_suffix(f'.backup.{datetime.now().strftime("%Y%m%d_%H%M%S")}.json')
|
|
212
|
+
try:
|
|
213
|
+
config_path.rename(backup_path)
|
|
214
|
+
print(f" Backup created: {backup_path}")
|
|
215
|
+
except Exception as backup_error:
|
|
216
|
+
print(f" Warning: Could not create backup: {backup_error}")
|
|
217
|
+
else:
|
|
218
|
+
# Force mode - create backup but proceed
|
|
219
|
+
try:
|
|
220
|
+
with open(config_path, 'r') as f:
|
|
221
|
+
existing_config = json.load(f)
|
|
222
|
+
config = existing_config
|
|
223
|
+
print(" Force mode: Overwriting existing configuration")
|
|
224
|
+
except:
|
|
225
|
+
pass # File doesn't exist or is invalid, start fresh
|
|
226
|
+
|
|
227
|
+
# Ensure mcpServers section exists
|
|
228
|
+
if "mcpServers" not in config:
|
|
229
|
+
config["mcpServers"] = {}
|
|
230
|
+
|
|
231
|
+
return config
|
|
232
|
+
|
|
233
|
+
def _save_config(self, config, config_path):
|
|
234
|
+
"""Save configuration to file.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
config: Configuration dictionary
|
|
238
|
+
config_path: Path to save configuration
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
bool: True if successful
|
|
242
|
+
"""
|
|
243
|
+
import json
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
# Ensure directory exists
|
|
247
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
248
|
+
|
|
249
|
+
# Write configuration with nice formatting
|
|
250
|
+
with open(config_path, 'w') as f:
|
|
251
|
+
json.dump(config, f, indent=2)
|
|
252
|
+
|
|
253
|
+
print(f"\n✅ Configuration saved to {config_path}")
|
|
254
|
+
return True
|
|
255
|
+
|
|
256
|
+
except Exception as e:
|
|
257
|
+
print(f"❌ Error saving configuration: {e}")
|
|
258
|
+
return False
|
|
@@ -280,7 +280,12 @@ def _show_basic_status(memory_manager):
|
|
|
280
280
|
print(f" Expected location: {memory_dir}")
|
|
281
281
|
return
|
|
282
282
|
|
|
283
|
-
|
|
283
|
+
# Support both old and new formats
|
|
284
|
+
memory_files = list(memory_dir.glob("*_memories.md"))
|
|
285
|
+
# Also check for old formats for backward compatibility
|
|
286
|
+
memory_files.extend(memory_dir.glob("*_agent.md"))
|
|
287
|
+
memory_files.extend([f for f in memory_dir.glob("*.md")
|
|
288
|
+
if f.name != "README.md" and not f.name.endswith("_memories.md") and not f.name.endswith("_agent.md")])
|
|
284
289
|
|
|
285
290
|
if not memory_files:
|
|
286
291
|
print("📭 No memory files found")
|
|
@@ -296,7 +301,13 @@ def _show_basic_status(memory_manager):
|
|
|
296
301
|
size_kb = stat.st_size / 1024
|
|
297
302
|
total_size += stat.st_size
|
|
298
303
|
|
|
299
|
-
|
|
304
|
+
# Extract agent name from various formats
|
|
305
|
+
if file_path.name.endswith("_memories.md"):
|
|
306
|
+
agent_id = file_path.stem[:-9] # Remove "_memories"
|
|
307
|
+
elif file_path.name.endswith("_agent.md"):
|
|
308
|
+
agent_id = file_path.stem[:-6] # Remove "_agent"
|
|
309
|
+
else:
|
|
310
|
+
agent_id = file_path.stem
|
|
300
311
|
print(f" {agent_id}: {size_kb:.1f} KB")
|
|
301
312
|
|
|
302
313
|
print(f"💾 Total size: {total_size / 1024:.1f} KB")
|
|
@@ -395,7 +406,12 @@ def _clean_memory(args, memory_manager):
|
|
|
395
406
|
print("📁 No memory directory found - nothing to clean")
|
|
396
407
|
return
|
|
397
408
|
|
|
398
|
-
|
|
409
|
+
# Support both old and new formats
|
|
410
|
+
memory_files = list(memory_dir.glob("*_memories.md"))
|
|
411
|
+
# Also check for old formats for backward compatibility
|
|
412
|
+
memory_files.extend(memory_dir.glob("*_agent.md"))
|
|
413
|
+
memory_files.extend([f for f in memory_dir.glob("*.md")
|
|
414
|
+
if f.name != "README.md" and not f.name.endswith("_memories.md") and not f.name.endswith("_agent.md")])
|
|
399
415
|
if not memory_files:
|
|
400
416
|
print("📭 No memory files found - nothing to clean")
|
|
401
417
|
return
|
|
@@ -651,7 +667,12 @@ def _show_all_agent_memories(format_type, memory_manager):
|
|
|
651
667
|
print("📁 No memory directory found")
|
|
652
668
|
return
|
|
653
669
|
|
|
654
|
-
|
|
670
|
+
# Support both old and new formats
|
|
671
|
+
memory_files = list(memory_dir.glob("*_memories.md"))
|
|
672
|
+
# Also check for old formats for backward compatibility
|
|
673
|
+
memory_files.extend(memory_dir.glob("*_agent.md"))
|
|
674
|
+
memory_files.extend([f for f in memory_dir.glob("*.md")
|
|
675
|
+
if f.name != "README.md" and not f.name.endswith("_memories.md") and not f.name.endswith("_agent.md")])
|
|
655
676
|
if not memory_files:
|
|
656
677
|
print("📭 No agent memories found")
|
|
657
678
|
return
|
|
@@ -664,7 +685,13 @@ def _show_all_agent_memories(format_type, memory_manager):
|
|
|
664
685
|
|
|
665
686
|
# Load all agent memories
|
|
666
687
|
for file_path in sorted(memory_files):
|
|
667
|
-
|
|
688
|
+
# Extract agent name from various formats
|
|
689
|
+
if file_path.name.endswith("_memories.md"):
|
|
690
|
+
agent_id = file_path.stem[:-9] # Remove "_memories"
|
|
691
|
+
elif file_path.name.endswith("_agent.md"):
|
|
692
|
+
agent_id = file_path.stem[:-6] # Remove "_agent"
|
|
693
|
+
else:
|
|
694
|
+
agent_id = file_path.stem
|
|
668
695
|
try:
|
|
669
696
|
memory_content = memory_manager.load_agent_memory(agent_id)
|
|
670
697
|
if memory_content:
|
claude_mpm/cli/commands/run.py
CHANGED
|
@@ -33,7 +33,8 @@ def filter_claude_mpm_args(claude_args):
|
|
|
33
33
|
flags and will error if they're passed through.
|
|
34
34
|
|
|
35
35
|
DESIGN DECISION: We maintain a list of known claude-mpm flags to filter out,
|
|
36
|
-
ensuring only genuine Claude CLI arguments are passed through.
|
|
36
|
+
ensuring only genuine Claude CLI arguments are passed through. We also remove
|
|
37
|
+
the '--' separator that argparse uses, as it's not needed by Claude CLI.
|
|
37
38
|
|
|
38
39
|
Args:
|
|
39
40
|
claude_args: List of arguments captured by argparse.REMAINDER
|
|
@@ -83,6 +84,11 @@ def filter_claude_mpm_args(claude_args):
|
|
|
83
84
|
while i < len(claude_args):
|
|
84
85
|
arg = claude_args[i]
|
|
85
86
|
|
|
87
|
+
# Skip the '--' separator used by argparse - Claude doesn't need it
|
|
88
|
+
if arg == "--":
|
|
89
|
+
i += 1
|
|
90
|
+
continue
|
|
91
|
+
|
|
86
92
|
# Check if this is a claude-mpm flag
|
|
87
93
|
if arg in mpm_flags:
|
|
88
94
|
# Skip this flag
|
|
@@ -379,16 +385,37 @@ def run_session(args):
|
|
|
379
385
|
# Create simple runner
|
|
380
386
|
enable_tickets = not args.no_tickets
|
|
381
387
|
raw_claude_args = getattr(args, "claude_args", []) or []
|
|
388
|
+
|
|
389
|
+
# Add --resume to claude_args if the flag is set
|
|
390
|
+
resume_flag_present = getattr(args, "resume", False)
|
|
391
|
+
if resume_flag_present:
|
|
392
|
+
logger.info("📌 --resume flag detected in args")
|
|
393
|
+
if "--resume" not in raw_claude_args:
|
|
394
|
+
raw_claude_args = ["--resume"] + raw_claude_args
|
|
395
|
+
logger.info("✅ Added --resume to claude_args")
|
|
396
|
+
else:
|
|
397
|
+
logger.info("ℹ️ --resume already in claude_args")
|
|
398
|
+
|
|
382
399
|
# Filter out claude-mpm specific flags before passing to Claude CLI
|
|
400
|
+
logger.debug(f"Pre-filter claude_args: {raw_claude_args}")
|
|
383
401
|
claude_args = filter_claude_mpm_args(raw_claude_args)
|
|
384
402
|
monitor_mode = getattr(args, "monitor", False)
|
|
385
403
|
|
|
386
|
-
#
|
|
404
|
+
# Enhanced debug logging for argument filtering
|
|
387
405
|
if raw_claude_args != claude_args:
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
406
|
+
filtered_out = list(set(raw_claude_args) - set(claude_args))
|
|
407
|
+
logger.debug(f"Filtered out MPM-specific args: {filtered_out}")
|
|
408
|
+
|
|
409
|
+
logger.info(f"Final claude_args being passed: {claude_args}")
|
|
410
|
+
|
|
411
|
+
# Explicit verification of --resume flag
|
|
412
|
+
if resume_flag_present:
|
|
413
|
+
if "--resume" in claude_args:
|
|
414
|
+
logger.info("✅ CONFIRMED: --resume flag will be passed to Claude CLI")
|
|
415
|
+
else:
|
|
416
|
+
logger.error("❌ WARNING: --resume flag was filtered out! This is a bug!")
|
|
417
|
+
logger.error(f" Original args: {raw_claude_args}")
|
|
418
|
+
logger.error(f" Filtered args: {claude_args}")
|
|
392
419
|
|
|
393
420
|
# Use the specified launch method (default: exec)
|
|
394
421
|
launch_method = getattr(args, "launch_method", "exec")
|
|
@@ -187,6 +187,11 @@ def add_top_level_run_arguments(parser: argparse.ArgumentParser) -> None:
|
|
|
187
187
|
const="last",
|
|
188
188
|
help="Resume an MPM session (last session if no ID specified, or specific session ID)",
|
|
189
189
|
)
|
|
190
|
+
run_group.add_argument(
|
|
191
|
+
"--resume",
|
|
192
|
+
action="store_true",
|
|
193
|
+
help="Pass --resume flag to Claude Desktop to resume the last conversation",
|
|
194
|
+
)
|
|
190
195
|
run_group.add_argument(
|
|
191
196
|
"--force",
|
|
192
197
|
action="store_true",
|
|
@@ -149,4 +149,27 @@ def add_mcp_subparser(subparsers) -> argparse.ArgumentParser:
|
|
|
149
149
|
help="Configuration action (default: view)",
|
|
150
150
|
)
|
|
151
151
|
|
|
152
|
+
# MCP Server command (direct server execution)
|
|
153
|
+
server_mcp_parser = mcp_subparsers.add_parser(
|
|
154
|
+
MCPCommands.SERVER.value, help="Run the MCP Gateway server directly"
|
|
155
|
+
)
|
|
156
|
+
server_mcp_parser.add_argument(
|
|
157
|
+
"--mode",
|
|
158
|
+
choices=["stdio", "standalone"],
|
|
159
|
+
default="stdio",
|
|
160
|
+
help="Server mode: stdio for Claude integration, standalone for testing (default: stdio)",
|
|
161
|
+
)
|
|
162
|
+
server_mcp_parser.add_argument(
|
|
163
|
+
"--port",
|
|
164
|
+
type=int,
|
|
165
|
+
default=8766,
|
|
166
|
+
help="Port for standalone mode (default: 8766)",
|
|
167
|
+
)
|
|
168
|
+
server_mcp_parser.add_argument(
|
|
169
|
+
"--config-file", type=Path, help="Path to MCP configuration file"
|
|
170
|
+
)
|
|
171
|
+
server_mcp_parser.add_argument(
|
|
172
|
+
"--test", action="store_true", help="Run in test mode with debug output"
|
|
173
|
+
)
|
|
174
|
+
|
|
152
175
|
return mcp_parser
|
|
@@ -75,6 +75,11 @@ def add_run_arguments(parser: argparse.ArgumentParser) -> None:
|
|
|
75
75
|
const="last",
|
|
76
76
|
help="Resume an MPM session (last session if no ID specified, or specific session ID)",
|
|
77
77
|
)
|
|
78
|
+
run_group.add_argument(
|
|
79
|
+
"--resume",
|
|
80
|
+
action="store_true",
|
|
81
|
+
help="Pass --resume flag to Claude Desktop to resume the last conversation",
|
|
82
|
+
)
|
|
78
83
|
|
|
79
84
|
# Dependency checking options
|
|
80
85
|
dep_group = parser.add_argument_group("dependency options")
|
claude_mpm/cli/utils.py
CHANGED
|
@@ -132,11 +132,24 @@ def list_agent_versions_at_startup() -> None:
|
|
|
132
132
|
|
|
133
133
|
WHY: Users want to see what agents are available when they start a session.
|
|
134
134
|
This provides immediate feedback about the deployed agent environment.
|
|
135
|
+
|
|
136
|
+
DESIGN DECISION: We suppress INFO logging during this call to avoid duplicate
|
|
137
|
+
initialization messages since the deployment service will be initialized again
|
|
138
|
+
later in the ClaudeRunner.
|
|
135
139
|
"""
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
+
# Temporarily suppress INFO level logging to avoid duplicate initialization messages
|
|
141
|
+
import logging
|
|
142
|
+
original_level = logging.getLogger("claude_mpm").level
|
|
143
|
+
logging.getLogger("claude_mpm").setLevel(logging.WARNING)
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
agent_versions = get_agent_versions_display()
|
|
147
|
+
if agent_versions:
|
|
148
|
+
print(agent_versions)
|
|
149
|
+
print() # Extra newline after the display
|
|
150
|
+
finally:
|
|
151
|
+
# Restore original logging level
|
|
152
|
+
logging.getLogger("claude_mpm").setLevel(original_level)
|
|
140
153
|
|
|
141
154
|
|
|
142
155
|
def setup_logging(args) -> object:
|
claude_mpm/constants.py
CHANGED
claude_mpm/core/base_service.py
CHANGED
|
@@ -120,7 +120,13 @@ class BaseService(LoggerMixin, ABC):
|
|
|
120
120
|
container: Optional service container for dependency injection
|
|
121
121
|
"""
|
|
122
122
|
self.name = name
|
|
123
|
-
|
|
123
|
+
# Use singleton Config instance to prevent duplicate configuration loading
|
|
124
|
+
# Only pass config_path if it's different from what might already be loaded
|
|
125
|
+
if config_path:
|
|
126
|
+
self.config = Config(config or {}, config_path)
|
|
127
|
+
else:
|
|
128
|
+
# Use existing singleton instance without forcing reload
|
|
129
|
+
self.config = Config(config or {})
|
|
124
130
|
self._enable_enhanced = enable_enhanced_features
|
|
125
131
|
self._container = container
|
|
126
132
|
|
|
@@ -159,7 +165,7 @@ class BaseService(LoggerMixin, ABC):
|
|
|
159
165
|
|
|
160
166
|
# Only log if not in quiet mode
|
|
161
167
|
if not os.environ.get("CLAUDE_PM_QUIET_MODE", "").lower() == "true":
|
|
162
|
-
self.logger.
|
|
168
|
+
self.logger.debug(f"Initialized {self.name} service")
|
|
163
169
|
|
|
164
170
|
def _init_enhanced_features(self):
|
|
165
171
|
"""Initialize enhanced features when enabled."""
|