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