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,444 @@
1
+ """
2
+ Simple MCP Server Implementation
3
+ =================================
4
+
5
+ A simplified MCP server implementation that follows the MCP protocol
6
+ without requiring the official MCP package.
7
+
8
+ Part of ISS-0035: MCP Server Implementation - Core Server and Tool Registry
9
+ """
10
+
11
+ import asyncio
12
+ import json
13
+ import sys
14
+ from typing import Any, Dict, List, Optional, Callable, Set
15
+ from datetime import datetime
16
+ import traceback
17
+
18
+ from claude_mpm.services.mcp_gateway.core.interfaces import (
19
+ IMCPServer,
20
+ IMCPToolRegistry,
21
+ IMCPCommunication,
22
+ MCPToolInvocation,
23
+ MCPToolResult,
24
+ )
25
+ from claude_mpm.services.mcp_gateway.core.base import BaseMCPService, MCPServiceState
26
+
27
+
28
+ class SimpleMCPServer(BaseMCPService, IMCPServer):
29
+ """
30
+ Simplified MCP Server implementation using JSON-RPC over stdio.
31
+
32
+ WHY: This implementation provides MCP protocol compatibility without
33
+ requiring the official MCP package, making it more portable and testable.
34
+
35
+ DESIGN DECISIONS:
36
+ - Use standard JSON-RPC 2.0 protocol
37
+ - Implement core MCP methods (initialize, tools/list, tools/call)
38
+ - Handle stdio communication directly
39
+ - Maintain compatibility with Claude Desktop's expectations
40
+ """
41
+
42
+ def __init__(self, server_name: str = "claude-mpm-mcp", version: str = "1.0.0"):
43
+ """
44
+ Initialize Simple MCP Server.
45
+
46
+ Args:
47
+ server_name: Name of the MCP server
48
+ version: Server version
49
+ """
50
+ super().__init__(f"SimpleMCPServer-{server_name}")
51
+
52
+ # Server configuration
53
+ self.server_name = server_name
54
+ self.version = version
55
+
56
+ # Dependencies (injected via setters)
57
+ self._tool_registry: Optional[IMCPToolRegistry] = None
58
+ self._communication: Optional[IMCPCommunication] = None
59
+
60
+ # Request handlers
61
+ self._handlers: Dict[str, Callable] = {}
62
+
63
+ # Server capabilities
64
+ self._capabilities = {
65
+ "tools": {},
66
+ "prompts": {},
67
+ "resources": {},
68
+ "experimental": {}
69
+ }
70
+
71
+ # Metrics
72
+ self._metrics = {
73
+ "requests_handled": 0,
74
+ "errors": 0,
75
+ "tool_invocations": 0,
76
+ "start_time": None,
77
+ "last_request_time": None
78
+ }
79
+
80
+ # Running state
81
+ self._run_task: Optional[asyncio.Task] = None
82
+ self._shutdown_event = asyncio.Event()
83
+
84
+ # Setup default handlers
85
+ self._setup_default_handlers()
86
+
87
+ def _setup_default_handlers(self) -> None:
88
+ """
89
+ Setup default MCP protocol handlers.
90
+
91
+ WHY: The MCP protocol requires specific handlers for initialization,
92
+ tool discovery, and tool invocation. We set these up to ensure
93
+ protocol compliance.
94
+ """
95
+ # Initialize handler
96
+ async def handle_initialize(params: Dict[str, Any]) -> Dict[str, Any]:
97
+ """Handle initialize request."""
98
+ self.log_info("Handling initialize request")
99
+
100
+ return {
101
+ "protocolVersion": "2024-11-05",
102
+ "capabilities": self.get_capabilities(),
103
+ "serverInfo": {
104
+ "name": self.server_name,
105
+ "version": self.version
106
+ }
107
+ }
108
+
109
+ self._handlers["initialize"] = handle_initialize
110
+
111
+ # Tools list handler
112
+ async def handle_tools_list(params: Dict[str, Any]) -> Dict[str, Any]:
113
+ """Handle tools/list request."""
114
+ self.log_info("Handling tools/list request")
115
+
116
+ if not self._tool_registry:
117
+ self.log_warning("No tool registry available")
118
+ return {"tools": []}
119
+
120
+ tools = []
121
+ for tool_def in self._tool_registry.list_tools():
122
+ tool = {
123
+ "name": tool_def.name,
124
+ "description": tool_def.description,
125
+ "inputSchema": tool_def.input_schema
126
+ }
127
+ tools.append(tool)
128
+
129
+ self.log_info(f"Returning {len(tools)} tools")
130
+ return {"tools": tools}
131
+
132
+ self._handlers["tools/list"] = handle_tools_list
133
+
134
+ # Tools call handler
135
+ async def handle_tools_call(params: Dict[str, Any]) -> Dict[str, Any]:
136
+ """Handle tools/call request."""
137
+ name = params.get("name", "")
138
+ arguments = params.get("arguments", {})
139
+
140
+ self.log_info(f"Handling tools/call request for tool: {name}")
141
+
142
+ if not self._tool_registry:
143
+ error_msg = "No tool registry available"
144
+ self.log_error(error_msg)
145
+ return {
146
+ "content": [
147
+ {"type": "text", "text": f"Error: {error_msg}"}
148
+ ]
149
+ }
150
+
151
+ # Create invocation request
152
+ invocation = MCPToolInvocation(
153
+ tool_name=name,
154
+ parameters=arguments,
155
+ request_id=f"req_{datetime.now().timestamp()}"
156
+ )
157
+
158
+ try:
159
+ # Invoke tool through registry
160
+ result = await self._tool_registry.invoke_tool(invocation)
161
+
162
+ # Update metrics
163
+ self._metrics["tool_invocations"] += 1
164
+
165
+ # Log invocation
166
+ self.log_tool_invocation(name, result.success, result.execution_time)
167
+
168
+ if result.success:
169
+ # Return successful result
170
+ if isinstance(result.data, str):
171
+ content = [{"type": "text", "text": result.data}]
172
+ else:
173
+ content = [{"type": "text", "text": json.dumps(result.data, indent=2)}]
174
+ else:
175
+ # Return error
176
+ content = [{"type": "text", "text": f"Error: {result.error}"}]
177
+
178
+ return {"content": content}
179
+
180
+ except Exception as e:
181
+ error_msg = f"Failed to invoke tool {name}: {str(e)}"
182
+ self.log_error(error_msg)
183
+ self._metrics["errors"] += 1
184
+ return {
185
+ "content": [
186
+ {"type": "text", "text": f"Error: {error_msg}"}
187
+ ]
188
+ }
189
+
190
+ self._handlers["tools/call"] = handle_tools_call
191
+
192
+ # Ping handler
193
+ async def handle_ping(params: Dict[str, Any]) -> Dict[str, Any]:
194
+ """Handle ping request."""
195
+ return {}
196
+
197
+ self._handlers["ping"] = handle_ping
198
+
199
+ def set_tool_registry(self, registry: IMCPToolRegistry) -> None:
200
+ """
201
+ Set the tool registry for the server.
202
+
203
+ Args:
204
+ registry: Tool registry to use
205
+ """
206
+ self._tool_registry = registry
207
+ self.log_info("Tool registry set")
208
+
209
+ def set_communication(self, communication: IMCPCommunication) -> None:
210
+ """
211
+ Set the communication handler.
212
+
213
+ Args:
214
+ communication: Communication handler to use
215
+ """
216
+ self._communication = communication
217
+ self.log_info("Communication handler set")
218
+
219
+ async def _do_initialize(self) -> bool:
220
+ """
221
+ Perform server initialization.
222
+
223
+ Returns:
224
+ True if initialization successful
225
+ """
226
+ try:
227
+ self.log_info("Initializing Simple MCP server components")
228
+
229
+ # Validate dependencies
230
+ if not self._tool_registry:
231
+ self.log_warning("No tool registry set - server will have no tools")
232
+
233
+ # Initialize metrics
234
+ self._metrics["start_time"] = datetime.now().isoformat()
235
+
236
+ # Update capabilities based on registry
237
+ if self._tool_registry:
238
+ tools = self._tool_registry.list_tools()
239
+ self._capabilities["tools"]["available"] = len(tools)
240
+ self._capabilities["tools"]["names"] = [t.name for t in tools]
241
+
242
+ self.log_info("Simple MCP server initialization complete")
243
+ return True
244
+
245
+ except Exception as e:
246
+ self.log_error(f"Failed to initialize Simple MCP server: {e}")
247
+ return False
248
+
249
+ async def _do_start(self) -> bool:
250
+ """
251
+ Start the MCP server.
252
+
253
+ Returns:
254
+ True if startup successful
255
+ """
256
+ try:
257
+ self.log_info("Starting Simple MCP server")
258
+
259
+ # Clear shutdown event
260
+ self._shutdown_event.clear()
261
+
262
+ # Start the run task
263
+ self._run_task = asyncio.create_task(self.run())
264
+
265
+ self.log_info("Simple MCP server started successfully")
266
+ return True
267
+
268
+ except Exception as e:
269
+ self.log_error(f"Failed to start Simple MCP server: {e}")
270
+ return False
271
+
272
+ async def _do_shutdown(self) -> None:
273
+ """
274
+ Shutdown the MCP server gracefully.
275
+ """
276
+ self.log_info("Shutting down Simple MCP server")
277
+
278
+ # Signal shutdown
279
+ self._shutdown_event.set()
280
+
281
+ # Cancel run task if active
282
+ if self._run_task and not self._run_task.done():
283
+ self._run_task.cancel()
284
+ try:
285
+ await self._run_task
286
+ except asyncio.CancelledError:
287
+ pass
288
+
289
+ # Clean up resources
290
+ if self._tool_registry:
291
+ self.log_info("Cleaning up tool registry")
292
+ # Tool registry cleanup if needed
293
+
294
+ self.log_info("Simple MCP server shutdown complete")
295
+
296
+ async def stop(self) -> None:
297
+ """
298
+ Stop the MCP service gracefully.
299
+
300
+ This implements the IMCPLifecycle interface method.
301
+ """
302
+ await self.shutdown()
303
+
304
+ async def handle_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
305
+ """
306
+ Handle an MCP request.
307
+
308
+ This method routes requests to appropriate handlers based on the method.
309
+
310
+ Args:
311
+ request: MCP request message
312
+
313
+ Returns:
314
+ Response message
315
+ """
316
+ try:
317
+ # Update metrics
318
+ self._metrics["requests_handled"] += 1
319
+ self._metrics["last_request_time"] = datetime.now().isoformat()
320
+
321
+ # Extract request details
322
+ method = request.get("method", "")
323
+ params = request.get("params", {})
324
+ request_id = request.get("id")
325
+
326
+ self.log_debug(f"Handling request: {method}")
327
+
328
+ # Check for handler
329
+ if method in self._handlers:
330
+ handler = self._handlers[method]
331
+ result = await handler(params)
332
+
333
+ # Build response
334
+ response = {
335
+ "jsonrpc": "2.0",
336
+ "id": request_id,
337
+ "result": result
338
+ }
339
+ else:
340
+ # Unknown method
341
+ self.log_warning(f"Unknown method: {method}")
342
+ response = {
343
+ "jsonrpc": "2.0",
344
+ "id": request_id,
345
+ "error": {
346
+ "code": -32601,
347
+ "message": f"Method not found: {method}"
348
+ }
349
+ }
350
+
351
+ return response
352
+
353
+ except Exception as e:
354
+ self.log_error(f"Error handling request: {e}")
355
+ self._metrics["errors"] += 1
356
+
357
+ return {
358
+ "jsonrpc": "2.0",
359
+ "id": request.get("id"),
360
+ "error": {
361
+ "code": -32603,
362
+ "message": f"Internal error: {str(e)}"
363
+ }
364
+ }
365
+
366
+ async def run(self) -> None:
367
+ """
368
+ Run the MCP server main loop.
369
+
370
+ This method handles incoming requests using the communication handler.
371
+
372
+ WHY: We use a simple request/response loop that works with any
373
+ communication handler (stdio, websocket, etc).
374
+ """
375
+ try:
376
+ self.log_info("Starting Simple MCP server main loop")
377
+
378
+ # If we have a communication handler, use it
379
+ if self._communication:
380
+ while not self._shutdown_event.is_set():
381
+ try:
382
+ # Receive message
383
+ message = await self._communication.receive_message()
384
+ if message:
385
+ # Handle request
386
+ response = await self.handle_request(message)
387
+ # Send response
388
+ await self._communication.send_message(response)
389
+ else:
390
+ # No message, wait a bit
391
+ await asyncio.sleep(0.1)
392
+ except Exception as e:
393
+ self.log_error(f"Error in message loop: {e}")
394
+ self._metrics["errors"] += 1
395
+ else:
396
+ # No communication handler, just wait for shutdown
397
+ self.log_warning("No communication handler set, waiting for shutdown")
398
+ await self._shutdown_event.wait()
399
+
400
+ self.log_info("Simple MCP server main loop ended")
401
+
402
+ except Exception as e:
403
+ self.log_error(f"Error in Simple MCP server main loop: {e}")
404
+ self.log_error(f"Traceback: {traceback.format_exc()}")
405
+ self._metrics["errors"] += 1
406
+ raise
407
+
408
+ def register_handler(self, method: str, handler: Callable) -> None:
409
+ """
410
+ Register a custom request handler.
411
+
412
+ Args:
413
+ method: Method name to handle
414
+ handler: Handler function
415
+ """
416
+ self._handlers[method] = handler
417
+ self.log_info(f"Registered handler for method: {method}")
418
+
419
+ def get_capabilities(self) -> Dict[str, Any]:
420
+ """
421
+ Get server capabilities.
422
+
423
+ Returns:
424
+ Dictionary of server capabilities formatted for MCP protocol
425
+ """
426
+ capabilities = {}
427
+
428
+ # Add tool capabilities if registry is available
429
+ if self._tool_registry:
430
+ capabilities["tools"] = {}
431
+
432
+ # Add experimental features
433
+ capabilities["experimental"] = {}
434
+
435
+ return capabilities
436
+
437
+ def get_metrics(self) -> Dict[str, Any]:
438
+ """
439
+ Get server metrics.
440
+
441
+ Returns:
442
+ Server metrics dictionary
443
+ """
444
+ return self._metrics.copy()