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.
Files changed (61) hide show
  1. claude_mpm/BUILD_NUMBER +1 -1
  2. claude_mpm/VERSION +1 -1
  3. claude_mpm/__main__.py +4 -0
  4. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +38 -2
  5. claude_mpm/agents/INSTRUCTIONS.md +74 -0
  6. claude_mpm/agents/OUTPUT_STYLE.md +84 -0
  7. claude_mpm/agents/WORKFLOW.md +308 -4
  8. claude_mpm/agents/agents_metadata.py +52 -0
  9. claude_mpm/agents/base_agent_loader.py +75 -19
  10. claude_mpm/agents/templates/__init__.py +4 -0
  11. claude_mpm/agents/templates/api_qa.json +206 -0
  12. claude_mpm/agents/templates/qa.json +1 -1
  13. claude_mpm/agents/templates/research.json +24 -16
  14. claude_mpm/agents/templates/ticketing.json +18 -5
  15. claude_mpm/agents/templates/vercel_ops_agent.json +281 -0
  16. claude_mpm/agents/templates/vercel_ops_instructions.md +582 -0
  17. claude_mpm/cli/__init__.py +23 -1
  18. claude_mpm/cli/__main__.py +4 -0
  19. claude_mpm/cli/commands/mcp_command_router.py +87 -1
  20. claude_mpm/cli/commands/mcp_install_commands.py +207 -26
  21. claude_mpm/cli/commands/memory.py +32 -5
  22. claude_mpm/cli/commands/run.py +33 -6
  23. claude_mpm/cli/parsers/base_parser.py +5 -0
  24. claude_mpm/cli/parsers/mcp_parser.py +23 -0
  25. claude_mpm/cli/parsers/run_parser.py +5 -0
  26. claude_mpm/cli/utils.py +17 -4
  27. claude_mpm/constants.py +1 -0
  28. claude_mpm/core/base_service.py +8 -2
  29. claude_mpm/core/config.py +122 -32
  30. claude_mpm/core/framework_loader.py +385 -34
  31. claude_mpm/core/interactive_session.py +77 -12
  32. claude_mpm/core/oneshot_session.py +7 -1
  33. claude_mpm/core/output_style_manager.py +468 -0
  34. claude_mpm/core/unified_paths.py +190 -21
  35. claude_mpm/hooks/claude_hooks/hook_handler.py +91 -16
  36. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +3 -0
  37. claude_mpm/init.py +1 -0
  38. claude_mpm/scripts/socketio_daemon.py +67 -7
  39. claude_mpm/scripts/socketio_daemon_hardened.py +897 -0
  40. claude_mpm/services/agents/deployment/agent_deployment.py +216 -10
  41. claude_mpm/services/agents/deployment/agent_template_builder.py +37 -1
  42. claude_mpm/services/agents/deployment/async_agent_deployment.py +65 -1
  43. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +441 -0
  44. claude_mpm/services/agents/memory/__init__.py +0 -2
  45. claude_mpm/services/agents/memory/agent_memory_manager.py +577 -44
  46. claude_mpm/services/agents/memory/content_manager.py +144 -14
  47. claude_mpm/services/agents/memory/template_generator.py +7 -354
  48. claude_mpm/services/mcp_gateway/server/stdio_server.py +61 -169
  49. claude_mpm/services/memory_hook_service.py +62 -4
  50. claude_mpm/services/runner_configuration_service.py +5 -9
  51. claude_mpm/services/socketio/server/broadcaster.py +32 -1
  52. claude_mpm/services/socketio/server/core.py +4 -0
  53. claude_mpm/services/socketio/server/main.py +23 -4
  54. claude_mpm/services/subprocess_launcher_service.py +5 -0
  55. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/METADATA +1 -1
  56. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/RECORD +60 -54
  57. claude_mpm/services/agents/memory/analyzer.py +0 -430
  58. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/WHEEL +0 -0
  59. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/entry_points.txt +0 -0
  60. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/licenses/LICENSE +0 -0
  61. {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: Run the configuration script
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
- result = subprocess.run(
60
- [sys.executable, str(config_script)],
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. Check process status: python scripts/check_mcp_processes.py")
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 script failed")
60
+ print("❌ Configuration failed")
73
61
  return 1
74
-
62
+
75
63
  except Exception as e:
76
- print(f"❌ Error running configuration: {e}")
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
- memory_files = list(memory_dir.glob("*_agent.md"))
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
- agent_id = file_path.stem.replace("_agent", "")
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
- memory_files = list(memory_dir.glob("*_agent.md"))
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
- memory_files = list(memory_dir.glob("*_agent.md"))
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
- agent_id = file_path.stem.replace("_agent", "")
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:
@@ -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
- # Debug logging for argument filtering
404
+ # Enhanced debug logging for argument filtering
387
405
  if raw_claude_args != claude_args:
388
- logger.debug(
389
- f"Filtered claude-mpm args: {set(raw_claude_args) - set(claude_args)}"
390
- )
391
- logger.debug(f"Passing to Claude CLI: {claude_args}")
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
- agent_versions = get_agent_versions_display()
137
- if agent_versions:
138
- print(agent_versions)
139
- print() # Extra newline after the display
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
@@ -125,6 +125,7 @@ class MCPCommands(str, Enum):
125
125
  REGISTER = "register"
126
126
  TEST = "test"
127
127
  CONFIG = "config"
128
+ SERVER = "server"
128
129
 
129
130
 
130
131
  class TicketCommands(str, Enum):
@@ -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
- self.config = Config(config or {}, config_path)
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.info(f"Initialized {self.name} service")
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."""