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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/templates/ticketing.json +1 -1
- claude_mpm/cli/__init__.py +3 -1
- claude_mpm/cli/commands/__init__.py +3 -1
- claude_mpm/cli/commands/cleanup.py +21 -1
- claude_mpm/cli/commands/mcp.py +821 -0
- claude_mpm/cli/parser.py +148 -1
- claude_mpm/config/memory_guardian_config.py +325 -0
- claude_mpm/constants.py +13 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +76 -19
- claude_mpm/models/state_models.py +433 -0
- claude_mpm/services/__init__.py +28 -0
- claude_mpm/services/communication/__init__.py +2 -2
- claude_mpm/services/communication/socketio.py +18 -16
- claude_mpm/services/infrastructure/__init__.py +4 -1
- claude_mpm/services/infrastructure/logging.py +3 -3
- claude_mpm/services/infrastructure/memory_guardian.py +770 -0
- claude_mpm/services/mcp_gateway/__init__.py +138 -0
- claude_mpm/services/mcp_gateway/config/__init__.py +17 -0
- claude_mpm/services/mcp_gateway/config/config_loader.py +232 -0
- claude_mpm/services/mcp_gateway/config/config_schema.py +234 -0
- claude_mpm/services/mcp_gateway/config/configuration.py +371 -0
- claude_mpm/services/mcp_gateway/core/__init__.py +51 -0
- claude_mpm/services/mcp_gateway/core/base.py +315 -0
- claude_mpm/services/mcp_gateway/core/exceptions.py +239 -0
- claude_mpm/services/mcp_gateway/core/interfaces.py +476 -0
- claude_mpm/services/mcp_gateway/main.py +326 -0
- claude_mpm/services/mcp_gateway/registry/__init__.py +12 -0
- claude_mpm/services/mcp_gateway/registry/service_registry.py +397 -0
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +477 -0
- claude_mpm/services/mcp_gateway/server/__init__.py +15 -0
- claude_mpm/services/mcp_gateway/server/mcp_server.py +430 -0
- claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +444 -0
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +373 -0
- claude_mpm/services/mcp_gateway/tools/__init__.py +22 -0
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +497 -0
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +729 -0
- claude_mpm/services/mcp_gateway/tools/hello_world.py +551 -0
- claude_mpm/utils/file_utils.py +293 -0
- claude_mpm/utils/platform_memory.py +524 -0
- claude_mpm/utils/subprocess_utils.py +305 -0
- {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/METADATA +4 -1
- {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/RECORD +49 -26
- claude_mpm/agents/templates/.claude-mpm/memories/README.md +0 -36
- claude_mpm/agents/templates/.claude-mpm/memories/engineer_agent.md +0 -39
- claude_mpm/agents/templates/.claude-mpm/memories/qa_agent.md +0 -38
- claude_mpm/agents/templates/.claude-mpm/memories/research_agent.md +0 -39
- claude_mpm/agents/templates/.claude-mpm/memories/version_control_agent.md +0 -38
- /claude_mpm/agents/templates/{research_memory_efficient.json → backup/research_memory_efficient.json} +0 -0
- {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.9.7.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
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            """
         | 
| 2 | 
            +
            MCP Gateway Server Module
         | 
| 3 | 
            +
            =========================
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Server components for the MCP Gateway service.
         | 
| 6 | 
            +
            """
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            from .mcp_server import MCPServer
         | 
| 9 | 
            +
            from .stdio_handler import StdioHandler, AlternativeStdioHandler
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            __all__ = [
         | 
| 12 | 
            +
                "MCPServer",
         | 
| 13 | 
            +
                "StdioHandler",
         | 
| 14 | 
            +
                "AlternativeStdioHandler",
         | 
| 15 | 
            +
            ]
         |