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,138 @@ | |
| 1 | 
            +
            """
         | 
| 2 | 
            +
            MCP Gateway Service Module
         | 
| 3 | 
            +
            ==========================
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            This module provides the Model Context Protocol (MCP) gateway implementation for Claude MPM.
         | 
| 6 | 
            +
            It enables integration with MCP-compatible tools and services through a standardized interface.
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            Part of ISS-0034: Infrastructure Setup - MCP Gateway Project Foundation
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            The MCP Gateway follows the claude-mpm service-oriented architecture with:
         | 
| 11 | 
            +
            - Interface-based contracts for all components
         | 
| 12 | 
            +
            - Dependency injection for service resolution
         | 
| 13 | 
            +
            - Lazy loading for performance optimization
         | 
| 14 | 
            +
            - Comprehensive error handling and logging
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            Structure:
         | 
| 17 | 
            +
            - core/: Core interfaces and base classes for MCP services
         | 
| 18 | 
            +
            - server/: MCP server implementation and lifecycle management
         | 
| 19 | 
            +
            - tools/: Tool registry and tool adapter implementations
         | 
| 20 | 
            +
            - config/: Configuration management for MCP Gateway
         | 
| 21 | 
            +
            - registry/: Service discovery and registration
         | 
| 22 | 
            +
            """
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            # Version information
         | 
| 25 | 
            +
            __version__ = "0.1.0"
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            # Lazy imports to prevent circular dependencies and improve startup performance
         | 
| 28 | 
            +
            def __getattr__(name):
         | 
| 29 | 
            +
                """Lazy import mechanism for MCP Gateway components."""
         | 
| 30 | 
            +
                
         | 
| 31 | 
            +
                # Core interfaces and base classes
         | 
| 32 | 
            +
                if name == "IMCPServer":
         | 
| 33 | 
            +
                    from .core.interfaces import IMCPServer
         | 
| 34 | 
            +
                    return IMCPServer
         | 
| 35 | 
            +
                elif name == "IMCPToolRegistry":
         | 
| 36 | 
            +
                    from .core.interfaces import IMCPToolRegistry
         | 
| 37 | 
            +
                    return IMCPToolRegistry
         | 
| 38 | 
            +
                elif name == "IMCPConfiguration":
         | 
| 39 | 
            +
                    from .core.interfaces import IMCPConfiguration
         | 
| 40 | 
            +
                    return IMCPConfiguration
         | 
| 41 | 
            +
                elif name == "IMCPToolAdapter":
         | 
| 42 | 
            +
                    from .core.interfaces import IMCPToolAdapter
         | 
| 43 | 
            +
                    return IMCPToolAdapter
         | 
| 44 | 
            +
                elif name == "BaseMCPService":
         | 
| 45 | 
            +
                    from .core.base import BaseMCPService
         | 
| 46 | 
            +
                    return BaseMCPService
         | 
| 47 | 
            +
                
         | 
| 48 | 
            +
                # Server implementations
         | 
| 49 | 
            +
                elif name == "MCPServer":
         | 
| 50 | 
            +
                    from .server.mcp_server import MCPServer
         | 
| 51 | 
            +
                    return MCPServer
         | 
| 52 | 
            +
                elif name == "StdioHandler":
         | 
| 53 | 
            +
                    from .server.stdio_handler import StdioHandler
         | 
| 54 | 
            +
                    return StdioHandler
         | 
| 55 | 
            +
                elif name == "AlternativeStdioHandler":
         | 
| 56 | 
            +
                    from .server.stdio_handler import AlternativeStdioHandler
         | 
| 57 | 
            +
                    return AlternativeStdioHandler
         | 
| 58 | 
            +
                
         | 
| 59 | 
            +
                # Tool registry and adapters
         | 
| 60 | 
            +
                elif name == "ToolRegistry":
         | 
| 61 | 
            +
                    from .registry.tool_registry import ToolRegistry
         | 
| 62 | 
            +
                    return ToolRegistry
         | 
| 63 | 
            +
                elif name == "BaseToolAdapter":
         | 
| 64 | 
            +
                    from .tools.base_adapter import BaseToolAdapter
         | 
| 65 | 
            +
                    return BaseToolAdapter
         | 
| 66 | 
            +
                elif name == "EchoToolAdapter":
         | 
| 67 | 
            +
                    from .tools.base_adapter import EchoToolAdapter
         | 
| 68 | 
            +
                    return EchoToolAdapter
         | 
| 69 | 
            +
                elif name == "CalculatorToolAdapter":
         | 
| 70 | 
            +
                    from .tools.base_adapter import CalculatorToolAdapter
         | 
| 71 | 
            +
                    return CalculatorToolAdapter
         | 
| 72 | 
            +
                elif name == "SystemInfoToolAdapter":
         | 
| 73 | 
            +
                    from .tools.base_adapter import SystemInfoToolAdapter
         | 
| 74 | 
            +
                    return SystemInfoToolAdapter
         | 
| 75 | 
            +
                
         | 
| 76 | 
            +
                # Configuration management
         | 
| 77 | 
            +
                elif name == "MCPConfiguration":
         | 
| 78 | 
            +
                    from .config.configuration import MCPConfiguration
         | 
| 79 | 
            +
                    return MCPConfiguration
         | 
| 80 | 
            +
                elif name == "MCPConfigLoader":
         | 
| 81 | 
            +
                    from .config.config_loader import MCPConfigLoader
         | 
| 82 | 
            +
                    return MCPConfigLoader
         | 
| 83 | 
            +
                
         | 
| 84 | 
            +
                # Service registry
         | 
| 85 | 
            +
                elif name == "MCPServiceRegistry":
         | 
| 86 | 
            +
                    from .registry.service_registry import MCPServiceRegistry
         | 
| 87 | 
            +
                    return MCPServiceRegistry
         | 
| 88 | 
            +
                
         | 
| 89 | 
            +
                # Exceptions
         | 
| 90 | 
            +
                elif name == "MCPException":
         | 
| 91 | 
            +
                    from .core.exceptions import MCPException
         | 
| 92 | 
            +
                    return MCPException
         | 
| 93 | 
            +
                elif name == "MCPConfigurationError":
         | 
| 94 | 
            +
                    from .core.exceptions import MCPConfigurationError
         | 
| 95 | 
            +
                    return MCPConfigurationError
         | 
| 96 | 
            +
                elif name == "MCPToolNotFoundError":
         | 
| 97 | 
            +
                    from .core.exceptions import MCPToolNotFoundError
         | 
| 98 | 
            +
                    return MCPToolNotFoundError
         | 
| 99 | 
            +
                elif name == "MCPServerError":
         | 
| 100 | 
            +
                    from .core.exceptions import MCPServerError
         | 
| 101 | 
            +
                    return MCPServerError
         | 
| 102 | 
            +
                
         | 
| 103 | 
            +
                raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            # Public API exports
         | 
| 106 | 
            +
            __all__ = [
         | 
| 107 | 
            +
                # Core interfaces
         | 
| 108 | 
            +
                "IMCPServer",
         | 
| 109 | 
            +
                "IMCPToolRegistry",
         | 
| 110 | 
            +
                "IMCPConfiguration",
         | 
| 111 | 
            +
                "IMCPToolAdapter",
         | 
| 112 | 
            +
                "BaseMCPService",
         | 
| 113 | 
            +
                
         | 
| 114 | 
            +
                # Server implementations
         | 
| 115 | 
            +
                "MCPServer",
         | 
| 116 | 
            +
                "StdioHandler",
         | 
| 117 | 
            +
                "AlternativeStdioHandler",
         | 
| 118 | 
            +
                
         | 
| 119 | 
            +
                # Tool management
         | 
| 120 | 
            +
                "ToolRegistry",
         | 
| 121 | 
            +
                "BaseToolAdapter",
         | 
| 122 | 
            +
                "EchoToolAdapter",
         | 
| 123 | 
            +
                "CalculatorToolAdapter",
         | 
| 124 | 
            +
                "SystemInfoToolAdapter",
         | 
| 125 | 
            +
                
         | 
| 126 | 
            +
                # Configuration
         | 
| 127 | 
            +
                "MCPConfiguration",
         | 
| 128 | 
            +
                "MCPConfigLoader",
         | 
| 129 | 
            +
                
         | 
| 130 | 
            +
                # Service registry
         | 
| 131 | 
            +
                "MCPServiceRegistry",
         | 
| 132 | 
            +
                
         | 
| 133 | 
            +
                # Exceptions
         | 
| 134 | 
            +
                "MCPException",
         | 
| 135 | 
            +
                "MCPConfigurationError",
         | 
| 136 | 
            +
                "MCPToolNotFoundError",
         | 
| 137 | 
            +
                "MCPServerError",
         | 
| 138 | 
            +
            ]
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            """
         | 
| 2 | 
            +
            MCP Gateway Configuration Module
         | 
| 3 | 
            +
            =================================
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Configuration management for the MCP Gateway service.
         | 
| 6 | 
            +
            """
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            from .configuration import MCPConfiguration
         | 
| 9 | 
            +
            from .config_loader import MCPConfigLoader
         | 
| 10 | 
            +
            from .config_schema import MCPConfigSchema, validate_config
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            __all__ = [
         | 
| 13 | 
            +
                "MCPConfiguration",
         | 
| 14 | 
            +
                "MCPConfigLoader",
         | 
| 15 | 
            +
                "MCPConfigSchema",
         | 
| 16 | 
            +
                "validate_config",
         | 
| 17 | 
            +
            ]
         | 
| @@ -0,0 +1,232 @@ | |
| 1 | 
            +
            """
         | 
| 2 | 
            +
            MCP Gateway Configuration Loader
         | 
| 3 | 
            +
            ================================
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Handles loading and discovery of MCP configuration files.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Part of ISS-0034: Infrastructure Setup - MCP Gateway Project Foundation
         | 
| 8 | 
            +
            """
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            import os
         | 
| 11 | 
            +
            from pathlib import Path
         | 
| 12 | 
            +
            from typing import Optional, List
         | 
| 13 | 
            +
            import yaml
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            from claude_mpm.core.logger import get_logger
         | 
| 16 | 
            +
             | 
| 17 | 
            +
             | 
| 18 | 
            +
            class MCPConfigLoader:
         | 
| 19 | 
            +
                """
         | 
| 20 | 
            +
                Configuration loader for MCP Gateway.
         | 
| 21 | 
            +
                
         | 
| 22 | 
            +
                This class handles discovering and loading configuration files from
         | 
| 23 | 
            +
                standard locations, supporting both user and system configurations.
         | 
| 24 | 
            +
                
         | 
| 25 | 
            +
                WHY: We separate configuration loading from the main configuration
         | 
| 26 | 
            +
                service to support multiple configuration sources and provide a clean
         | 
| 27 | 
            +
                abstraction for configuration discovery.
         | 
| 28 | 
            +
                """
         | 
| 29 | 
            +
                
         | 
| 30 | 
            +
                # Standard configuration file search paths
         | 
| 31 | 
            +
                CONFIG_SEARCH_PATHS = [
         | 
| 32 | 
            +
                    # User-specific configurations
         | 
| 33 | 
            +
                    Path("~/.claude/mcp/config.yaml"),
         | 
| 34 | 
            +
                    Path("~/.claude/mcp_gateway.yaml"),
         | 
| 35 | 
            +
                    Path("~/.config/claude-mpm/mcp_gateway.yaml"),
         | 
| 36 | 
            +
                    
         | 
| 37 | 
            +
                    # Project-specific configurations
         | 
| 38 | 
            +
                    Path("./mcp_gateway.yaml"),
         | 
| 39 | 
            +
                    Path("./config/mcp_gateway.yaml"),
         | 
| 40 | 
            +
                    Path("./.claude/mcp_gateway.yaml"),
         | 
| 41 | 
            +
                    
         | 
| 42 | 
            +
                    # System-wide configurations
         | 
| 43 | 
            +
                    Path("/etc/claude-mpm/mcp_gateway.yaml"),
         | 
| 44 | 
            +
                ]
         | 
| 45 | 
            +
                
         | 
| 46 | 
            +
                def __init__(self):
         | 
| 47 | 
            +
                    """Initialize configuration loader."""
         | 
| 48 | 
            +
                    self.logger = get_logger("MCPConfigLoader")
         | 
| 49 | 
            +
                
         | 
| 50 | 
            +
                def find_config_file(self) -> Optional[Path]:
         | 
| 51 | 
            +
                    """
         | 
| 52 | 
            +
                    Find the first available configuration file.
         | 
| 53 | 
            +
                    
         | 
| 54 | 
            +
                    Searches through standard locations and returns the first
         | 
| 55 | 
            +
                    existing configuration file.
         | 
| 56 | 
            +
                    
         | 
| 57 | 
            +
                    Returns:
         | 
| 58 | 
            +
                        Path to configuration file if found, None otherwise
         | 
| 59 | 
            +
                    """
         | 
| 60 | 
            +
                    for config_path in self.CONFIG_SEARCH_PATHS:
         | 
| 61 | 
            +
                        expanded_path = config_path.expanduser()
         | 
| 62 | 
            +
                        if expanded_path.exists() and expanded_path.is_file():
         | 
| 63 | 
            +
                            self.logger.info(f"Found configuration file: {expanded_path}")
         | 
| 64 | 
            +
                            return expanded_path
         | 
| 65 | 
            +
                    
         | 
| 66 | 
            +
                    self.logger.debug("No configuration file found in standard locations")
         | 
| 67 | 
            +
                    return None
         | 
| 68 | 
            +
                
         | 
| 69 | 
            +
                def load_from_file(self, config_path: Path) -> Optional[dict]:
         | 
| 70 | 
            +
                    """
         | 
| 71 | 
            +
                    Load configuration from a specific file.
         | 
| 72 | 
            +
                    
         | 
| 73 | 
            +
                    Args:
         | 
| 74 | 
            +
                        config_path: Path to configuration file
         | 
| 75 | 
            +
                        
         | 
| 76 | 
            +
                    Returns:
         | 
| 77 | 
            +
                        Configuration dictionary if successful, None otherwise
         | 
| 78 | 
            +
                    """
         | 
| 79 | 
            +
                    try:
         | 
| 80 | 
            +
                        expanded_path = config_path.expanduser()
         | 
| 81 | 
            +
                        
         | 
| 82 | 
            +
                        if not expanded_path.exists():
         | 
| 83 | 
            +
                            self.logger.error(f"Configuration file not found: {expanded_path}")
         | 
| 84 | 
            +
                            return None
         | 
| 85 | 
            +
                        
         | 
| 86 | 
            +
                        with open(expanded_path, 'r') as f:
         | 
| 87 | 
            +
                            config = yaml.safe_load(f)
         | 
| 88 | 
            +
                        
         | 
| 89 | 
            +
                        self.logger.info(f"Configuration loaded from {expanded_path}")
         | 
| 90 | 
            +
                        return config or {}
         | 
| 91 | 
            +
                        
         | 
| 92 | 
            +
                    except yaml.YAMLError as e:
         | 
| 93 | 
            +
                        self.logger.error(f"Failed to parse YAML configuration: {e}")
         | 
| 94 | 
            +
                        return None
         | 
| 95 | 
            +
                    except Exception as e:
         | 
| 96 | 
            +
                        self.logger.error(f"Failed to load configuration: {e}")
         | 
| 97 | 
            +
                        return None
         | 
| 98 | 
            +
                
         | 
| 99 | 
            +
                def load_from_env(self) -> dict:
         | 
| 100 | 
            +
                    """
         | 
| 101 | 
            +
                    Load configuration from environment variables.
         | 
| 102 | 
            +
                    
         | 
| 103 | 
            +
                    Environment variables follow the pattern: MCP_GATEWAY_<SECTION>_<KEY>
         | 
| 104 | 
            +
                    
         | 
| 105 | 
            +
                    Returns:
         | 
| 106 | 
            +
                        Configuration dictionary built from environment variables
         | 
| 107 | 
            +
                    """
         | 
| 108 | 
            +
                    config = {}
         | 
| 109 | 
            +
                    prefix = "MCP_GATEWAY_"
         | 
| 110 | 
            +
                    
         | 
| 111 | 
            +
                    for env_key, env_value in os.environ.items():
         | 
| 112 | 
            +
                        if not env_key.startswith(prefix):
         | 
| 113 | 
            +
                            continue
         | 
| 114 | 
            +
                        
         | 
| 115 | 
            +
                        # Parse environment variable into configuration path
         | 
| 116 | 
            +
                        config_path = env_key[len(prefix):].lower().split('_')
         | 
| 117 | 
            +
                        
         | 
| 118 | 
            +
                        # Build nested configuration structure
         | 
| 119 | 
            +
                        current = config
         | 
| 120 | 
            +
                        for part in config_path[:-1]:
         | 
| 121 | 
            +
                            if part not in current:
         | 
| 122 | 
            +
                                current[part] = {}
         | 
| 123 | 
            +
                            current = current[part]
         | 
| 124 | 
            +
                        
         | 
| 125 | 
            +
                        # Set the value
         | 
| 126 | 
            +
                        key = config_path[-1]
         | 
| 127 | 
            +
                        try:
         | 
| 128 | 
            +
                            # Try to parse as JSON for complex types
         | 
| 129 | 
            +
                            import json
         | 
| 130 | 
            +
                            current[key] = json.loads(env_value)
         | 
| 131 | 
            +
                        except:
         | 
| 132 | 
            +
                            # Fall back to string value
         | 
| 133 | 
            +
                            current[key] = env_value
         | 
| 134 | 
            +
                        
         | 
| 135 | 
            +
                        self.logger.debug(f"Loaded from environment: {env_key}")
         | 
| 136 | 
            +
                    
         | 
| 137 | 
            +
                    return config
         | 
| 138 | 
            +
                
         | 
| 139 | 
            +
                def load(self, config_path: Optional[Path] = None) -> dict:
         | 
| 140 | 
            +
                    """
         | 
| 141 | 
            +
                    Load configuration from all sources.
         | 
| 142 | 
            +
                    
         | 
| 143 | 
            +
                    Loads configuration in the following priority order:
         | 
| 144 | 
            +
                    1. Default configuration
         | 
| 145 | 
            +
                    2. File configuration (if found or specified)
         | 
| 146 | 
            +
                    3. Environment variable overrides
         | 
| 147 | 
            +
                    
         | 
| 148 | 
            +
                    Args:
         | 
| 149 | 
            +
                        config_path: Optional specific configuration file path
         | 
| 150 | 
            +
                        
         | 
| 151 | 
            +
                    Returns:
         | 
| 152 | 
            +
                        Merged configuration dictionary
         | 
| 153 | 
            +
                    """
         | 
| 154 | 
            +
                    from .configuration import MCPConfiguration
         | 
| 155 | 
            +
                    
         | 
| 156 | 
            +
                    # Start with defaults
         | 
| 157 | 
            +
                    config = MCPConfiguration.DEFAULT_CONFIG.copy()
         | 
| 158 | 
            +
                    
         | 
| 159 | 
            +
                    # Load from file
         | 
| 160 | 
            +
                    file_path = config_path or self.find_config_file()
         | 
| 161 | 
            +
                    if file_path:
         | 
| 162 | 
            +
                        file_config = self.load_from_file(file_path)
         | 
| 163 | 
            +
                        if file_config:
         | 
| 164 | 
            +
                            config = self._merge_configs(config, file_config)
         | 
| 165 | 
            +
                    
         | 
| 166 | 
            +
                    # Apply environment overrides
         | 
| 167 | 
            +
                    env_config = self.load_from_env()
         | 
| 168 | 
            +
                    if env_config:
         | 
| 169 | 
            +
                        config = self._merge_configs(config, env_config)
         | 
| 170 | 
            +
                    
         | 
| 171 | 
            +
                    return config
         | 
| 172 | 
            +
                
         | 
| 173 | 
            +
                def _merge_configs(self, base: dict, overlay: dict) -> dict:
         | 
| 174 | 
            +
                    """
         | 
| 175 | 
            +
                    Recursively merge two configuration dictionaries.
         | 
| 176 | 
            +
                    
         | 
| 177 | 
            +
                    Args:
         | 
| 178 | 
            +
                        base: Base configuration
         | 
| 179 | 
            +
                        overlay: Configuration to merge in
         | 
| 180 | 
            +
                        
         | 
| 181 | 
            +
                    Returns:
         | 
| 182 | 
            +
                        Merged configuration
         | 
| 183 | 
            +
                    """
         | 
| 184 | 
            +
                    result = base.copy()
         | 
| 185 | 
            +
                    
         | 
| 186 | 
            +
                    for key, value in overlay.items():
         | 
| 187 | 
            +
                        if key in result and isinstance(result[key], dict) and isinstance(value, dict):
         | 
| 188 | 
            +
                            result[key] = self._merge_configs(result[key], value)
         | 
| 189 | 
            +
                        else:
         | 
| 190 | 
            +
                            result[key] = value
         | 
| 191 | 
            +
                    
         | 
| 192 | 
            +
                    return result
         | 
| 193 | 
            +
                
         | 
| 194 | 
            +
                def create_default_config(self, path: Path) -> bool:
         | 
| 195 | 
            +
                    """
         | 
| 196 | 
            +
                    Create a default configuration file.
         | 
| 197 | 
            +
                    
         | 
| 198 | 
            +
                    Args:
         | 
| 199 | 
            +
                        path: Path where to create the configuration file
         | 
| 200 | 
            +
                        
         | 
| 201 | 
            +
                    Returns:
         | 
| 202 | 
            +
                        True if file created successfully
         | 
| 203 | 
            +
                    """
         | 
| 204 | 
            +
                    from .configuration import MCPConfiguration
         | 
| 205 | 
            +
                    
         | 
| 206 | 
            +
                    try:
         | 
| 207 | 
            +
                        expanded_path = path.expanduser()
         | 
| 208 | 
            +
                        expanded_path.parent.mkdir(parents=True, exist_ok=True)
         | 
| 209 | 
            +
                        
         | 
| 210 | 
            +
                        with open(expanded_path, 'w') as f:
         | 
| 211 | 
            +
                            yaml.dump(
         | 
| 212 | 
            +
                                MCPConfiguration.DEFAULT_CONFIG,
         | 
| 213 | 
            +
                                f,
         | 
| 214 | 
            +
                                default_flow_style=False,
         | 
| 215 | 
            +
                                sort_keys=True
         | 
| 216 | 
            +
                            )
         | 
| 217 | 
            +
                        
         | 
| 218 | 
            +
                        self.logger.info(f"Created default configuration at {expanded_path}")
         | 
| 219 | 
            +
                        return True
         | 
| 220 | 
            +
                        
         | 
| 221 | 
            +
                    except Exception as e:
         | 
| 222 | 
            +
                        self.logger.error(f"Failed to create default configuration: {e}")
         | 
| 223 | 
            +
                        return False
         | 
| 224 | 
            +
                
         | 
| 225 | 
            +
                def list_config_locations(self) -> List[str]:
         | 
| 226 | 
            +
                    """
         | 
| 227 | 
            +
                    List all configuration file search locations.
         | 
| 228 | 
            +
                    
         | 
| 229 | 
            +
                    Returns:
         | 
| 230 | 
            +
                        List of configuration file paths (as strings)
         | 
| 231 | 
            +
                    """
         | 
| 232 | 
            +
                    return [str(path.expanduser()) for path in self.CONFIG_SEARCH_PATHS]
         | 
| @@ -0,0 +1,234 @@ | |
| 1 | 
            +
            """
         | 
| 2 | 
            +
            MCP Gateway Configuration Schema
         | 
| 3 | 
            +
            ================================
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Defines and validates the configuration schema for MCP Gateway.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Part of ISS-0034: Infrastructure Setup - MCP Gateway Project Foundation
         | 
| 8 | 
            +
            """
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            from typing import Any, Dict, List, Optional
         | 
| 11 | 
            +
            from dataclasses import dataclass
         | 
| 12 | 
            +
             | 
| 13 | 
            +
             | 
| 14 | 
            +
            @dataclass
         | 
| 15 | 
            +
            class MCPConfigSchema:
         | 
| 16 | 
            +
                """
         | 
| 17 | 
            +
                Configuration schema definition for MCP Gateway.
         | 
| 18 | 
            +
                
         | 
| 19 | 
            +
                This class defines the structure and validation rules for
         | 
| 20 | 
            +
                MCP Gateway configuration.
         | 
| 21 | 
            +
                """
         | 
| 22 | 
            +
                
         | 
| 23 | 
            +
                # Schema version for migration support
         | 
| 24 | 
            +
                SCHEMA_VERSION = "1.0.0"
         | 
| 25 | 
            +
                
         | 
| 26 | 
            +
                # Configuration schema definition
         | 
| 27 | 
            +
                SCHEMA = {
         | 
| 28 | 
            +
                    "mcp": {
         | 
| 29 | 
            +
                        "type": "object",
         | 
| 30 | 
            +
                        "required": True,
         | 
| 31 | 
            +
                        "properties": {
         | 
| 32 | 
            +
                            "server": {
         | 
| 33 | 
            +
                                "type": "object",
         | 
| 34 | 
            +
                                "required": True,
         | 
| 35 | 
            +
                                "properties": {
         | 
| 36 | 
            +
                                    "name": {"type": "string", "required": True},
         | 
| 37 | 
            +
                                    "version": {"type": "string", "required": True},
         | 
| 38 | 
            +
                                    "description": {"type": "string", "required": False},
         | 
| 39 | 
            +
                                    "communication": {
         | 
| 40 | 
            +
                                        "type": "object",
         | 
| 41 | 
            +
                                        "required": True,
         | 
| 42 | 
            +
                                        "properties": {
         | 
| 43 | 
            +
                                            "type": {
         | 
| 44 | 
            +
                                                "type": "string",
         | 
| 45 | 
            +
                                                "required": True,
         | 
| 46 | 
            +
                                                "enum": ["stdio", "websocket", "http"]
         | 
| 47 | 
            +
                                            },
         | 
| 48 | 
            +
                                            "timeout": {
         | 
| 49 | 
            +
                                                "type": "number",
         | 
| 50 | 
            +
                                                "required": False,
         | 
| 51 | 
            +
                                                "min": 1,
         | 
| 52 | 
            +
                                                "max": 3600
         | 
| 53 | 
            +
                                            },
         | 
| 54 | 
            +
                                            "buffer_size": {
         | 
| 55 | 
            +
                                                "type": "integer",
         | 
| 56 | 
            +
                                                "required": False,
         | 
| 57 | 
            +
                                                "min": 1024,
         | 
| 58 | 
            +
                                                "max": 1048576
         | 
| 59 | 
            +
                                            },
         | 
| 60 | 
            +
                                        }
         | 
| 61 | 
            +
                                    },
         | 
| 62 | 
            +
                                    "capabilities": {
         | 
| 63 | 
            +
                                        "type": "object",
         | 
| 64 | 
            +
                                        "required": False,
         | 
| 65 | 
            +
                                        "properties": {
         | 
| 66 | 
            +
                                            "tools": {"type": "boolean", "required": False},
         | 
| 67 | 
            +
                                            "resources": {"type": "boolean", "required": False},
         | 
| 68 | 
            +
                                            "prompts": {"type": "boolean", "required": False},
         | 
| 69 | 
            +
                                        }
         | 
| 70 | 
            +
                                    },
         | 
| 71 | 
            +
                                }
         | 
| 72 | 
            +
                            },
         | 
| 73 | 
            +
                            "tools": {
         | 
| 74 | 
            +
                                "type": "object",
         | 
| 75 | 
            +
                                "required": False,
         | 
| 76 | 
            +
                                "properties": {
         | 
| 77 | 
            +
                                    "enabled": {"type": "boolean", "required": False},
         | 
| 78 | 
            +
                                    "auto_discover": {"type": "boolean", "required": False},
         | 
| 79 | 
            +
                                    "discovery_paths": {
         | 
| 80 | 
            +
                                        "type": "array",
         | 
| 81 | 
            +
                                        "required": False,
         | 
| 82 | 
            +
                                        "items": {"type": "string"}
         | 
| 83 | 
            +
                                    },
         | 
| 84 | 
            +
                                    "timeout_default": {
         | 
| 85 | 
            +
                                        "type": "number",
         | 
| 86 | 
            +
                                        "required": False,
         | 
| 87 | 
            +
                                        "min": 1,
         | 
| 88 | 
            +
                                        "max": 300
         | 
| 89 | 
            +
                                    },
         | 
| 90 | 
            +
                                    "max_concurrent": {
         | 
| 91 | 
            +
                                        "type": "integer",
         | 
| 92 | 
            +
                                        "required": False,
         | 
| 93 | 
            +
                                        "min": 1,
         | 
| 94 | 
            +
                                        "max": 100
         | 
| 95 | 
            +
                                    },
         | 
| 96 | 
            +
                                }
         | 
| 97 | 
            +
                            },
         | 
| 98 | 
            +
                            "logging": {
         | 
| 99 | 
            +
                                "type": "object",
         | 
| 100 | 
            +
                                "required": False,
         | 
| 101 | 
            +
                                "properties": {
         | 
| 102 | 
            +
                                    "level": {
         | 
| 103 | 
            +
                                        "type": "string",
         | 
| 104 | 
            +
                                        "required": False,
         | 
| 105 | 
            +
                                        "enum": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
         | 
| 106 | 
            +
                                    },
         | 
| 107 | 
            +
                                    "file": {"type": "string", "required": False},
         | 
| 108 | 
            +
                                    "max_size": {"type": "string", "required": False},
         | 
| 109 | 
            +
                                    "max_files": {
         | 
| 110 | 
            +
                                        "type": "integer",
         | 
| 111 | 
            +
                                        "required": False,
         | 
| 112 | 
            +
                                        "min": 1,
         | 
| 113 | 
            +
                                        "max": 100
         | 
| 114 | 
            +
                                    },
         | 
| 115 | 
            +
                                    "format": {
         | 
| 116 | 
            +
                                        "type": "string",
         | 
| 117 | 
            +
                                        "required": False,
         | 
| 118 | 
            +
                                        "enum": ["json", "text"]
         | 
| 119 | 
            +
                                    },
         | 
| 120 | 
            +
                                }
         | 
| 121 | 
            +
                            },
         | 
| 122 | 
            +
                            "security": {
         | 
| 123 | 
            +
                                "type": "object",
         | 
| 124 | 
            +
                                "required": False,
         | 
| 125 | 
            +
                                "properties": {
         | 
| 126 | 
            +
                                    "validate_schemas": {"type": "boolean", "required": False},
         | 
| 127 | 
            +
                                    "sanitize_inputs": {"type": "boolean", "required": False},
         | 
| 128 | 
            +
                                    "max_request_size": {
         | 
| 129 | 
            +
                                        "type": "integer",
         | 
| 130 | 
            +
                                        "required": False,
         | 
| 131 | 
            +
                                        "min": 1024,
         | 
| 132 | 
            +
                                        "max": 104857600  # 100MB max
         | 
| 133 | 
            +
                                    },
         | 
| 134 | 
            +
                                    "allowed_tools": {
         | 
| 135 | 
            +
                                        "type": "array",
         | 
| 136 | 
            +
                                        "required": False,
         | 
| 137 | 
            +
                                        "items": {"type": "string"}
         | 
| 138 | 
            +
                                    },
         | 
| 139 | 
            +
                                    "blocked_tools": {
         | 
| 140 | 
            +
                                        "type": "array",
         | 
| 141 | 
            +
                                        "required": False,
         | 
| 142 | 
            +
                                        "items": {"type": "string"}
         | 
| 143 | 
            +
                                    },
         | 
| 144 | 
            +
                                }
         | 
| 145 | 
            +
                            },
         | 
| 146 | 
            +
                        }
         | 
| 147 | 
            +
                    }
         | 
| 148 | 
            +
                }
         | 
| 149 | 
            +
             | 
| 150 | 
            +
             | 
| 151 | 
            +
            def validate_config(config: Dict[str, Any], schema: Optional[Dict[str, Any]] = None) -> List[str]:
         | 
| 152 | 
            +
                """
         | 
| 153 | 
            +
                Validate configuration against schema.
         | 
| 154 | 
            +
                
         | 
| 155 | 
            +
                Args:
         | 
| 156 | 
            +
                    config: Configuration dictionary to validate
         | 
| 157 | 
            +
                    schema: Schema to validate against (uses default if not provided)
         | 
| 158 | 
            +
                    
         | 
| 159 | 
            +
                Returns:
         | 
| 160 | 
            +
                    List of validation errors (empty if valid)
         | 
| 161 | 
            +
                """
         | 
| 162 | 
            +
                if schema is None:
         | 
| 163 | 
            +
                    schema = MCPConfigSchema.SCHEMA
         | 
| 164 | 
            +
                
         | 
| 165 | 
            +
                errors = []
         | 
| 166 | 
            +
                
         | 
| 167 | 
            +
                def validate_value(value: Any, spec: Dict[str, Any], path: str) -> None:
         | 
| 168 | 
            +
                    """Recursively validate a value against its specification."""
         | 
| 169 | 
            +
                    
         | 
| 170 | 
            +
                    # Check type
         | 
| 171 | 
            +
                    expected_type = spec.get("type")
         | 
| 172 | 
            +
                    if expected_type:
         | 
| 173 | 
            +
                        if expected_type == "object" and not isinstance(value, dict):
         | 
| 174 | 
            +
                            errors.append(f"{path}: Expected object, got {type(value).__name__}")
         | 
| 175 | 
            +
                            return
         | 
| 176 | 
            +
                        elif expected_type == "array" and not isinstance(value, list):
         | 
| 177 | 
            +
                            errors.append(f"{path}: Expected array, got {type(value).__name__}")
         | 
| 178 | 
            +
                            return
         | 
| 179 | 
            +
                        elif expected_type == "string" and not isinstance(value, str):
         | 
| 180 | 
            +
                            errors.append(f"{path}: Expected string, got {type(value).__name__}")
         | 
| 181 | 
            +
                            return
         | 
| 182 | 
            +
                        elif expected_type == "number" and not isinstance(value, (int, float)):
         | 
| 183 | 
            +
                            errors.append(f"{path}: Expected number, got {type(value).__name__}")
         | 
| 184 | 
            +
                            return
         | 
| 185 | 
            +
                        elif expected_type == "integer" and not isinstance(value, int):
         | 
| 186 | 
            +
                            errors.append(f"{path}: Expected integer, got {type(value).__name__}")
         | 
| 187 | 
            +
                            return
         | 
| 188 | 
            +
                        elif expected_type == "boolean" and not isinstance(value, bool):
         | 
| 189 | 
            +
                            errors.append(f"{path}: Expected boolean, got {type(value).__name__}")
         | 
| 190 | 
            +
                            return
         | 
| 191 | 
            +
                    
         | 
| 192 | 
            +
                    # Check enum values
         | 
| 193 | 
            +
                    if "enum" in spec and value not in spec["enum"]:
         | 
| 194 | 
            +
                        errors.append(f"{path}: Value '{value}' not in allowed values: {spec['enum']}")
         | 
| 195 | 
            +
                    
         | 
| 196 | 
            +
                    # Check numeric constraints
         | 
| 197 | 
            +
                    if isinstance(value, (int, float)):
         | 
| 198 | 
            +
                        if "min" in spec and value < spec["min"]:
         | 
| 199 | 
            +
                            errors.append(f"{path}: Value {value} is less than minimum {spec['min']}")
         | 
| 200 | 
            +
                        if "max" in spec and value > spec["max"]:
         | 
| 201 | 
            +
                            errors.append(f"{path}: Value {value} is greater than maximum {spec['max']}")
         | 
| 202 | 
            +
                    
         | 
| 203 | 
            +
                    # Validate object properties
         | 
| 204 | 
            +
                    if expected_type == "object" and isinstance(value, dict):
         | 
| 205 | 
            +
                        properties = spec.get("properties", {})
         | 
| 206 | 
            +
                        for prop_name, prop_spec in properties.items():
         | 
| 207 | 
            +
                            prop_path = f"{path}.{prop_name}"
         | 
| 208 | 
            +
                            
         | 
| 209 | 
            +
                            if prop_name in value:
         | 
| 210 | 
            +
                                validate_value(value[prop_name], prop_spec, prop_path)
         | 
| 211 | 
            +
                            elif prop_spec.get("required", False):
         | 
| 212 | 
            +
                                errors.append(f"{prop_path}: Required field missing")
         | 
| 213 | 
            +
                    
         | 
| 214 | 
            +
                    # Validate array items
         | 
| 215 | 
            +
                    if expected_type == "array" and isinstance(value, list):
         | 
| 216 | 
            +
                        item_spec = spec.get("items", {})
         | 
| 217 | 
            +
                        for i, item in enumerate(value):
         | 
| 218 | 
            +
                            validate_value(item, item_spec, f"{path}[{i}]")
         | 
| 219 | 
            +
                
         | 
| 220 | 
            +
                # Start validation from root
         | 
| 221 | 
            +
                validate_value(config, {"type": "object", "properties": schema}, "config")
         | 
| 222 | 
            +
                
         | 
| 223 | 
            +
                return errors
         | 
| 224 | 
            +
             | 
| 225 | 
            +
             | 
| 226 | 
            +
            def generate_config_template() -> Dict[str, Any]:
         | 
| 227 | 
            +
                """
         | 
| 228 | 
            +
                Generate a configuration template with all possible options.
         | 
| 229 | 
            +
                
         | 
| 230 | 
            +
                Returns:
         | 
| 231 | 
            +
                    Configuration template dictionary
         | 
| 232 | 
            +
                """
         | 
| 233 | 
            +
                from ..config.configuration import MCPConfiguration
         | 
| 234 | 
            +
                return MCPConfiguration.DEFAULT_CONFIG
         |