claude-mpm 3.9.8__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 (44) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/base_agent.json +1 -1
  3. claude_mpm/cli/__init__.py +3 -1
  4. claude_mpm/cli/commands/__init__.py +3 -1
  5. claude_mpm/cli/commands/cleanup.py +21 -1
  6. claude_mpm/cli/commands/mcp.py +821 -0
  7. claude_mpm/cli/parser.py +148 -1
  8. claude_mpm/config/memory_guardian_config.py +325 -0
  9. claude_mpm/constants.py +13 -0
  10. claude_mpm/hooks/claude_hooks/hook_handler.py +76 -19
  11. claude_mpm/models/state_models.py +433 -0
  12. claude_mpm/services/communication/__init__.py +2 -2
  13. claude_mpm/services/communication/socketio.py +18 -16
  14. claude_mpm/services/infrastructure/__init__.py +4 -1
  15. claude_mpm/services/infrastructure/logging.py +3 -3
  16. claude_mpm/services/infrastructure/memory_guardian.py +770 -0
  17. claude_mpm/services/mcp_gateway/__init__.py +28 -12
  18. claude_mpm/services/mcp_gateway/main.py +326 -0
  19. claude_mpm/services/mcp_gateway/registry/__init__.py +6 -3
  20. claude_mpm/services/mcp_gateway/registry/service_registry.py +397 -0
  21. claude_mpm/services/mcp_gateway/registry/tool_registry.py +477 -0
  22. claude_mpm/services/mcp_gateway/server/__init__.py +9 -3
  23. claude_mpm/services/mcp_gateway/server/mcp_server.py +430 -0
  24. claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +444 -0
  25. claude_mpm/services/mcp_gateway/server/stdio_handler.py +373 -0
  26. claude_mpm/services/mcp_gateway/tools/__init__.py +16 -3
  27. claude_mpm/services/mcp_gateway/tools/base_adapter.py +497 -0
  28. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +729 -0
  29. claude_mpm/services/mcp_gateway/tools/hello_world.py +551 -0
  30. claude_mpm/utils/file_utils.py +293 -0
  31. claude_mpm/utils/platform_memory.py +524 -0
  32. claude_mpm/utils/subprocess_utils.py +305 -0
  33. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.9.dist-info}/METADATA +3 -1
  34. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.9.dist-info}/RECORD +39 -28
  35. claude_mpm/agents/templates/.claude-mpm/memories/README.md +0 -36
  36. claude_mpm/agents/templates/.claude-mpm/memories/engineer_agent.md +0 -39
  37. claude_mpm/agents/templates/.claude-mpm/memories/qa_agent.md +0 -38
  38. claude_mpm/agents/templates/.claude-mpm/memories/research_agent.md +0 -39
  39. claude_mpm/agents/templates/.claude-mpm/memories/version_control_agent.md +0 -38
  40. /claude_mpm/agents/templates/{research_memory_efficient.json → backup/research_memory_efficient.json} +0 -0
  41. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.9.dist-info}/WHEEL +0 -0
  42. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.9.dist-info}/entry_points.txt +0 -0
  43. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.9.dist-info}/licenses/LICENSE +0 -0
  44. {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,477 @@
1
+ """
2
+ MCP Tool Registry Implementation
3
+ =================================
4
+
5
+ Comprehensive tool registry system for managing MCP tools.
6
+ Provides registration, discovery, and invocation capabilities.
7
+
8
+ Part of ISS-0035: MCP Server Implementation - Core Server and Tool Registry
9
+ """
10
+
11
+ import asyncio
12
+ from typing import Dict, List, Optional, Set, Any
13
+ from datetime import datetime
14
+ import re
15
+ import traceback
16
+ from threading import RLock
17
+
18
+ from claude_mpm.services.mcp_gateway.core.interfaces import (
19
+ IMCPToolRegistry,
20
+ IMCPToolAdapter,
21
+ MCPToolDefinition,
22
+ MCPToolInvocation,
23
+ MCPToolResult,
24
+ )
25
+ from claude_mpm.services.mcp_gateway.core.base import BaseMCPService
26
+
27
+
28
+ class ToolRegistry(BaseMCPService, IMCPToolRegistry):
29
+ """
30
+ Thread-safe tool registry implementation.
31
+
32
+ WHY: We need a centralized registry to manage all MCP tools, ensuring
33
+ thread safety for concurrent access, efficient tool discovery, and
34
+ proper lifecycle management.
35
+
36
+ DESIGN DECISIONS:
37
+ - Use RLock for thread safety to allow recursive locking
38
+ - Maintain both adapters and definitions for fast access
39
+ - Implement caching for frequently used tools
40
+ - Track metrics for monitoring and optimization
41
+ - Support pattern-based search for tool discovery
42
+ """
43
+
44
+ def __init__(self):
45
+ """Initialize the tool registry."""
46
+ super().__init__("ToolRegistry")
47
+
48
+ # Thread safety
49
+ self._lock = RLock()
50
+
51
+ # Tool storage
52
+ self._adapters: Dict[str, IMCPToolAdapter] = {}
53
+ self._definitions: Dict[str, MCPToolDefinition] = {}
54
+
55
+ # Tool categories for organization
56
+ self._categories: Dict[str, Set[str]] = {
57
+ "system": set(),
58
+ "user": set(),
59
+ "builtin": set(),
60
+ "external": set()
61
+ }
62
+
63
+ # Metrics
64
+ self._metrics = {
65
+ "total_tools": 0,
66
+ "invocations": {}, # Per-tool invocation counts
67
+ "errors": {}, # Per-tool error counts
68
+ "last_invocation": {}, # Per-tool last invocation time
69
+ "registration_time": {} # Per-tool registration time
70
+ }
71
+
72
+ # Cache for frequently used tools (LRU-style)
73
+ self._cache_size = 10
74
+ self._tool_cache: List[str] = []
75
+
76
+ async def _do_initialize(self) -> bool:
77
+ """
78
+ Initialize the tool registry.
79
+
80
+ Returns:
81
+ True if initialization successful
82
+ """
83
+ try:
84
+ self.log_info("Initializing tool registry")
85
+
86
+ # Clear any existing data
87
+ with self._lock:
88
+ self._adapters.clear()
89
+ self._definitions.clear()
90
+ for category in self._categories.values():
91
+ category.clear()
92
+ self._tool_cache.clear()
93
+
94
+ self.log_info("Tool registry initialized")
95
+ return True
96
+
97
+ except Exception as e:
98
+ self.log_error(f"Failed to initialize tool registry: {e}")
99
+ return False
100
+
101
+ async def _do_shutdown(self) -> None:
102
+ """Shutdown the tool registry and clean up resources."""
103
+ self.log_info("Shutting down tool registry")
104
+
105
+ # Shutdown all tool adapters
106
+ with self._lock:
107
+ for tool_name, adapter in self._adapters.items():
108
+ try:
109
+ self.log_info(f"Shutting down tool adapter: {tool_name}")
110
+ await adapter.shutdown()
111
+ except Exception as e:
112
+ self.log_error(f"Error shutting down tool {tool_name}: {e}")
113
+
114
+ # Clear all data
115
+ self._adapters.clear()
116
+ self._definitions.clear()
117
+ for category in self._categories.values():
118
+ category.clear()
119
+ self._tool_cache.clear()
120
+
121
+ self.log_info("Tool registry shutdown complete")
122
+
123
+ def register_tool(self, adapter: IMCPToolAdapter, category: str = "user") -> bool:
124
+ """
125
+ Register a tool adapter.
126
+
127
+ Args:
128
+ adapter: Tool adapter to register
129
+ category: Tool category (system, user, builtin, external)
130
+
131
+ Returns:
132
+ True if registration successful
133
+
134
+ WHY: We validate the adapter and its definition before registration
135
+ to ensure only valid tools are added to the registry.
136
+ """
137
+ try:
138
+ # Get tool definition
139
+ definition = adapter.get_definition()
140
+ tool_name = definition.name
141
+
142
+ self.log_info(f"Registering tool: {tool_name} (category: {category})")
143
+
144
+ with self._lock:
145
+ # Check if tool already registered
146
+ if tool_name in self._adapters:
147
+ self.log_warning(f"Tool already registered: {tool_name}")
148
+ return False
149
+
150
+ # Validate category
151
+ if category not in self._categories:
152
+ self.log_warning(f"Invalid category: {category}, using 'user'")
153
+ category = "user"
154
+
155
+ # Register the tool
156
+ self._adapters[tool_name] = adapter
157
+ self._definitions[tool_name] = definition
158
+ self._categories[category].add(tool_name)
159
+
160
+ # Update metrics
161
+ self._metrics["total_tools"] = len(self._adapters)
162
+ self._metrics["registration_time"][tool_name] = datetime.now().isoformat()
163
+ self._metrics["invocations"][tool_name] = 0
164
+ self._metrics["errors"][tool_name] = 0
165
+
166
+ self.log_info(f"Tool registered successfully: {tool_name}")
167
+ self.log_mcp_event("tool_registered", {"tool": tool_name, "category": category})
168
+
169
+ return True
170
+
171
+ except Exception as e:
172
+ self.log_error(f"Failed to register tool: {e}")
173
+ self.log_error(f"Traceback: {traceback.format_exc()}")
174
+ return False
175
+
176
+ def unregister_tool(self, tool_name: str) -> bool:
177
+ """
178
+ Unregister a tool by name.
179
+
180
+ Args:
181
+ tool_name: Name of tool to unregister
182
+
183
+ Returns:
184
+ True if unregistration successful
185
+ """
186
+ try:
187
+ self.log_info(f"Unregistering tool: {tool_name}")
188
+
189
+ with self._lock:
190
+ # Check if tool exists
191
+ if tool_name not in self._adapters:
192
+ self.log_warning(f"Tool not found: {tool_name}")
193
+ return False
194
+
195
+ # Get the adapter for shutdown
196
+ adapter = self._adapters[tool_name]
197
+
198
+ # Remove from all storage
199
+ del self._adapters[tool_name]
200
+ del self._definitions[tool_name]
201
+
202
+ # Remove from categories
203
+ for category_tools in self._categories.values():
204
+ category_tools.discard(tool_name)
205
+
206
+ # Remove from cache
207
+ if tool_name in self._tool_cache:
208
+ self._tool_cache.remove(tool_name)
209
+
210
+ # Update metrics
211
+ self._metrics["total_tools"] = len(self._adapters)
212
+
213
+ # Shutdown adapter (outside lock to avoid deadlock)
214
+ try:
215
+ asyncio.create_task(adapter.shutdown())
216
+ except Exception as e:
217
+ self.log_warning(f"Error shutting down tool adapter {tool_name}: {e}")
218
+
219
+ self.log_info(f"Tool unregistered successfully: {tool_name}")
220
+ self.log_mcp_event("tool_unregistered", {"tool": tool_name})
221
+
222
+ return True
223
+
224
+ except Exception as e:
225
+ self.log_error(f"Failed to unregister tool {tool_name}: {e}")
226
+ return False
227
+
228
+ def get_tool(self, tool_name: str) -> Optional[IMCPToolAdapter]:
229
+ """
230
+ Get a tool adapter by name.
231
+
232
+ Args:
233
+ tool_name: Name of the tool
234
+
235
+ Returns:
236
+ Tool adapter if found, None otherwise
237
+ """
238
+ with self._lock:
239
+ adapter = self._adapters.get(tool_name)
240
+
241
+ # Update cache if tool found
242
+ if adapter and tool_name not in self._tool_cache:
243
+ self._update_cache(tool_name)
244
+
245
+ return adapter
246
+
247
+ def list_tools(self) -> List[MCPToolDefinition]:
248
+ """
249
+ List all registered tools.
250
+
251
+ Returns:
252
+ List of tool definitions
253
+ """
254
+ with self._lock:
255
+ return list(self._definitions.values())
256
+
257
+ async def invoke_tool(self, invocation: MCPToolInvocation) -> MCPToolResult:
258
+ """
259
+ Invoke a tool through the registry.
260
+
261
+ Args:
262
+ invocation: Tool invocation request
263
+
264
+ Returns:
265
+ Tool execution result
266
+
267
+ WHY: We handle invocation through the registry to provide centralized
268
+ error handling, metrics tracking, and validation.
269
+ """
270
+ tool_name = invocation.tool_name
271
+ start_time = datetime.now()
272
+
273
+ try:
274
+ self.log_info(f"Invoking tool: {tool_name}")
275
+
276
+ # Get the adapter
277
+ adapter = self.get_tool(tool_name)
278
+ if not adapter:
279
+ error_msg = f"Tool not found: {tool_name}"
280
+ self.log_error(error_msg)
281
+ return MCPToolResult(
282
+ success=False,
283
+ error=error_msg
284
+ )
285
+
286
+ # Validate parameters
287
+ if not adapter.validate_parameters(invocation.parameters):
288
+ error_msg = f"Invalid parameters for tool {tool_name}"
289
+ self.log_error(error_msg)
290
+
291
+ with self._lock:
292
+ self._metrics["errors"][tool_name] = self._metrics["errors"].get(tool_name, 0) + 1
293
+
294
+ return MCPToolResult(
295
+ success=False,
296
+ error=error_msg
297
+ )
298
+
299
+ # Invoke the tool
300
+ result = await adapter.invoke(invocation)
301
+
302
+ # Calculate execution time
303
+ execution_time = (datetime.now() - start_time).total_seconds()
304
+ result.execution_time = execution_time
305
+
306
+ # Update metrics
307
+ with self._lock:
308
+ self._metrics["invocations"][tool_name] = self._metrics["invocations"].get(tool_name, 0) + 1
309
+ self._metrics["last_invocation"][tool_name] = datetime.now().isoformat()
310
+
311
+ if not result.success:
312
+ self._metrics["errors"][tool_name] = self._metrics["errors"].get(tool_name, 0) + 1
313
+
314
+ # Log the invocation
315
+ self.log_tool_invocation(tool_name, result.success, execution_time)
316
+
317
+ return result
318
+
319
+ except Exception as e:
320
+ error_msg = f"Exception invoking tool {tool_name}: {str(e)}"
321
+ self.log_error(error_msg)
322
+ self.log_error(f"Traceback: {traceback.format_exc()}")
323
+
324
+ # Update error metrics
325
+ with self._lock:
326
+ self._metrics["errors"][tool_name] = self._metrics["errors"].get(tool_name, 0) + 1
327
+
328
+ execution_time = (datetime.now() - start_time).total_seconds()
329
+
330
+ return MCPToolResult(
331
+ success=False,
332
+ error=error_msg,
333
+ execution_time=execution_time
334
+ )
335
+
336
+ def search_tools(self, query: str) -> List[MCPToolDefinition]:
337
+ """
338
+ Search for tools by query.
339
+
340
+ Supports:
341
+ - Exact name matching
342
+ - Partial name matching
343
+ - Description keyword matching
344
+ - Regex patterns
345
+
346
+ Args:
347
+ query: Search query
348
+
349
+ Returns:
350
+ List of matching tool definitions
351
+ """
352
+ try:
353
+ query_lower = query.lower()
354
+ matching_tools = []
355
+
356
+ with self._lock:
357
+ for name, definition in self._definitions.items():
358
+ # Exact match
359
+ if name.lower() == query_lower:
360
+ matching_tools.append(definition)
361
+ continue
362
+
363
+ # Partial name match
364
+ if query_lower in name.lower():
365
+ matching_tools.append(definition)
366
+ continue
367
+
368
+ # Description match
369
+ if query_lower in definition.description.lower():
370
+ matching_tools.append(definition)
371
+ continue
372
+
373
+ # Try regex match
374
+ try:
375
+ if re.search(query, name, re.IGNORECASE):
376
+ matching_tools.append(definition)
377
+ continue
378
+ except re.error:
379
+ # Not a valid regex, skip
380
+ pass
381
+
382
+ self.log_info(f"Search query '{query}' returned {len(matching_tools)} tools")
383
+ return matching_tools
384
+
385
+ except Exception as e:
386
+ self.log_error(f"Error searching tools: {e}")
387
+ return []
388
+
389
+ def get_tools_by_category(self, category: str) -> List[MCPToolDefinition]:
390
+ """
391
+ Get all tools in a specific category.
392
+
393
+ Args:
394
+ category: Category name (system, user, builtin, external)
395
+
396
+ Returns:
397
+ List of tool definitions in the category
398
+ """
399
+ with self._lock:
400
+ if category not in self._categories:
401
+ self.log_warning(f"Invalid category: {category}")
402
+ return []
403
+
404
+ tool_names = self._categories[category]
405
+ return [self._definitions[name] for name in tool_names if name in self._definitions]
406
+
407
+ def _update_cache(self, tool_name: str) -> None:
408
+ """
409
+ Update the tool cache (LRU-style).
410
+
411
+ Args:
412
+ tool_name: Name of tool to add to cache
413
+ """
414
+ # Remove if already in cache
415
+ if tool_name in self._tool_cache:
416
+ self._tool_cache.remove(tool_name)
417
+
418
+ # Add to front
419
+ self._tool_cache.insert(0, tool_name)
420
+
421
+ # Trim cache if needed
422
+ if len(self._tool_cache) > self._cache_size:
423
+ self._tool_cache = self._tool_cache[:self._cache_size]
424
+
425
+ def get_metrics(self) -> Dict[str, Any]:
426
+ """
427
+ Get registry metrics.
428
+
429
+ Returns:
430
+ Metrics dictionary
431
+ """
432
+ with self._lock:
433
+ return {
434
+ **self._metrics,
435
+ "categories": {
436
+ category: len(tools)
437
+ for category, tools in self._categories.items()
438
+ },
439
+ "cached_tools": list(self._tool_cache)
440
+ }
441
+
442
+ def clear_metrics(self) -> None:
443
+ """Clear all metrics except registration data."""
444
+ with self._lock:
445
+ for tool_name in self._metrics["invocations"]:
446
+ self._metrics["invocations"][tool_name] = 0
447
+ self._metrics["errors"][tool_name] = 0
448
+ self._metrics["last_invocation"].clear()
449
+
450
+ async def initialize_all_tools(self) -> Dict[str, bool]:
451
+ """
452
+ Initialize all registered tool adapters.
453
+
454
+ Returns:
455
+ Dictionary mapping tool names to initialization success
456
+ """
457
+ results = {}
458
+
459
+ with self._lock:
460
+ adapters_copy = dict(self._adapters)
461
+
462
+ for tool_name, adapter in adapters_copy.items():
463
+ try:
464
+ self.log_info(f"Initializing tool: {tool_name}")
465
+ success = await adapter.initialize()
466
+ results[tool_name] = success
467
+
468
+ if success:
469
+ self.log_info(f"Tool initialized successfully: {tool_name}")
470
+ else:
471
+ self.log_warning(f"Tool initialization failed: {tool_name}")
472
+
473
+ except Exception as e:
474
+ self.log_error(f"Exception initializing tool {tool_name}: {e}")
475
+ results[tool_name] = False
476
+
477
+ return results
@@ -2,8 +2,14 @@
2
2
  MCP Gateway Server Module
3
3
  =========================
4
4
 
5
- Server implementation for the MCP Gateway.
5
+ Server components for the MCP Gateway service.
6
6
  """
7
7
 
8
- # Placeholder for future implementation
9
- # Will be implemented in ISS-0035: MCP Server Core Implementation
8
+ from .mcp_server import MCPServer
9
+ from .stdio_handler import StdioHandler, AlternativeStdioHandler
10
+
11
+ __all__ = [
12
+ "MCPServer",
13
+ "StdioHandler",
14
+ "AlternativeStdioHandler",
15
+ ]