claude-mpm 5.6.23__py3-none-any.whl → 5.6.73__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (82) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/auth/__init__.py +35 -0
  3. claude_mpm/auth/callback_server.py +328 -0
  4. claude_mpm/auth/models.py +104 -0
  5. claude_mpm/auth/oauth_manager.py +266 -0
  6. claude_mpm/auth/providers/__init__.py +12 -0
  7. claude_mpm/auth/providers/base.py +165 -0
  8. claude_mpm/auth/providers/google.py +261 -0
  9. claude_mpm/auth/token_storage.py +252 -0
  10. claude_mpm/cli/commands/commander.py +6 -6
  11. claude_mpm/cli/commands/mcp.py +29 -17
  12. claude_mpm/cli/commands/mcp_command_router.py +39 -0
  13. claude_mpm/cli/commands/mcp_service_commands.py +304 -0
  14. claude_mpm/cli/commands/oauth.py +481 -0
  15. claude_mpm/cli/executor.py +9 -0
  16. claude_mpm/cli/helpers.py +1 -1
  17. claude_mpm/cli/parsers/base_parser.py +13 -0
  18. claude_mpm/cli/parsers/mcp_parser.py +79 -0
  19. claude_mpm/cli/parsers/oauth_parser.py +165 -0
  20. claude_mpm/cli/startup.py +150 -33
  21. claude_mpm/cli/startup_display.py +3 -2
  22. claude_mpm/commander/chat/cli.py +5 -2
  23. claude_mpm/commander/chat/commands.py +42 -16
  24. claude_mpm/commander/chat/repl.py +1581 -70
  25. claude_mpm/commander/events/manager.py +61 -1
  26. claude_mpm/commander/frameworks/base.py +87 -0
  27. claude_mpm/commander/frameworks/mpm.py +9 -14
  28. claude_mpm/commander/git/__init__.py +5 -0
  29. claude_mpm/commander/git/worktree_manager.py +212 -0
  30. claude_mpm/commander/instance_manager.py +428 -13
  31. claude_mpm/commander/models/events.py +6 -0
  32. claude_mpm/commander/persistence/state_store.py +95 -1
  33. claude_mpm/commander/tmux_orchestrator.py +3 -2
  34. claude_mpm/constants.py +5 -0
  35. claude_mpm/core/hook_manager.py +2 -1
  36. claude_mpm/core/logging_utils.py +4 -2
  37. claude_mpm/core/output_style_manager.py +5 -2
  38. claude_mpm/core/socketio_pool.py +34 -10
  39. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +1 -1
  40. claude_mpm/hooks/claude_hooks/event_handlers.py +206 -94
  41. claude_mpm/hooks/claude_hooks/hook_handler.py +115 -32
  42. claude_mpm/hooks/claude_hooks/installer.py +175 -51
  43. claude_mpm/hooks/claude_hooks/memory_integration.py +1 -1
  44. claude_mpm/hooks/claude_hooks/response_tracking.py +1 -1
  45. claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
  46. claude_mpm/hooks/claude_hooks/services/connection_manager.py +2 -2
  47. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +2 -2
  48. claude_mpm/hooks/claude_hooks/services/container.py +326 -0
  49. claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
  50. claude_mpm/hooks/claude_hooks/services/state_manager.py +2 -2
  51. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +2 -2
  52. claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
  53. claude_mpm/hooks/templates/pre_tool_use_template.py +6 -6
  54. claude_mpm/init.py +21 -14
  55. claude_mpm/mcp/__init__.py +9 -0
  56. claude_mpm/mcp/google_workspace_server.py +610 -0
  57. claude_mpm/scripts/claude-hook-handler.sh +3 -3
  58. claude_mpm/services/command_deployment_service.py +44 -26
  59. claude_mpm/services/hook_installer_service.py +77 -8
  60. claude_mpm/services/mcp_config_manager.py +99 -19
  61. claude_mpm/services/mcp_service_registry.py +294 -0
  62. claude_mpm/services/monitor/server.py +6 -1
  63. {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/METADATA +24 -1
  64. {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/RECORD +69 -64
  65. {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/WHEEL +1 -1
  66. {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/entry_points.txt +2 -0
  67. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  68. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  69. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  70. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  71. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  72. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  73. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  74. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  75. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  76. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  77. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  78. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  79. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  80. {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/licenses/LICENSE +0 -0
  81. {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  82. {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,304 @@
1
+ """MCP service management commands for claude-mpm CLI.
2
+
3
+ This module provides enable, disable, and list operations for MCP services
4
+ using the service registry for configuration generation.
5
+
6
+ WHY: Enables users to easily enable/disable MCP services with proper
7
+ credential handling and configuration management.
8
+ """
9
+
10
+ import getpass
11
+ import json
12
+ from pathlib import Path
13
+ from typing import TYPE_CHECKING
14
+
15
+ if TYPE_CHECKING:
16
+ from argparse import Namespace
17
+ from logging import Logger
18
+
19
+
20
+ class MCPServiceCommands:
21
+ """Command handlers for MCP service management."""
22
+
23
+ # Configuration file paths
24
+ GLOBAL_CONFIG = Path.home() / ".claude.json"
25
+ PROJECT_CONFIG = Path(".mcp.json")
26
+
27
+ def __init__(self, logger: "Logger") -> None:
28
+ """Initialize the command handler.
29
+
30
+ Args:
31
+ logger: Logger instance for output
32
+ """
33
+ self.logger = logger
34
+
35
+ def enable_service(self, args: "Namespace") -> int:
36
+ """Enable an MCP service in configuration.
37
+
38
+ Args:
39
+ args: Parsed command arguments with:
40
+ - service_name: Name of service to enable
41
+ - interactive: Whether to prompt for credentials
42
+ - env: List of KEY=VALUE strings
43
+ - use_global: Use global config instead of project
44
+
45
+ Returns:
46
+ Exit code (0 for success, 1 for error)
47
+ """
48
+ from ...services.mcp_service_registry import MCPServiceRegistry
49
+
50
+ service_name = args.service_name
51
+
52
+ # Check if service exists in registry
53
+ service = MCPServiceRegistry.get(service_name)
54
+ if not service:
55
+ available = MCPServiceRegistry.list_names()
56
+ print(f"Error: Unknown service '{service_name}'")
57
+ print(f"Available services: {', '.join(available)}")
58
+ return 1
59
+
60
+ # Parse environment variables from --env flags
61
+ env_vars: dict[str, str] = {}
62
+ if args.env:
63
+ for env_str in args.env:
64
+ if "=" not in env_str:
65
+ print(f"Error: Invalid env format '{env_str}'. Use KEY=VALUE")
66
+ return 1
67
+ key, value = env_str.split("=", 1)
68
+ env_vars[key] = value
69
+
70
+ # Interactive mode: prompt for required env vars
71
+ if args.interactive:
72
+ for var in service.required_env:
73
+ if var not in env_vars:
74
+ # Use getpass for sensitive values (tokens, keys, etc.)
75
+ if any(
76
+ keyword in var.upper()
77
+ for keyword in [
78
+ "TOKEN",
79
+ "KEY",
80
+ "SECRET",
81
+ "PASSWORD",
82
+ "CREDENTIAL",
83
+ ]
84
+ ):
85
+ value = getpass.getpass(f"Enter {var}: ")
86
+ else:
87
+ value = input(f"Enter {var}: ")
88
+ if value:
89
+ env_vars[var] = value
90
+
91
+ # Validate required environment variables
92
+ is_valid, missing = MCPServiceRegistry.validate_env(service, env_vars)
93
+ if not is_valid:
94
+ print(
95
+ f"Error: Missing required environment variables: {', '.join(missing)}"
96
+ )
97
+ print(f"Use --env {missing[0]}=VALUE or --interactive to provide them")
98
+ return 1
99
+
100
+ # Generate service configuration
101
+ config = MCPServiceRegistry.generate_config(service, env_vars)
102
+
103
+ # Determine config file path
104
+ config_path = self.GLOBAL_CONFIG if args.use_global else self.PROJECT_CONFIG
105
+
106
+ # Load existing config or create new
107
+ existing_config = self._load_config(config_path)
108
+
109
+ # Add/update service in mcpServers section
110
+ if "mcpServers" not in existing_config:
111
+ existing_config["mcpServers"] = {}
112
+
113
+ existing_config["mcpServers"][service_name] = config
114
+
115
+ # Save configuration
116
+ if self._save_config(config_path, existing_config):
117
+ location = (
118
+ "global (~/.claude.json)" if args.use_global else "project (.mcp.json)"
119
+ )
120
+ print(f"Enabled '{service_name}' in {location}")
121
+ print(f"Description: {service.description}")
122
+
123
+ if service.optional_env:
124
+ print(
125
+ f"\nOptional environment variables: {', '.join(service.optional_env)}"
126
+ )
127
+ print("Use --env VAR=VALUE to set them")
128
+
129
+ return 0
130
+ print(f"Error: Failed to save configuration to {config_path}")
131
+ return 1
132
+
133
+ def disable_service(self, args: "Namespace") -> int:
134
+ """Disable an MCP service from configuration.
135
+
136
+ This removes the service from the configuration file but does NOT
137
+ uninstall the underlying package.
138
+
139
+ Args:
140
+ args: Parsed command arguments with:
141
+ - service_name: Name of service to disable
142
+ - use_global: Use global config instead of project
143
+
144
+ Returns:
145
+ Exit code (0 for success, 1 for error)
146
+ """
147
+ service_name = args.service_name
148
+ config_path = self.GLOBAL_CONFIG if args.use_global else self.PROJECT_CONFIG
149
+
150
+ # Load existing config
151
+ existing_config = self._load_config(config_path)
152
+
153
+ # Check if service exists in config
154
+ if "mcpServers" not in existing_config:
155
+ print(f"Error: No MCP services configured in {config_path}")
156
+ return 1
157
+
158
+ if service_name not in existing_config["mcpServers"]:
159
+ print(f"Error: Service '{service_name}' is not enabled")
160
+ enabled = list(existing_config["mcpServers"].keys())
161
+ if enabled:
162
+ print(f"Enabled services: {', '.join(enabled)}")
163
+ return 1
164
+
165
+ # Remove service from config
166
+ del existing_config["mcpServers"][service_name]
167
+
168
+ # Save configuration
169
+ if self._save_config(config_path, existing_config):
170
+ location = (
171
+ "global (~/.claude.json)" if args.use_global else "project (.mcp.json)"
172
+ )
173
+ print(f"Disabled '{service_name}' in {location}")
174
+ print(
175
+ "Note: The package is still installed. Use pipx/uvx to uninstall if needed."
176
+ )
177
+ return 0
178
+ print(f"Error: Failed to save configuration to {config_path}")
179
+ return 1
180
+
181
+ def list_services(self, args: "Namespace") -> int:
182
+ """List MCP services (available and/or enabled).
183
+
184
+ Args:
185
+ args: Parsed command arguments with:
186
+ - available: Show all available services from registry
187
+ - enabled: Show only enabled services
188
+ - use_global: Check global config instead of project
189
+ - verbose: Show detailed information
190
+
191
+ Returns:
192
+ Exit code (0 for success)
193
+ """
194
+ from ...services.mcp_service_registry import MCPServiceRegistry
195
+
196
+ config_path = self.GLOBAL_CONFIG if args.use_global else self.PROJECT_CONFIG
197
+ existing_config = self._load_config(config_path)
198
+ enabled_services = existing_config.get("mcpServers", {})
199
+
200
+ # Default: show both available and enabled
201
+ show_available = args.available or not (args.available or args.enabled)
202
+ show_enabled = args.enabled or not (args.available or args.enabled)
203
+
204
+ if show_available:
205
+ print("Available MCP Services:")
206
+ print("-" * 60)
207
+ for service in MCPServiceRegistry.list_all():
208
+ status = "[enabled]" if service.name in enabled_services else ""
209
+ default_marker = " (default)" if service.enabled_by_default else ""
210
+ print(f" {service.name:<25} {status:>10}{default_marker}")
211
+ if args.verbose:
212
+ print(f" Description: {service.description}")
213
+ print(f" Package: {service.package}")
214
+ print(f" Install: {service.install_method.value}")
215
+ if service.required_env:
216
+ print(f" Required env: {', '.join(service.required_env)}")
217
+ if service.optional_env:
218
+ print(f" Optional env: {', '.join(service.optional_env)}")
219
+ print()
220
+ print()
221
+
222
+ if show_enabled:
223
+ location = (
224
+ "global (~/.claude.json)" if args.use_global else "project (.mcp.json)"
225
+ )
226
+ print(f"Enabled Services ({location}):")
227
+ print("-" * 60)
228
+ if not enabled_services:
229
+ print(" No services enabled")
230
+ else:
231
+ for name, config in enabled_services.items():
232
+ registry_service = MCPServiceRegistry.get(name)
233
+ if registry_service:
234
+ print(f" {name:<25} [registered]")
235
+ if args.verbose:
236
+ print(f" Description: {registry_service.description}")
237
+ else:
238
+ print(f" {name:<25} [custom]")
239
+ if args.verbose:
240
+ print(f" Command: {config.get('command', 'N/A')}")
241
+ if "args" in config:
242
+ print(f" Args: {config['args']}")
243
+ if "env" in config:
244
+ # Mask sensitive values
245
+ env_display = {}
246
+ for k, v in config.get("env", {}).items():
247
+ if any(
248
+ keyword in k.upper()
249
+ for keyword in [
250
+ "TOKEN",
251
+ "KEY",
252
+ "SECRET",
253
+ "PASSWORD",
254
+ ]
255
+ ):
256
+ env_display[k] = "***"
257
+ else:
258
+ env_display[k] = v
259
+ print(f" Env: {env_display}")
260
+ print()
261
+
262
+ return 0
263
+
264
+ def _load_config(self, path: Path) -> dict:
265
+ """Load configuration from a JSON file.
266
+
267
+ Args:
268
+ path: Path to the configuration file
269
+
270
+ Returns:
271
+ Configuration dictionary (empty if file doesn't exist)
272
+ """
273
+ if not path.exists():
274
+ return {}
275
+ try:
276
+ with open(path) as f:
277
+ data = json.load(f)
278
+ return dict(data) if isinstance(data, dict) else {}
279
+ except json.JSONDecodeError as e:
280
+ self.logger.warning(f"Invalid JSON in {path}: {e}")
281
+ return {}
282
+ except OSError as e:
283
+ self.logger.warning(f"Failed to read {path}: {e}")
284
+ return {}
285
+
286
+ def _save_config(self, path: Path, config: dict) -> bool:
287
+ """Save configuration to a JSON file.
288
+
289
+ Args:
290
+ path: Path to the configuration file
291
+ config: Configuration dictionary to save
292
+
293
+ Returns:
294
+ True if saved successfully, False otherwise
295
+ """
296
+ try:
297
+ # Ensure parent directory exists
298
+ path.parent.mkdir(parents=True, exist_ok=True)
299
+ with open(path, "w") as f:
300
+ json.dump(config, f, indent=2)
301
+ return True
302
+ except OSError as e:
303
+ self.logger.error(f"Failed to write {path}: {e}")
304
+ return False