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,315 @@
1
+ """
2
+ MCP Gateway Base Classes
3
+ ========================
4
+
5
+ Base implementations for MCP Gateway services.
6
+
7
+ Part of ISS-0034: Infrastructure Setup - MCP Gateway Project Foundation
8
+ """
9
+
10
+ from enum import Enum
11
+ from typing import Any, Dict, Optional
12
+ import asyncio
13
+ import logging
14
+ from pathlib import Path
15
+
16
+ from claude_mpm.services.core.base import BaseService
17
+ from claude_mpm.core.logger import get_logger
18
+
19
+
20
+ class MCPServiceState(Enum):
21
+ """MCP service lifecycle states."""
22
+ UNINITIALIZED = "uninitialized"
23
+ INITIALIZING = "initializing"
24
+ INITIALIZED = "initialized"
25
+ STARTING = "starting"
26
+ RUNNING = "running"
27
+ STOPPING = "stopping"
28
+ STOPPED = "stopped"
29
+ ERROR = "error"
30
+
31
+
32
+ class BaseMCPService(BaseService):
33
+ """
34
+ Base class for all MCP Gateway services.
35
+
36
+ Extends the claude-mpm BaseService with MCP-specific functionality
37
+ including state management, health monitoring, and async lifecycle support.
38
+
39
+ WHY: We extend BaseService to maintain consistency with the claude-mpm
40
+ architecture while adding MCP-specific capabilities. This ensures all
41
+ MCP services integrate seamlessly with the existing service container
42
+ and dependency injection system.
43
+ """
44
+
45
+ def __init__(self, service_name: Optional[str] = None, config: Optional[Dict[str, Any]] = None):
46
+ """
47
+ Initialize MCP service.
48
+
49
+ Args:
50
+ service_name: Name of the service for logging
51
+ config: Service-specific configuration
52
+ """
53
+ super().__init__(service_name or "MCPService", config)
54
+ self._state = MCPServiceState.UNINITIALIZED
55
+ self._health_status = {
56
+ "healthy": False,
57
+ "state": self._state.value,
58
+ "last_check": None,
59
+ "details": {}
60
+ }
61
+ self._state_lock = asyncio.Lock()
62
+ self._initialization_task = None
63
+ self._shutdown_task = None
64
+
65
+ async def initialize(self) -> bool:
66
+ """
67
+ Initialize the MCP service.
68
+
69
+ This method manages state transitions and ensures thread-safe initialization.
70
+ Subclasses should override _do_initialize() for custom initialization logic.
71
+
72
+ Returns:
73
+ True if initialization successful, False otherwise
74
+ """
75
+ async with self._state_lock:
76
+ if self._state not in [MCPServiceState.UNINITIALIZED, MCPServiceState.STOPPED]:
77
+ self.log_warning(f"Cannot initialize from state {self._state.value}")
78
+ return False
79
+
80
+ self._state = MCPServiceState.INITIALIZING
81
+ self.log_info("Initializing MCP service")
82
+
83
+ try:
84
+ # Call subclass initialization
85
+ success = await self._do_initialize()
86
+
87
+ async with self._state_lock:
88
+ if success:
89
+ self._state = MCPServiceState.INITIALIZED
90
+ self._initialized = True
91
+ self._health_status["healthy"] = True
92
+ self._health_status["state"] = self._state.value
93
+ self.log_info("MCP service initialized successfully")
94
+ else:
95
+ self._state = MCPServiceState.ERROR
96
+ self._health_status["healthy"] = False
97
+ self._health_status["state"] = self._state.value
98
+ self.log_error("MCP service initialization failed")
99
+
100
+ return success
101
+
102
+ except Exception as e:
103
+ async with self._state_lock:
104
+ self._state = MCPServiceState.ERROR
105
+ self._health_status["healthy"] = False
106
+ self._health_status["state"] = self._state.value
107
+ self._health_status["details"]["error"] = str(e)
108
+
109
+ self.log_error(f"Exception during initialization: {e}")
110
+ return False
111
+
112
+ async def _do_initialize(self) -> bool:
113
+ """
114
+ Perform actual initialization logic.
115
+
116
+ Subclasses should override this method to implement custom initialization.
117
+
118
+ Returns:
119
+ True if initialization successful
120
+ """
121
+ # Default implementation - subclasses should override
122
+ return True
123
+
124
+ async def start(self) -> bool:
125
+ """
126
+ Start the MCP service.
127
+
128
+ Returns:
129
+ True if startup successful
130
+ """
131
+ async with self._state_lock:
132
+ if self._state != MCPServiceState.INITIALIZED:
133
+ self.log_warning(f"Cannot start from state {self._state.value}")
134
+ return False
135
+
136
+ self._state = MCPServiceState.STARTING
137
+ self.log_info("Starting MCP service")
138
+
139
+ try:
140
+ success = await self._do_start()
141
+
142
+ async with self._state_lock:
143
+ if success:
144
+ self._state = MCPServiceState.RUNNING
145
+ self._health_status["healthy"] = True
146
+ self._health_status["state"] = self._state.value
147
+ self.log_info("MCP service started successfully")
148
+ else:
149
+ self._state = MCPServiceState.ERROR
150
+ self._health_status["healthy"] = False
151
+ self._health_status["state"] = self._state.value
152
+ self.log_error("MCP service startup failed")
153
+
154
+ return success
155
+
156
+ except Exception as e:
157
+ async with self._state_lock:
158
+ self._state = MCPServiceState.ERROR
159
+ self._health_status["healthy"] = False
160
+ self._health_status["state"] = self._state.value
161
+ self._health_status["details"]["error"] = str(e)
162
+
163
+ self.log_error(f"Exception during startup: {e}")
164
+ return False
165
+
166
+ async def _do_start(self) -> bool:
167
+ """
168
+ Perform actual startup logic.
169
+
170
+ Subclasses should override this method to implement custom startup.
171
+
172
+ Returns:
173
+ True if startup successful
174
+ """
175
+ # Default implementation - subclasses should override
176
+ return True
177
+
178
+ async def shutdown(self) -> None:
179
+ """
180
+ Shutdown the MCP service gracefully.
181
+
182
+ This method manages state transitions and ensures clean shutdown.
183
+ Subclasses should override _do_shutdown() for custom shutdown logic.
184
+ """
185
+ async with self._state_lock:
186
+ if self._state in [MCPServiceState.STOPPED, MCPServiceState.STOPPING]:
187
+ self.log_warning(f"Already in state {self._state.value}")
188
+ return
189
+
190
+ self._state = MCPServiceState.STOPPING
191
+ self.log_info("Shutting down MCP service")
192
+
193
+ try:
194
+ await self._do_shutdown()
195
+
196
+ async with self._state_lock:
197
+ self._state = MCPServiceState.STOPPED
198
+ self._shutdown = True
199
+ self._health_status["healthy"] = False
200
+ self._health_status["state"] = self._state.value
201
+ self.log_info("MCP service shutdown complete")
202
+
203
+ except Exception as e:
204
+ async with self._state_lock:
205
+ self._state = MCPServiceState.ERROR
206
+ self._health_status["healthy"] = False
207
+ self._health_status["state"] = self._state.value
208
+ self._health_status["details"]["error"] = str(e)
209
+
210
+ self.log_error(f"Exception during shutdown: {e}")
211
+
212
+ async def _do_shutdown(self) -> None:
213
+ """
214
+ Perform actual shutdown logic.
215
+
216
+ Subclasses should override this method to implement custom shutdown.
217
+ """
218
+ # Default implementation - subclasses should override
219
+ pass
220
+
221
+ async def restart(self) -> bool:
222
+ """
223
+ Restart the MCP service.
224
+
225
+ Returns:
226
+ True if restart successful
227
+ """
228
+ self.log_info("Restarting MCP service")
229
+
230
+ # Shutdown if running
231
+ if self._state == MCPServiceState.RUNNING:
232
+ await self.shutdown()
233
+
234
+ # Re-initialize
235
+ if not await self.initialize():
236
+ return False
237
+
238
+ # Start again
239
+ return await self.start()
240
+
241
+ def get_state(self) -> str:
242
+ """
243
+ Get current service state.
244
+
245
+ Returns:
246
+ Service state string
247
+ """
248
+ return self._state.value
249
+
250
+ def is_healthy(self) -> bool:
251
+ """
252
+ Check if service is healthy.
253
+
254
+ Returns:
255
+ True if service is healthy
256
+ """
257
+ return self._health_status["healthy"]
258
+
259
+ def get_health_status(self) -> Dict[str, Any]:
260
+ """
261
+ Get detailed health status.
262
+
263
+ Returns:
264
+ Health status information
265
+ """
266
+ return self._health_status.copy()
267
+
268
+ def update_health_status(self, healthy: bool, details: Optional[Dict[str, Any]] = None) -> None:
269
+ """
270
+ Update health status.
271
+
272
+ Args:
273
+ healthy: Whether service is healthy
274
+ details: Additional health details
275
+ """
276
+ from datetime import datetime
277
+
278
+ self._health_status["healthy"] = healthy
279
+ self._health_status["last_check"] = datetime.now().isoformat()
280
+
281
+ if details:
282
+ self._health_status["details"].update(details)
283
+
284
+ # Additional logging methods for MCP-specific events
285
+ def log_mcp_event(self, event_type: str, data: Optional[Dict[str, Any]] = None) -> None:
286
+ """
287
+ Log an MCP-specific event.
288
+
289
+ Args:
290
+ event_type: Type of MCP event
291
+ data: Event data
292
+ """
293
+ message = f"MCP Event: {event_type}"
294
+ if data:
295
+ message += f" - {data}"
296
+ self.log_info(message)
297
+
298
+ def log_tool_invocation(self, tool_name: str, success: bool, duration: Optional[float] = None) -> None:
299
+ """
300
+ Log a tool invocation.
301
+
302
+ Args:
303
+ tool_name: Name of the tool invoked
304
+ success: Whether invocation was successful
305
+ duration: Execution duration in seconds
306
+ """
307
+ status = "successful" if success else "failed"
308
+ message = f"Tool invocation: {tool_name} {status}"
309
+ if duration:
310
+ message += f" ({duration:.3f}s)"
311
+
312
+ if success:
313
+ self.log_info(message)
314
+ else:
315
+ self.log_warning(message)
@@ -0,0 +1,239 @@
1
+ """
2
+ MCP Gateway Exception Classes
3
+ =============================
4
+
5
+ Custom exceptions for MCP Gateway operations.
6
+
7
+ Part of ISS-0034: Infrastructure Setup - MCP Gateway Project Foundation
8
+ """
9
+
10
+ from typing import Optional, Any, Dict
11
+
12
+
13
+ class MCPException(Exception):
14
+ """
15
+ Base exception for all MCP Gateway errors.
16
+
17
+ WHY: We create a base exception to allow catching all MCP-related
18
+ errors in a single except block while still being able to handle
19
+ specific error types when needed.
20
+ """
21
+
22
+ def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
23
+ """
24
+ Initialize MCP exception.
25
+
26
+ Args:
27
+ message: Error message
28
+ details: Additional error details
29
+ """
30
+ super().__init__(message)
31
+ self.message = message
32
+ self.details = details or {}
33
+
34
+ def __str__(self) -> str:
35
+ """String representation of the exception."""
36
+ if self.details:
37
+ return f"{self.message} - Details: {self.details}"
38
+ return self.message
39
+
40
+
41
+ class MCPConfigurationError(MCPException):
42
+ """
43
+ Raised when MCP configuration is invalid or cannot be loaded.
44
+
45
+ Common scenarios:
46
+ - Missing required configuration fields
47
+ - Invalid configuration values
48
+ - Configuration file not found
49
+ - YAML parsing errors
50
+ """
51
+
52
+ def __init__(self, message: str, config_key: Optional[str] = None,
53
+ expected_type: Optional[str] = None):
54
+ """
55
+ Initialize configuration error.
56
+
57
+ Args:
58
+ message: Error message
59
+ config_key: Configuration key that caused the error
60
+ expected_type: Expected type for the configuration value
61
+ """
62
+ details = {}
63
+ if config_key:
64
+ details["config_key"] = config_key
65
+ if expected_type:
66
+ details["expected_type"] = expected_type
67
+
68
+ super().__init__(message, details)
69
+
70
+
71
+ class MCPToolNotFoundError(MCPException):
72
+ """
73
+ Raised when a requested tool is not found in the registry.
74
+
75
+ This error occurs when:
76
+ - Attempting to invoke a non-existent tool
77
+ - Trying to unregister a tool that isn't registered
78
+ - Searching for a tool that doesn't exist
79
+ """
80
+
81
+ def __init__(self, tool_name: str, available_tools: Optional[list] = None):
82
+ """
83
+ Initialize tool not found error.
84
+
85
+ Args:
86
+ tool_name: Name of the tool that wasn't found
87
+ available_tools: List of available tool names for reference
88
+ """
89
+ message = f"Tool '{tool_name}' not found in registry"
90
+ details = {"tool_name": tool_name}
91
+
92
+ if available_tools:
93
+ details["available_tools"] = available_tools
94
+ message += f". Available tools: {', '.join(available_tools)}"
95
+
96
+ super().__init__(message, details)
97
+
98
+
99
+ class MCPServerError(MCPException):
100
+ """
101
+ Raised when MCP server encounters an error.
102
+
103
+ Common scenarios:
104
+ - Server initialization failure
105
+ - Port binding issues
106
+ - Server crash during operation
107
+ - Invalid server state transitions
108
+ """
109
+
110
+ def __init__(self, message: str, server_state: Optional[str] = None,
111
+ error_code: Optional[int] = None):
112
+ """
113
+ Initialize server error.
114
+
115
+ Args:
116
+ message: Error message
117
+ server_state: Current server state when error occurred
118
+ error_code: Numeric error code if applicable
119
+ """
120
+ details = {}
121
+ if server_state:
122
+ details["server_state"] = server_state
123
+ if error_code:
124
+ details["error_code"] = error_code
125
+
126
+ super().__init__(message, details)
127
+
128
+
129
+ class MCPCommunicationError(MCPException):
130
+ """
131
+ Raised when communication with MCP client fails.
132
+
133
+ This includes:
134
+ - Stdio communication failures
135
+ - Message parsing errors
136
+ - Protocol violations
137
+ - Timeout errors
138
+ """
139
+
140
+ def __init__(self, message: str, direction: Optional[str] = None,
141
+ raw_data: Optional[str] = None):
142
+ """
143
+ Initialize communication error.
144
+
145
+ Args:
146
+ message: Error message
147
+ direction: Direction of communication ("send" or "receive")
148
+ raw_data: Raw data that caused the error (for debugging)
149
+ """
150
+ details = {}
151
+ if direction:
152
+ details["direction"] = direction
153
+ if raw_data and len(raw_data) < 1000: # Limit raw data size in exceptions
154
+ details["raw_data"] = raw_data
155
+ elif raw_data:
156
+ details["raw_data"] = raw_data[:1000] + "... (truncated)"
157
+
158
+ super().__init__(message, details)
159
+
160
+
161
+ class MCPValidationError(MCPException):
162
+ """
163
+ Raised when validation fails.
164
+
165
+ Used for:
166
+ - Tool parameter validation
167
+ - Schema validation
168
+ - Input validation
169
+ - Response validation
170
+ """
171
+
172
+ def __init__(self, message: str, field: Optional[str] = None,
173
+ expected: Optional[Any] = None, actual: Optional[Any] = None):
174
+ """
175
+ Initialize validation error.
176
+
177
+ Args:
178
+ message: Error message
179
+ field: Field that failed validation
180
+ expected: Expected value or type
181
+ actual: Actual value received
182
+ """
183
+ details = {}
184
+ if field:
185
+ details["field"] = field
186
+ if expected is not None:
187
+ details["expected"] = str(expected)
188
+ if actual is not None:
189
+ details["actual"] = str(actual)
190
+
191
+ super().__init__(message, details)
192
+
193
+
194
+ class MCPTimeoutError(MCPException):
195
+ """
196
+ Raised when an operation times out.
197
+
198
+ Common scenarios:
199
+ - Tool invocation timeout
200
+ - Server startup timeout
201
+ - Communication timeout
202
+ """
203
+
204
+ def __init__(self, operation: str, timeout_seconds: float):
205
+ """
206
+ Initialize timeout error.
207
+
208
+ Args:
209
+ operation: Operation that timed out
210
+ timeout_seconds: Timeout duration in seconds
211
+ """
212
+ message = f"Operation '{operation}' timed out after {timeout_seconds} seconds"
213
+ details = {
214
+ "operation": operation,
215
+ "timeout_seconds": timeout_seconds
216
+ }
217
+ super().__init__(message, details)
218
+
219
+
220
+ class MCPAuthenticationError(MCPException):
221
+ """
222
+ Raised when authentication fails.
223
+
224
+ For future use when MCP supports authentication.
225
+ """
226
+
227
+ def __init__(self, message: str, auth_method: Optional[str] = None):
228
+ """
229
+ Initialize authentication error.
230
+
231
+ Args:
232
+ message: Error message
233
+ auth_method: Authentication method that failed
234
+ """
235
+ details = {}
236
+ if auth_method:
237
+ details["auth_method"] = auth_method
238
+
239
+ super().__init__(message, details)