claude-mpm 3.9.7__py3-none-any.whl → 3.9.9__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 (54) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/base_agent.json +1 -1
  3. claude_mpm/agents/templates/ticketing.json +1 -1
  4. claude_mpm/cli/__init__.py +3 -1
  5. claude_mpm/cli/commands/__init__.py +3 -1
  6. claude_mpm/cli/commands/cleanup.py +21 -1
  7. claude_mpm/cli/commands/mcp.py +821 -0
  8. claude_mpm/cli/parser.py +148 -1
  9. claude_mpm/config/memory_guardian_config.py +325 -0
  10. claude_mpm/constants.py +13 -0
  11. claude_mpm/hooks/claude_hooks/hook_handler.py +76 -19
  12. claude_mpm/models/state_models.py +433 -0
  13. claude_mpm/services/__init__.py +28 -0
  14. claude_mpm/services/communication/__init__.py +2 -2
  15. claude_mpm/services/communication/socketio.py +18 -16
  16. claude_mpm/services/infrastructure/__init__.py +4 -1
  17. claude_mpm/services/infrastructure/logging.py +3 -3
  18. claude_mpm/services/infrastructure/memory_guardian.py +770 -0
  19. claude_mpm/services/mcp_gateway/__init__.py +138 -0
  20. claude_mpm/services/mcp_gateway/config/__init__.py +17 -0
  21. claude_mpm/services/mcp_gateway/config/config_loader.py +232 -0
  22. claude_mpm/services/mcp_gateway/config/config_schema.py +234 -0
  23. claude_mpm/services/mcp_gateway/config/configuration.py +371 -0
  24. claude_mpm/services/mcp_gateway/core/__init__.py +51 -0
  25. claude_mpm/services/mcp_gateway/core/base.py +315 -0
  26. claude_mpm/services/mcp_gateway/core/exceptions.py +239 -0
  27. claude_mpm/services/mcp_gateway/core/interfaces.py +476 -0
  28. claude_mpm/services/mcp_gateway/main.py +326 -0
  29. claude_mpm/services/mcp_gateway/registry/__init__.py +12 -0
  30. claude_mpm/services/mcp_gateway/registry/service_registry.py +397 -0
  31. claude_mpm/services/mcp_gateway/registry/tool_registry.py +477 -0
  32. claude_mpm/services/mcp_gateway/server/__init__.py +15 -0
  33. claude_mpm/services/mcp_gateway/server/mcp_server.py +430 -0
  34. claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +444 -0
  35. claude_mpm/services/mcp_gateway/server/stdio_handler.py +373 -0
  36. claude_mpm/services/mcp_gateway/tools/__init__.py +22 -0
  37. claude_mpm/services/mcp_gateway/tools/base_adapter.py +497 -0
  38. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +729 -0
  39. claude_mpm/services/mcp_gateway/tools/hello_world.py +551 -0
  40. claude_mpm/utils/file_utils.py +293 -0
  41. claude_mpm/utils/platform_memory.py +524 -0
  42. claude_mpm/utils/subprocess_utils.py +305 -0
  43. {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/METADATA +4 -1
  44. {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/RECORD +49 -26
  45. claude_mpm/agents/templates/.claude-mpm/memories/README.md +0 -36
  46. claude_mpm/agents/templates/.claude-mpm/memories/engineer_agent.md +0 -39
  47. claude_mpm/agents/templates/.claude-mpm/memories/qa_agent.md +0 -38
  48. claude_mpm/agents/templates/.claude-mpm/memories/research_agent.md +0 -39
  49. claude_mpm/agents/templates/.claude-mpm/memories/version_control_agent.md +0 -38
  50. /claude_mpm/agents/templates/{research_memory_efficient.json → backup/research_memory_efficient.json} +0 -0
  51. {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/WHEEL +0 -0
  52. {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/entry_points.txt +0 -0
  53. {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/licenses/LICENSE +0 -0
  54. {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,371 @@
1
+ """
2
+ MCP Gateway Configuration Implementation
3
+ ========================================
4
+
5
+ Manages configuration for the MCP Gateway service.
6
+
7
+ Part of ISS-0034: Infrastructure Setup - MCP Gateway Project Foundation
8
+ """
9
+
10
+ import os
11
+ from typing import Any, Dict, Optional
12
+ from pathlib import Path
13
+ import yaml
14
+
15
+ from claude_mpm.services.mcp_gateway.core.interfaces import IMCPConfiguration
16
+ from claude_mpm.services.mcp_gateway.core.base import BaseMCPService
17
+ from claude_mpm.services.mcp_gateway.core.exceptions import MCPConfigurationError
18
+
19
+
20
+ class MCPConfiguration(BaseMCPService, IMCPConfiguration):
21
+ """
22
+ MCP Gateway configuration management service.
23
+
24
+ This service handles loading, validation, and access to MCP Gateway configuration.
25
+ It supports YAML-based configuration files and environment variable overrides.
26
+
27
+ WHY: Configuration is centralized in a service to ensure consistent access
28
+ patterns, validation, and the ability to reload configuration at runtime.
29
+ The service pattern also allows for dependency injection of configuration
30
+ into other MCP services.
31
+ """
32
+
33
+ DEFAULT_CONFIG = {
34
+ "mcp": {
35
+ "server": {
36
+ "name": "claude-mpm-gateway",
37
+ "version": "1.0.0",
38
+ "description": "Claude MPM MCP Gateway Server",
39
+ "communication": {
40
+ "type": "stdio", # stdio, websocket, or http
41
+ "timeout": 30, # seconds
42
+ "buffer_size": 8192,
43
+ },
44
+ "capabilities": {
45
+ "tools": True,
46
+ "resources": False, # Not yet implemented
47
+ "prompts": False, # Not yet implemented
48
+ },
49
+ },
50
+ "tools": {
51
+ "enabled": True,
52
+ "auto_discover": True,
53
+ "discovery_paths": [
54
+ "~/.claude/mcp/tools",
55
+ "./mcp_tools",
56
+ ],
57
+ "timeout_default": 30, # seconds
58
+ "max_concurrent": 10,
59
+ },
60
+ "logging": {
61
+ "level": "INFO",
62
+ "file": "~/.claude/logs/mcp_gateway.log",
63
+ "max_size": "10MB",
64
+ "max_files": 5,
65
+ "format": "json", # json or text
66
+ },
67
+ "security": {
68
+ "validate_schemas": True,
69
+ "sanitize_inputs": True,
70
+ "max_request_size": 1048576, # 1MB
71
+ "allowed_tools": [], # Empty means all tools allowed
72
+ "blocked_tools": [],
73
+ },
74
+ }
75
+ }
76
+
77
+ def __init__(self, config_path: Optional[Path] = None):
78
+ """
79
+ Initialize MCP configuration service.
80
+
81
+ Args:
82
+ config_path: Optional path to configuration file
83
+ """
84
+ super().__init__("MCPConfiguration")
85
+ self._config_path = config_path
86
+ self._config_data: Dict[str, Any] = {}
87
+ self._is_loaded = False
88
+
89
+ async def _do_initialize(self) -> bool:
90
+ """
91
+ Initialize the configuration service.
92
+
93
+ Returns:
94
+ True if initialization successful
95
+ """
96
+ # Start with default configuration
97
+ self._config_data = self.DEFAULT_CONFIG.copy()
98
+
99
+ # Load from file if path provided
100
+ if self._config_path:
101
+ if not self.load_config(self._config_path):
102
+ return False
103
+
104
+ # Apply environment variable overrides
105
+ self._apply_env_overrides()
106
+
107
+ # Validate configuration
108
+ if not self.validate():
109
+ return False
110
+
111
+ self._is_loaded = True
112
+ self.log_info("Configuration initialized successfully")
113
+ return True
114
+
115
+ def load_config(self, config_path: Path) -> bool:
116
+ """
117
+ Load configuration from a file.
118
+
119
+ Args:
120
+ config_path: Path to configuration file
121
+
122
+ Returns:
123
+ True if configuration loaded successfully
124
+ """
125
+ try:
126
+ # Expand user path
127
+ config_path = Path(config_path).expanduser()
128
+
129
+ if not config_path.exists():
130
+ self.log_warning(f"Configuration file not found: {config_path}")
131
+ return True # Not an error, use defaults
132
+
133
+ with open(config_path, 'r') as f:
134
+ if config_path.suffix in ['.yaml', '.yml']:
135
+ loaded_config = yaml.safe_load(f) or {}
136
+ else:
137
+ raise MCPConfigurationError(
138
+ f"Unsupported configuration file format: {config_path.suffix}"
139
+ )
140
+
141
+ # Merge with existing configuration
142
+ self._merge_config(self._config_data, loaded_config)
143
+ self._config_path = config_path
144
+
145
+ self.log_info(f"Configuration loaded from {config_path}")
146
+ return True
147
+
148
+ except yaml.YAMLError as e:
149
+ raise MCPConfigurationError(f"Failed to parse YAML configuration: {e}")
150
+ except Exception as e:
151
+ self.log_error(f"Failed to load configuration: {e}")
152
+ return False
153
+
154
+ def _merge_config(self, base: Dict[str, Any], overlay: Dict[str, Any]) -> None:
155
+ """
156
+ Recursively merge overlay configuration into base.
157
+
158
+ Args:
159
+ base: Base configuration dictionary
160
+ overlay: Configuration to merge in
161
+ """
162
+ for key, value in overlay.items():
163
+ if key in base and isinstance(base[key], dict) and isinstance(value, dict):
164
+ self._merge_config(base[key], value)
165
+ else:
166
+ base[key] = value
167
+
168
+ def _apply_env_overrides(self) -> None:
169
+ """
170
+ Apply environment variable overrides to configuration.
171
+
172
+ Environment variables follow the pattern: MCP_GATEWAY_<SECTION>_<KEY>
173
+ For example: MCP_GATEWAY_SERVER_NAME=my-server
174
+ """
175
+ prefix = "MCP_GATEWAY_"
176
+
177
+ for env_key, env_value in os.environ.items():
178
+ if not env_key.startswith(prefix):
179
+ continue
180
+
181
+ # Parse environment variable into configuration path
182
+ config_path = env_key[len(prefix):].lower().split('_')
183
+
184
+ # Navigate to the configuration location
185
+ current = self._config_data
186
+ for i, part in enumerate(config_path[:-1]):
187
+ if part not in current:
188
+ current[part] = {}
189
+ elif not isinstance(current[part], dict):
190
+ self.log_warning(f"Cannot override non-dict config at {'.'.join(config_path[:i+1])}")
191
+ break
192
+ current = current[part]
193
+ else:
194
+ # Set the value
195
+ key = config_path[-1]
196
+ # Try to parse as JSON for complex types
197
+ try:
198
+ import json
199
+ current[key] = json.loads(env_value)
200
+ except:
201
+ # Fall back to string value
202
+ current[key] = env_value
203
+
204
+ self.log_debug(f"Applied environment override: {env_key}")
205
+
206
+ def get(self, key: str, default: Any = None) -> Any:
207
+ """
208
+ Get configuration value by key.
209
+
210
+ Args:
211
+ key: Configuration key (supports dot notation, e.g., "mcp.server.name")
212
+ default: Default value if key not found
213
+
214
+ Returns:
215
+ Configuration value or default
216
+ """
217
+ parts = key.split('.')
218
+ current = self._config_data
219
+
220
+ for part in parts:
221
+ if isinstance(current, dict) and part in current:
222
+ current = current[part]
223
+ else:
224
+ return default
225
+
226
+ return current
227
+
228
+ def set(self, key: str, value: Any) -> None:
229
+ """
230
+ Set configuration value.
231
+
232
+ Args:
233
+ key: Configuration key (supports dot notation)
234
+ value: Configuration value
235
+ """
236
+ parts = key.split('.')
237
+ current = self._config_data
238
+
239
+ # Navigate to parent
240
+ for part in parts[:-1]:
241
+ if part not in current:
242
+ current[part] = {}
243
+ elif not isinstance(current[part], dict):
244
+ raise MCPConfigurationError(
245
+ f"Cannot set value at {key}: parent is not a dictionary"
246
+ )
247
+ current = current[part]
248
+
249
+ # Set the value
250
+ current[parts[-1]] = value
251
+ self.log_debug(f"Configuration updated: {key} = {value}")
252
+
253
+ def validate(self) -> bool:
254
+ """
255
+ Validate the current configuration.
256
+
257
+ Returns:
258
+ True if configuration is valid
259
+ """
260
+ try:
261
+ # Check required fields
262
+ required_fields = [
263
+ "mcp.server.name",
264
+ "mcp.server.version",
265
+ "mcp.server.communication.type",
266
+ ]
267
+
268
+ for field in required_fields:
269
+ if self.get(field) is None:
270
+ raise MCPConfigurationError(
271
+ f"Required configuration field missing: {field}",
272
+ config_key=field
273
+ )
274
+
275
+ # Validate communication type
276
+ comm_type = self.get("mcp.server.communication.type")
277
+ if comm_type not in ["stdio", "websocket", "http"]:
278
+ raise MCPConfigurationError(
279
+ f"Invalid communication type: {comm_type}",
280
+ config_key="mcp.server.communication.type",
281
+ expected_type="stdio|websocket|http"
282
+ )
283
+
284
+ # Validate numeric fields
285
+ timeout = self.get("mcp.server.communication.timeout")
286
+ if not isinstance(timeout, (int, float)) or timeout <= 0:
287
+ raise MCPConfigurationError(
288
+ "Invalid timeout value",
289
+ config_key="mcp.server.communication.timeout",
290
+ expected_type="positive number"
291
+ )
292
+
293
+ self.log_debug("Configuration validation successful")
294
+ return True
295
+
296
+ except MCPConfigurationError:
297
+ raise
298
+ except Exception as e:
299
+ self.log_error(f"Configuration validation failed: {e}")
300
+ return False
301
+
302
+ def get_server_config(self) -> Dict[str, Any]:
303
+ """
304
+ Get MCP server configuration.
305
+
306
+ Returns:
307
+ Server configuration dictionary
308
+ """
309
+ return self.get("mcp.server", {})
310
+
311
+ def get_tools_config(self) -> Dict[str, Any]:
312
+ """
313
+ Get tools configuration.
314
+
315
+ Returns:
316
+ Tools configuration dictionary
317
+ """
318
+ return self.get("mcp.tools", {})
319
+
320
+ def save_config(self, path: Optional[Path] = None) -> bool:
321
+ """
322
+ Save current configuration to file.
323
+
324
+ Args:
325
+ path: Path to save configuration (uses loaded path if not specified)
326
+
327
+ Returns:
328
+ True if save successful
329
+ """
330
+ save_path = path or self._config_path
331
+ if not save_path:
332
+ self.log_error("No path specified for saving configuration")
333
+ return False
334
+
335
+ try:
336
+ save_path = Path(save_path).expanduser()
337
+ save_path.parent.mkdir(parents=True, exist_ok=True)
338
+
339
+ with open(save_path, 'w') as f:
340
+ yaml.dump(self._config_data, f, default_flow_style=False, sort_keys=True)
341
+
342
+ self.log_info(f"Configuration saved to {save_path}")
343
+ return True
344
+
345
+ except Exception as e:
346
+ self.log_error(f"Failed to save configuration: {e}")
347
+ return False
348
+
349
+ def reload(self) -> bool:
350
+ """
351
+ Reload configuration from file.
352
+
353
+ Returns:
354
+ True if reload successful
355
+ """
356
+ if not self._config_path:
357
+ self.log_warning("No configuration file to reload")
358
+ return True
359
+
360
+ # Reset to defaults
361
+ self._config_data = self.DEFAULT_CONFIG.copy()
362
+
363
+ # Reload from file
364
+ if not self.load_config(self._config_path):
365
+ return False
366
+
367
+ # Reapply environment overrides
368
+ self._apply_env_overrides()
369
+
370
+ # Revalidate
371
+ return self.validate()
@@ -0,0 +1,51 @@
1
+ """
2
+ MCP Gateway Core Module
3
+ =======================
4
+
5
+ Core interfaces and base classes for the MCP Gateway service.
6
+ """
7
+
8
+ from .interfaces import (
9
+ IMCPServer,
10
+ IMCPToolRegistry,
11
+ IMCPConfiguration,
12
+ IMCPToolAdapter,
13
+ IMCPLifecycle,
14
+ IMCPCommunication,
15
+ )
16
+
17
+ from .base import (
18
+ BaseMCPService,
19
+ MCPServiceState,
20
+ )
21
+
22
+ from .exceptions import (
23
+ MCPException,
24
+ MCPConfigurationError,
25
+ MCPToolNotFoundError,
26
+ MCPServerError,
27
+ MCPCommunicationError,
28
+ MCPValidationError,
29
+ )
30
+
31
+ __all__ = [
32
+ # Interfaces
33
+ "IMCPServer",
34
+ "IMCPToolRegistry",
35
+ "IMCPConfiguration",
36
+ "IMCPToolAdapter",
37
+ "IMCPLifecycle",
38
+ "IMCPCommunication",
39
+
40
+ # Base classes
41
+ "BaseMCPService",
42
+ "MCPServiceState",
43
+
44
+ # Exceptions
45
+ "MCPException",
46
+ "MCPConfigurationError",
47
+ "MCPToolNotFoundError",
48
+ "MCPServerError",
49
+ "MCPCommunicationError",
50
+ "MCPValidationError",
51
+ ]