{"owner":"microsoft","repo":"mcp-for-beginners","hasSkills":true,"totalSkillsCount":58,"totalTokensCount":115352,"categories":["plugin-manifest","mcp-config","root-instruction"],"hasMcp":true,"mcpConfig":{"mcpServers":{"mcp-for-beginners":{"command":"npx","args":["-y","@modelcontextprotocol/server-mcp-for-beginners"]}}},"found":["translations/bg/11-MCPServerHandsOnLabs/06-Tools/README.md","09-CaseStudy/docs-mcp/solution/scenario3/mcp.json","translations/bn/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/ar/11-MCPServerHandsOnLabs/06-Tools/README.md","11-MCPServerHandsOnLabs/06-Tools/README.md","AGENTS.md","translations/de/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/en/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/da/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/el/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/cs/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/es/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/et/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/fa/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/fi/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/fr/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/he/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/hi/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/hr/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/hu/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/id/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/it/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/ja/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/km/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/kn/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/ko/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/lt/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/ml/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/mr/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/ms/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/my/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/ne/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/nl/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/no/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/pa/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/pcm/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/pl/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/pt-BR/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/pt-PT/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/ro/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/ru/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/sk/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/sl/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/sr/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/sv/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/sw/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/ta/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/te/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/th/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/tl/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/tr/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/uk/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/ur/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/vi/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/zh-CN/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/zh-HK/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/zh-MO/11-MCPServerHandsOnLabs/06-Tools/README.md","translations/zh-TW/11-MCPServerHandsOnLabs/06-Tools/README.md"],"skills":{"translations/bg/11-MCPServerHandsOnLabs/06-Tools/README.md":"# Разработка на инструменти\n\n## 🎯 Какво обхваща този лабораторен урок\n\nТози лабораторен урок се фокусира върху създаването на усъвършенствани MCP инструменти, които предоставят на AI асистентите мощни възможности за заявки към бази данни, инспекция на схеми и аналитични функции. Ще научите как да изграждате инструменти, които са едновременно мощни и безопасни, с изчерпателно управление на грешки и оптимизация на производителността.\n\n## Обзор\n\nMCP инструментите са интерфейсът между AI асистентите и вашите системи за данни. Добре проектираните инструменти осигуряват структурирани и валидирани операции, като същевременно поддържат сигурност и производителност. Този лабораторен урок обхваща целия жизнен цикъл на разработката на инструменти – от проектиране до внедряване.\n\nНашият MCP сървър за търговия на дребно включва цялостен набор от инструменти, които позволяват заявки на естествен език за данни за продажби, продуктови каталози и бизнес анализи, като същевременно поддържат строги граници на сигурност и оптимална производителност.\n\n## Цели на обучението\n\nДо края на този лабораторен урок ще можете:\n\n- **Да проектирате** усъвършенствани MCP инструменти с комплексна валидация на параметри\n- **Да внедрите** сигурни инструменти за заявки към бази данни с защита срещу SQL инжекции\n- **Да създадете** възможности за инспекция на схеми за динамични заявки\n- **Да изградите** персонализирани аналитични инструменти за бизнес интелигентност\n- **Да приложите** изчерпателно управление на грешки и плавна деградация\n- **Да оптимизирате** производителността на инструментите за работни натоварвания в продукция\n\n## 🛠️ Основна архитектура на инструментите\n\n### Принципи на проектиране на инструменти\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Валидация на заявки и сигурност\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Инструменти за заявки към бази данни\n\n### Инструмент за анализ на продажби\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Инструмент за инспекция на схеми\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Аналитични и помощни инструменти\n\n### Инструмент за бизнес интелигентност\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Основни изводи\n\nСлед завършване на този лабораторен урок, трябва да","09-CaseStudy/docs-mcp/solution/scenario3/mcp.json":"{\n    \"servers\": {\n        \"LearnDocsMCP\": {\n            \"url\": \"https://learn.microsoft.com/api/mcp\"\n        }\n    }\n}","translations/bn/11-MCPServerHandsOnLabs/06-Tools/README.md":"# টুল ডেভেলপমেন্ট\n\n## 🎯 এই ল্যাবে কী শেখানো হবে\n\nএই ল্যাবে আপনি শিখবেন কীভাবে উন্নত MCP টুল তৈরি করতে হয় যা AI অ্যাসিস্ট্যান্টকে শক্তিশালী ডাটাবেস কোয়েরি করার ক্ষমতা, স্কিমা ইনট্রোস্পেকশন এবং অ্যানালিটিক্স ফাংশন প্রদান করে। আপনি এমন টুল তৈরি করতে শিখবেন যা শক্তিশালী এবং নিরাপদ, যেখানে ব্যাপক ত্রুটি পরিচালনা এবং পারফরম্যান্স অপ্টিমাইজেশন অন্তর্ভুক্ত থাকবে।\n\n## ওভারভিউ\n\nMCP টুল হলো AI অ্যাসিস্ট্যান্ট এবং আপনার ডেটা সিস্টেমের মধ্যে ইন্টারফেস। ভালোভাবে ডিজাইন করা টুল জটিল অপারেশনের জন্য গঠনমূলক এবং যাচাই করা অ্যাক্সেস প্রদান করে, একইসঙ্গে নিরাপত্তা এবং পারফরম্যান্স বজায় রাখে। এই ল্যাবে টুল ডেভেলপমেন্টের সম্পূর্ণ জীবনচক্র, ডিজাইন থেকে ডিপ্লয়মেন্ট পর্যন্ত, কভার করা হয়েছে।\n\nআমাদের রিটেইল MCP সার্ভার একটি বিস্তৃত টুলের সেট বাস্তবায়ন করে যা সেলস ডেটা, প্রোডাক্ট ক্যাটালগ এবং বিজনেস অ্যানালিটিক্সের প্রাকৃতিক ভাষায় কোয়েরি করার ক্ষমতা প্রদান করে, কঠোর নিরাপত্তা সীমা এবং সর্বোত্তম পারফরম্যান্স বজায় রেখে।\n\n## শেখার লক্ষ্য\n\nএই ল্যাব শেষে আপনি সক্ষম হবেন:\n\n- **ডিজাইন**: জটিল প্যারামিটার যাচাই সহ উন্নত MCP টুল ডিজাইন করা  \n- **ইমপ্লিমেন্ট**: SQL ইনজেকশন প্রতিরোধ সহ নিরাপদ ডাটাবেস কোয়েরি টুল তৈরি করা  \n- **ক্রিয়েট**: ডায়নামিক কোয়েরির জন্য স্কিমা ইনট্রোস্পেকশন ক্ষমতা তৈরি করা  \n- **বিল্ড**: বিজনেস ইন্টেলিজেন্সের জন্য কাস্টম অ্যানালিটিক্স টুল তৈরি করা  \n- **অ্যাপ্লাই**: ব্যাপক ত্রুটি পরিচালনা এবং গ্রেসফুল ডিগ্রেডেশন প্রয়োগ করা  \n- **অপ্টিমাইজ**: প্রোডাকশন ওয়ার্কলোডের জন্য টুল পারফরম্যান্স অপ্টিমাইজ করা  \n\n## 🛠️ মূল টুল আর্কিটেকচার\n\n### টুল ডিজাইন নীতিমালা\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n  \n### কোয়েরি যাচাই এবং নিরাপত্তা\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n  \n\n## 🗃️ ডাটাবেস কোয়েরি টুল\n\n### সেলস অ্যানালাইসিস টুল\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n  \n### স্কিমা ইনট্রোস্পেকশন টুল\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 📊 অ্যানালিটিক্স এবং ইউটিলিটি টুল\n\n### বিজনেস ইন্টেলিজেন্স টুল\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 🎯 মূল বিষয়গুলো\n\nএই ল্যাব সম্পন্ন করার পর আপনি অর্জন করবেন:\n\n✅ **উন্নত টুল আর্কিটেকচার**: ব্যাপক ত্রুটি পরিচালনা সহ উন্নত MCP টুল বাস্তবায়ন  \n✅ **কোয়েরি যাচাই**: SQL ইনজেকশন আক্রমণ প্রতিরোধের জন্য নিরাপদ SQL যাচাই তৈরি  \n✅ **ডাটাবেস টুল**: শক্তিশালী সেলস অ্যানালাইসিস এবং স্কিমা ইনট্রোস্পেকশন ক্ষমতা তৈরি  \n✅ **বিজনেস ইন্টেলিজেন্স**: ব্যাপক","translations/ar/11-MCPServerHandsOnLabs/06-Tools/README.md":"# تطوير الأدوات\n\n## 🎯 ما يغطيه هذا المختبر\n\nيستعرض هذا المختبر كيفية إنشاء أدوات MCP متقدمة تمنح المساعدين الذكيين قدرات قوية لاستعلام قواعد البيانات، استكشاف المخططات، ووظائف التحليل. ستتعلم كيفية بناء أدوات قوية وآمنة مع معالجة شاملة للأخطاء وتحسين الأداء.\n\n## نظرة عامة\n\nأدوات MCP هي الواجهة بين المساعدين الذكيين وأنظمة البيانات الخاصة بك. الأدوات المصممة بشكل جيد توفر وصولاً منظمًا ومُتحققًا إلى العمليات المعقدة مع الحفاظ على الأمان والأداء. يغطي هذا المختبر دورة حياة تطوير الأدوات بالكامل من التصميم إلى النشر.\n\nيُنفذ خادم MCP الخاص بالتجزئة مجموعة شاملة من الأدوات التي تُمكّن الاستعلام الطبيعي عن بيانات المبيعات، كتالوجات المنتجات، وتحليلات الأعمال مع الحفاظ على حدود أمان صارمة وأداء مثالي.\n\n## أهداف التعلم\n\nبنهاية هذا المختبر، ستكون قادرًا على:\n\n- **تصميم** أدوات MCP متقدمة مع تحقق معقد من المعلمات  \n- **تنفيذ** أدوات استعلام قواعد بيانات آمنة مع حماية من حقن SQL  \n- **إنشاء** قدرات استكشاف المخططات للاستعلامات الديناميكية  \n- **بناء** أدوات تحليل مخصصة لذكاء الأعمال  \n- **تطبيق** معالجة شاملة للأخطاء وتدهور سلس  \n- **تحسين** أداء الأدوات لتناسب أعباء العمل الإنتاجية  \n\n## 🛠️ بنية الأدوات الأساسية\n\n### مبادئ تصميم الأدوات\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n  \n### التحقق من الاستعلام والأمان\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n  \n\n## 🗃️ أدوات استعلام قواعد البيانات\n\n### أداة تحليل المبيعات\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n  \n### أداة استكشاف المخططات\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 📊 أدوات التحليل والمرافق\n\n### أداة ذكاء الأعمال\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 🎯 النقاط الرئيسية\n\nبعد إكمال هذا المختبر، يجب أن تكون قد حققت:\n\n✅ **بنية أدوات متقدمة**: تنفيذ أدوات MCP متطورة مع معالجة شاملة للأخطاء  \n✅ **التحقق من الاستعلام**: بناء تحقق SQL آمن لمنع هجمات الحقن  \n✅ **أدوات قواعد البيانات**: إنشاء قدرات قوية لتحليل المبيعات واستكشاف المخططات  \n✅ **ذكاء الأعمال**: تطوير أدوات تحليل للحصول على رؤى شاملة للأعمال  \n✅ **تحسين الأداء**: تطبيق التخزين المؤقت، إدارة الاتصال، وتحسين الاستعلام  \n✅ **تكامل الأمان**: تنفيذ التحكم في الوصول بناءً على الأدوار وتسجيل التدقيق  \n\n## 🚀 ما التالي\n\nتابع مع **[مختبر 07: تكامل البحث الدلالي](../07-Semantic-Search/README.md)** لـ:\n\n- دمج قدرات البحث المتجهي مع أدوات MCP  \n- بناء وظيفة البحث الدلالي عن المن","11-MCPServerHandsOnLabs/06-Tools/README.md":"# Tool Development\n\n## 🎯 What This Lab Covers\n\nThis lab dives deep into creating sophisticated MCP tools that provide AI assistants with powerful database query capabilities, schema introspection, and analytics functions. You'll learn to build tools that are both powerful and safe, with comprehensive error handling and performance optimization.\n\n## Overview\n\nMCP tools are the interface between AI assistants and your data systems. Well-designed tools provide structured, validated access to complex operations while maintaining security and performance. This lab covers the complete lifecycle of tool development from design to deployment.\n\nOur retail MCP server implements a comprehensive suite of tools that enable natural language querying of sales data, product catalogs, and business analytics while maintaining strict security boundaries and optimal performance.\n\n## Learning Objectives\n\nBy the end of this lab, you will be able to:\n\n- **Design** advanced MCP tools with complex parameter validation\n- **Implement** secure database query tools with SQL injection protection\n- **Create** schema introspection capabilities for dynamic queries\n- **Build** custom analytics tools for business intelligence\n- **Apply** comprehensive error handling and graceful degradation\n- **Optimize** tool performance for production workloads\n\n## 🛠️ Core Tool Architecture\n\n### Tool Design Principles\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Query Validation and Security\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Database Query Tools\n\n### Sales Analysis Tool\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Schema Introspection Tool\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytics and Utility Tools\n\n### Business Intelligence Tool\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Key Takeaways\n\nAfter completing this lab, you should have:\n\n✅ **Advanced Tool Architecture**: Implemented sophisticated MCP tools with comprehensive error handling  \n✅ **Query Validation**: Built secure SQL validation to prevent injection attacks  \n✅ **Database Tools**: Created powerful sales analysis and schema introspection capabilities  \n✅ **Business Intelligence**: Developed analytics tools for comprehensive business insights","AGENTS.md":"# AGENTS.md\n\n## Project Overview\n\n**MCP for Beginners** is an open-source educational curriculum for learning the Model Context Protocol (MCP) - a standardized framework for interactions between AI models and client applications. This repository provides comprehensive learning materials with hands-on code examples across multiple programming languages.\n\n### Key Technologies\n\n- **Programming Languages**: C#, Java, JavaScript, TypeScript, Python, Rust\n- **Frameworks & SDKs**: \n  - MCP SDK (`@modelcontextprotocol/sdk`)\n  - Spring Boot (Java)\n  - FastMCP (Python)\n  - LangChain4j (Java)\n- **Databases**: PostgreSQL with pgvector extension\n- **Cloud Platforms**: Azure (Container Apps, OpenAI, Content Safety, Application Insights)\n- **Build Tools**: npm, Maven, pip, Cargo\n- **Documentation**: Markdown with automated multi-language translation (48+ languages)\n\n### Architecture\n\n- **11 Core Modules (00-11)**: Sequential learning path from fundamentals to advanced topics\n- **Hands-on Labs**: Practical exercises with complete solution code in multiple languages\n- **Sample Projects**: Working MCP server and client implementations\n- **Translation System**: Automated GitHub Actions workflow for multi-language support\n- **Image Assets**: Centralized images directory with translated versions\n\n## Setup Commands\n\nThis is a documentation-focused repository. Most setup occurs within individual sample projects and labs.\n\n### Repository Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/microsoft/mcp-for-beginners.git\ncd mcp-for-beginners\n```\n\n### Working with Sample Projects\n\nSample projects are located in:\n- `03-GettingStarted/samples/` - Language-specific examples\n- `03-GettingStarted/01-first-server/solution/` - First server implementations\n- `03-GettingStarted/02-client/solution/` - Client implementations\n- `11-MCPServerHandsOnLabs/` - Comprehensive database integration labs\n\nEach sample project contains its own setup instructions:\n\n#### TypeScript/JavaScript Projects\n```bash\ncd <project-directory>\nnpm install\nnpm start\n```\n\n#### Python Projects\n```bash\ncd <project-directory>\npip install -r requirements.txt\n# or\npip install -e .\npython main.py\n```\n\n#### Java Projects\n```bash\ncd <project-directory>\nmvn clean install\nmvn spring-boot:run\n```\n\n## Development Workflow\n\n### MCP 7-28 Readiness\n\n#### Repo readiness checklist\n\n- [x] **New contributor clarity**: This file defines repository purpose,\n  structure, contribution rules, and sample setup paths.\n- [x] **Build/test/lint commands with exact flags**:\n  - Repository docs lint:\n    `npx --yes markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#translations\" \"#translated_images\"`\n  - Repository docs link pattern audit:\n    `find . -name \"*.md\" -not -path \"*/node_modules/*\" -not -path \"./translations/*\" -not -path \"./translated_images/*\" -print0 | xargs -0 grep -En \"\\[.*\\]\\(.*\\)\"`\n  - TypeScript sample validation:\n    `cd 03-GettingStarted/samples/typescript && npm ci && npm test && npm run build`\n  - Python sample validation:\n    `cd 10-StreamliningAIWorkflowsBuildingAnMCPServerWithAIToolkit/lab3/code/weather_mcp && python -m pip install -e . && pytest -q`\n  - Java sample validation:\n    `cd 03-GettingStarted/samples/java/calculator && mvn -B -ntp test verify`\n- [x] **One realistic workflow that can become an MCP tool**:\n  `validate_curriculum_change`\n- [x] **Inputs/outputs are explicit** (see specification below).\n- [x] **Permissions and failure modes are documented** (see specification below).\n- [x] **CI testability is explicit** (deterministic commands, explicit\n  exit codes, and machine-readable outputs).\n\n#### Candidate MCP tool workflow: `validate_curriculum_change`\n\n##### Goal\n\nValidate curriculum documentation changes and representative sample code\nhealth before merge.\n\n##### Inputs\n\n- `changed_paths: string[]` (required) - relative paths changed in PR.\n- `run_docs_lint: boolean` (default `true`)\n- `run_links_audit: boolean` (default `true`)\n- `run_samples: { typescript?: boolean, python?: boolean, java?: boolean }`\n  (default all `false`)\n\n##### Outputs\n\n- `status: \"ok\" | \"failed\"`\n- `checks: Array<{ name: string, command: string, exit_code: number,\n  summary: string }>`\n- `artifacts: Array<{ type: \"log\" | \"report\", path: string }>`\n- `failed_checks: string[]`\n\n##### Permissions\n\n- Read workspace files and write tool-generated artifacts (e.g., lint\n  reports, test logs) only; no writes to `translations/` or\n  `translated_images/`.\n- Execute local shell commands.\n- Optional network access only for package restore (`npm ci`,\n  `python -m pip install`, `mvn` dependency resolution).\n- No permission to push, merge, or modify `translations/` or\n  `translated_images/`.\n\n##### Failure modes\n\n- `E_NO_INPUT_PATHS`: `changed_paths` empty.\n- `E_INVALID_PATH`: input path escapes repository root.\n- `E_LINT_FAILED`: markdown lint exits non-zero.\n- `E_LINK_AUDIT_FAILED`: link audit command exits non-zero.\n- `E_SAMPLE_TEST_FAILED`: sample test/build exits non-zero.\n- `E_TIMEOUT`: command exceeded configured timeout.\n\n##### Recommended CI contract\n\nTo automate validation, configure a CI job that:\n\n- Triggers on pull requests touching `*.md`, sample code, or this file.\n- Runs the exact commands listed above.\n- Persists logs as artifacts.\n- Fails the job on any non-zero exit code.\n\n#### If you ship an MCP server from this repo\n\n- [ ] Read the final MCP `2026-07-28` changelog:\n  <https://modelcontextprotocol.io/specification/2026-07-28/changelog>\n- [ ] Verify that the selected SDK release supports MCP `2026-07-28`:\n  <https://modelcontextprotocol.io/docs/sdk>\n- [ ] Remove session and handshake assumptions; treat each request as\n  self-contained:\n  <https://modelcontextprotocol.io/specification/2026-07-28/basic/lifecycle>\n- [ ] Send `Mcp-Method` and `Mcp-Name` headers for raw HTTP requests:\n  <https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http>\n- [ ] Audit hardcoded error codes (`missing resource` moved from `-32002` to `-32602`).\n- [ ] Migrate deprecated Roots, Sampling, Logging, and Dynamic Client\n  Registration:\n  <https://modelcontextprotocol.io/specification/2026-07-28/deprecated>\n- [ ] Migrate off the experimental `2025-11-25` Tasks API:\n  <https://modelcontextprotocol.io/extensions/tasks>\n- [ ] Review authorization for OAuth and OpenID Connect hardening:\n  <https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization>\n\n### Documentation Structure\n\n- **Modules 00-11**: Core curriculum content in sequential order\n- **translations/**: Language-specific versions (auto-generated, do not edit directly)\n- **translated_images/**: Localized image versions (auto-generated)\n- **images/**: Source images and diagrams\n\n### Making Documentation Changes\n\n1. Edit only the English markdown files in the root module directories (00-11)\n2. Update images in the `images/` directory if needed\n3. The co-op-translator GitHub Action will automatically generate translations\n4. Translations are regenerated on push to main branch\n\n### Working with Translations\n\n- **Automated Translation**: GitHub Actions workflow handles all translations\n- **Do NOT manually edit** files in `translations/` directory\n- Translation metadata is embedded in each translated file\n- Supported languages: 48+ languages including Arabic, Chinese, French, German, Hindi, Japanese, Korean, Portuguese, Russian, Spanish, and many more\n\n## Testing Instructions\n\n### Documentation Validation\n\nSince this is primarily a documentation repository, testing focuses on:\n\n1. **Link Pattern Audit**: List Markdown links for review\n\n   ```bash\n   # List Markdown links (pattern audit)\n   find . -name \"*.md\" -not -path \"*/node_modules/*\" -not -path \"./translations/*\" -not -path \"./translated_images/*\" -print0 | xargs -0 grep -En \"\\[.*\\]\\(.*\\)\"\n   ```\n\n2. **Code Sample Validation**: Test that code examples compile/run\n\n   ```bash\n   # Navigate to specific sample and run its tests\n   cd 03-GettingStarted/samples/typescript\n   npm install && npm test\n   ```\n\n3. **Markdown Linting**: Check formatting consistency\n\n   ```bash\n   # Use markdownlint if needed\n   npx --yes markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#translations\" \"#translated_images\"\n   ```\n\n### Sample Project Testing\n\nEach language-specific sample includes its own testing approach:\n\n#### TypeScript/JavaScript\n```bash\nnpm test\nnpm run build\n```\n\n#### Python\n```bash\npytest\npython -m pytest tests/\n```\n\n#### Java\n```bash\nmvn test\nmvn verify\n```\n\n## Code Style Guidelines\n\n### Documentation Style\n\n- Use clear, beginner-friendly language\n- Include code examples in multiple languages where applicable\n- Follow markdown best practices:\n  - Use ATX-style headers (`#` syntax)\n  - Use fenced code blocks with language identifiers\n  - Include descriptive alt text for images\n  - Keep line lengths reasonable (no hard limit, but be sensible)\n\n### Code Sample Style\n\n#### TypeScript/JavaScript\n- Use ES modules (`import`/`export`)\n- Follow TypeScript strict mode conventions\n- Include type annotations\n- Target ES2022\n\n#### Python\n- Follow PEP 8 style guidelines\n- Use type hints where appropriate\n- Include docstrings for functions and classes\n- Use modern Python features (3.8+)\n\n#### Java\n- Follow Spring Boot conventions\n- Use Java 21 features\n- Follow standard Maven project structure\n- Include Javadoc comments\n\n### File Organization\n\n```\n<module-number>-<ModuleName>/\n├── README.md              # Main module content\n├── samples/               # Code examples (if applicable)\n│   ├── typescript/\n│   ├── python/\n│   ├── java/\n│   └── ...\n└── solution/              # Complete working solutions\n    └── <language>/\n```\n\n## Build and Deployment\n\n### Documentation Deployment\n\nThe repository uses GitHub Pages or similar for documentation hosting (if applicable). Changes to the main branch trigger:\n\n1. Translation workflow (`.github/workflows/co-op-translator.yml`)\n2. Automated translation of all English markdown files\n3. Image localization as needed\n\n### No Build Process Required\n\nThis repository primarily contains markdown documentation. No compilation or build step is needed for the core curriculum content.\n\n### Sample Project Deployment\n\nIndividual sample projects may have deployment instructions:\n- See `03-GettingStarted/09-deployment/` for MCP server deployment guidance\n- Azure Container Apps deployment examples in `11-MCPServerHandsOnLabs/`\n\n## Contributing Guidelines\n\n### Pull Request Process\n\n1. **Fork and Clone**: Fork the repository and clone your fork locally\n2. **Create a Branch**: Use descriptive branch names (e.g., `fix/typo-module-3`, `add/python-example`)\n3. **Make Changes**: Edit English markdown files only (not translations)\n4. **Test Locally**: Verify markdown renders correctly\n5. **Submit PR**: Use clear PR titles and descriptions\n6. **CLA**: Sign the Microsoft Contributor License Agreement when prompted\n\n### PR Title Format\n\nUse clear, descriptive titles:\n- `[Module XX] Brief description` for module-specific changes\n- `[Samples] Description` for sample code changes\n- `[Docs] Description` for general documentation updates\n\n### What to Contribute\n\n- Bug fixes in documentation or code samples\n- New code examples in additional languages\n- Clarifications and improvements to existing content\n- New case studies or practical examples\n- Issue reports for unclear or incorrect content\n\n### What NOT to Do\n\n- Do not directly edit files in `translations/` directory\n- Do not edit `translated_images/` directory\n- Do not add large binary files without discussion\n- Do not change translation workflow files without coordination\n\n## Additional Notes\n\n### Repository Maintenance\n\n- **Changelog**: All significant changes are documented in `changelog.md`\n- **Study Guide**: Use `study_guide.md` for curriculum navigation overview\n- **Issue Templates**: Use GitHub issue templates for bug reports and feature requests\n- **Code of Conduct**: All contributors must follow the Microsoft Open Source Code of Conduct\n\n### Learning Path\n\nFollow modules in sequential order (00-11) for optimal learning:\n1. **00-02**: Fundamentals (Introduction, Core Concepts, Security)\n2. **03**: Getting Started with hands-on implementation\n3. **04-05**: Practical implementation and advanced topics\n4. **06-10**: Community, best practices, and real-world applications\n5. **11**: Comprehensive database integration labs (13 sequential labs)\n\n### Support Resources\n\n- **Documentation**: https://modelcontextprotocol.io/\n- **Specification**: https://modelcontextprotocol.io/specification/2026-07-28/\n- **Community**: https://github.com/orgs/modelcontextprotocol/discussions\n- **Discord**: Microsoft Foundry Discord server\n- **Related Courses**: See README.md for other Microsoft learning paths\n\n### Common Troubleshooting\n\n**Q: My PR is failing the translation check**\nA: Ensure you only edited English markdown files in the root module directories, not translated versions.\n\n**Q: How do I add a new language?**\nA: Language support is managed through the co-op-translator workflow. Open an issue to discuss adding new languages.\n\n**Q: Code samples aren't working**\nA: Ensure you've followed the setup instructions in the specific sample's README. Check that you have the correct versions of dependencies installed.\n\n**Q: Images aren't displaying**\nA: Verify image paths are relative and use forward slashes. Images should be in the `images/` directory or `translated_images/` for localized versions.\n\n### Performance Considerations\n\n- Translation workflow may take several minutes to complete\n- Large images should be optimized before committing\n- Keep individual markdown files focused and reasonably sized\n- Use relative links for better portability\n\n### Project Governance\n\nThis project follows Microsoft open source practices:\n- MIT License for code and documentation\n- Microsoft Open Source Code of Conduct\n- CLA required for contributions\n- Security issues: Follow SECURITY.md guidelines\n- Support: See SUPPORT.md for help resources\n","translations/de/11-MCPServerHandsOnLabs/06-Tools/README.md":"# Tool-Entwicklung\n\n## 🎯 Was dieses Lab abdeckt\n\nDieses Lab beschäftigt sich intensiv mit der Entwicklung fortschrittlicher MCP-Tools, die KI-Assistenten leistungsstarke Datenbankabfragefunktionen, Schema-Inspektion und Analysefunktionen bieten. Sie lernen, Tools zu erstellen, die sowohl leistungsstark als auch sicher sind, mit umfassender Fehlerbehandlung und Leistungsoptimierung.\n\n## Überblick\n\nMCP-Tools sind die Schnittstelle zwischen KI-Assistenten und Ihren Datensystemen. Gut gestaltete Tools bieten strukturierten, validierten Zugriff auf komplexe Operationen und gewährleisten dabei Sicherheit und Leistung. Dieses Lab behandelt den gesamten Lebenszyklus der Tool-Entwicklung, von der Konzeption bis zur Bereitstellung.\n\nUnser MCP-Server für den Einzelhandel implementiert eine umfassende Suite von Tools, die natürliche Sprachabfragen von Verkaufsdaten, Produktkatalogen und Geschäftsanalyse ermöglichen, während strenge Sicherheitsgrenzen und optimale Leistung eingehalten werden.\n\n## Lernziele\n\nAm Ende dieses Labs werden Sie in der Lage sein:\n\n- **Entwerfen** fortschrittlicher MCP-Tools mit komplexer Parametervalidierung  \n- **Implementieren** sicherer Datenbankabfragetools mit Schutz vor SQL-Injection  \n- **Erstellen** von Schema-Inspektionsfunktionen für dynamische Abfragen  \n- **Entwickeln** benutzerdefinierter Analysetools für Business Intelligence  \n- **Anwenden** umfassender Fehlerbehandlung und sanfter Degradierung  \n- **Optimieren** der Tool-Leistung für Produktionsumgebungen  \n\n## 🛠️ Kernarchitektur der Tools\n\n### Prinzipien des Tool-Designs\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n  \n### Abfragevalidierung und Sicherheit\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n  \n\n## 🗃️ Datenbankabfragetools\n\n### Verkaufsanalyse-Tool\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n  \n### Schema-Inspektions-Tool\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 📊 Analyse- und Utility-Tools\n\n### Business-Intelligence-Tool\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 🎯 Wichtige Erkenntnisse\n\nNach Abschluss dieses Labs sollten Sie:\n\n✅ **Fortschrittliche Tool-Architektur**: Komplexe MCP-Tools mit umfassender Fehlerbehandlung implementiert haben  \n✅ **Abfragevalidierung**: Sichere SQL-Validierung zur","translations/en/11-MCPServerHandsOnLabs/06-Tools/README.md":"# Tool Development\n\n## 🎯 What This Lab Covers\n\nThis lab focuses on creating advanced MCP tools that equip AI assistants with robust capabilities for database querying, schema exploration, and analytics. You'll learn how to design tools that are both powerful and secure, incorporating thorough error handling and performance optimization.\n\n## Overview\n\nMCP tools act as the bridge between AI assistants and your data systems. Properly designed tools enable structured and validated access to complex operations while ensuring security and efficiency. This lab guides you through the entire process of tool development, from initial design to final deployment.\n\nOur retail MCP server includes a comprehensive set of tools that allow natural language querying of sales data, product catalogs, and business analytics, all while maintaining strict security protocols and high performance.\n\n## Learning Objectives\n\nBy the end of this lab, you will be able to:\n\n- **Design** advanced MCP tools with robust parameter validation\n- **Implement** secure database query tools that protect against SQL injection\n- **Create** schema exploration features for dynamic queries\n- **Develop** custom analytics tools for business intelligence\n- **Apply** thorough error handling and graceful fallback mechanisms\n- **Optimize** tool performance for production environments\n\n## 🛠️ Core Tool Architecture\n\n### Tool Design Principles\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Query Validation and Security\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Database Query Tools\n\n### Sales Analysis Tool\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Schema Introspection Tool\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytics and Utility Tools\n\n### Business Intelligence Tool\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Key Takeaways\n\nAfter completing this lab, you should have:\n\n✅ **Advanced Tool Architecture**: Designed and implemented sophisticated MCP tools with robust error handling  \n✅ **Query Validation**: Developed secure SQL validation mechanisms to prevent injection attacks  \n✅ **Database Tools**: Built powerful tools for sales analysis and schema exploration  \n✅ **Business Intelligence**: Created analytics tools for ","translations/da/11-MCPServerHandsOnLabs/06-Tools/README.md":"# Udvikling af Værktøjer\n\n## 🎯 Hvad Dækker Dette Lab\n\nDette lab går i dybden med at skabe avancerede MCP-værktøjer, der giver AI-assistenter kraftfulde muligheder for databaseforespørgsler, skemainspektion og analytiske funktioner. Du vil lære at bygge værktøjer, der både er effektive og sikre, med omfattende fejlhåndtering og optimering af ydeevne.\n\n## Oversigt\n\nMCP-værktøjer er grænsefladen mellem AI-assistenter og dine datasystemer. Veludformede værktøjer giver struktureret og valideret adgang til komplekse operationer, samtidig med at sikkerhed og ydeevne opretholdes. Dette lab dækker hele livscyklussen for værktøjsudvikling fra design til implementering.\n\nVores MCP-server til detailhandel implementerer en omfattende suite af værktøjer, der muliggør naturlig sprogforespørgsel af salgsdata, produktkataloger og forretningsanalyse, samtidig med at strenge sikkerhedsgrænser og optimal ydeevne opretholdes.\n\n## Læringsmål\n\nVed afslutningen af dette lab vil du kunne:\n\n- **Designe** avancerede MCP-værktøjer med kompleks parametervalidering\n- **Implementere** sikre databaseforespørgselsværktøjer med beskyttelse mod SQL-injektion\n- **Skabe** skemainspektionsfunktioner til dynamiske forespørgsler\n- **Bygge** skræddersyede analytiske værktøjer til forretningsintelligens\n- **Anvende** omfattende fejlhåndtering og elegant nedgradering\n- **Optimere** værktøjets ydeevne til produktionsarbejdsbelastninger\n\n## 🛠️ Grundlæggende Værktøjsarkitektur\n\n### Principper for Værktøjsdesign\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Validering af Forespørgsler og Sikkerhed\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Databaseforespørgselsværktøjer\n\n### Værktøj til Salgsanalyse\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Skemainspektionsværktøj\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytiske og Hjælpeværktøjer\n\n### Værktøj til Forretningsintelligens\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Vigtige Læringspunkter\n\nEfter at have gennemført dette lab, bør du have:\n\n✅ **Avanceret Værktøjsarkitektur**: Implementeret sofistikerede MCP-værktøjer med omfattende fejlhåndtering  \n✅ **Validering af Forespørgsler**: Bygget sikker SQL-validering for at forhindre injektionsangreb  \n✅ **Databaseværk","translations/el/11-MCPServerHandsOnLabs/06-Tools/README.md":"# Ανάπτυξη Εργαλείων\n\n## 🎯 Τι Καλύπτει Αυτό το Εργαστήριο\n\nΑυτό το εργαστήριο εμβαθύνει στη δημιουργία εξελιγμένων εργαλείων MCP που παρέχουν στους βοηθούς AI ισχυρές δυνατότητες ερωτημάτων βάσεων δεδομένων, ανάλυσης σχημάτων και λειτουργιών ανάλυσης. Θα μάθετε να δημιουργείτε εργαλεία που είναι τόσο ισχυρά όσο και ασφαλή, με ολοκληρωμένο χειρισμό σφαλμάτων και βελτιστοποίηση απόδοσης.\n\n## Επισκόπηση\n\nΤα εργαλεία MCP είναι η διεπαφή μεταξύ των βοηθών AI και των συστημάτων δεδομένων σας. Καλά σχεδιασμένα εργαλεία παρέχουν δομημένη, επικυρωμένη πρόσβαση σε σύνθετες λειτουργίες, διατηρώντας παράλληλα την ασφάλεια και την απόδοση. Αυτό το εργαστήριο καλύπτει τον πλήρη κύκλο ζωής της ανάπτυξης εργαλείων, από τον σχεδιασμό έως την υλοποίηση.\n\nΟ MCP server μας για το λιανικό εμπόριο υλοποιεί μια ολοκληρωμένη σουίτα εργαλείων που επιτρέπουν ερωτήματα φυσικής γλώσσας για δεδομένα πωλήσεων, καταλόγους προϊόντων και επιχειρηματική ανάλυση, ενώ διατηρούν αυστηρά όρια ασφαλείας και βέλτιστη απόδοση.\n\n## Στόχοι Μάθησης\n\nΜέχρι το τέλος αυτού του εργαστηρίου, θα μπορείτε να:\n\n- **Σχεδιάσετε** εξελιγμένα εργαλεία MCP με σύνθετη επικύρωση παραμέτρων\n- **Υλοποιήσετε** ασφαλή εργαλεία ερωτημάτων βάσεων δεδομένων με προστασία από SQL injection\n- **Δημιουργήσετε** δυνατότητες ανάλυσης σχημάτων για δυναμικά ερωτήματα\n- **Κατασκευάσετε** προσαρμοσμένα εργαλεία ανάλυσης για επιχειρηματική ευφυΐα\n- **Εφαρμόσετε** ολοκληρωμένο χειρισμό σφαλμάτων και ομαλή υποβάθμιση λειτουργιών\n- **Βελτιστοποιήσετε** την απόδοση εργαλείων για παραγωγικά φορτία\n\n## 🛠️ Βασική Αρχιτεκτονική Εργαλείων\n\n### Αρχές Σχεδιασμού Εργαλείων\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Επικύρωση Ερωτημάτων και Ασφάλεια\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Εργαλεία Ερωτημάτων Βάσεων Δεδομένων\n\n### Εργαλείο Ανάλυσης Πωλήσεων\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Εργαλείο Ανάλυσης Σχημάτων\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Εργαλεία Ανάλυσης και Χρησιμότητας\n\n### Εργαλείο Επιχειρηματικής Ευφυΐας\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Βασικά Συμπεράσματα\n\nΜετά την ολοκλήρωση αυτού του εργαστηρίου, θα έχετε:\n\n✅ **Προηγμένη Αρχιτεκτονική Εργαλείων**: Υλοποιήσει εξελιγμένα εργαλεία MCP με ολοκληρωμένο χειρ","translations/cs/11-MCPServerHandsOnLabs/06-Tools/README.md":"# Vývoj nástrojů\n\n## 🎯 Co tento lab pokrývá\n\nTento lab se podrobně zabývá tvorbou sofistikovaných MCP nástrojů, které poskytují AI asistentům výkonné schopnosti dotazování do databází, introspekce schémat a analytické funkce. Naučíte se vytvářet nástroje, které jsou nejen výkonné, ale také bezpečné, s důkladným zpracováním chyb a optimalizací výkonu.\n\n## Přehled\n\nMCP nástroje představují rozhraní mezi AI asistenty a vašimi datovými systémy. Dobře navržené nástroje poskytují strukturovaný a validovaný přístup ke složitým operacím při zachování bezpečnosti a výkonu. Tento lab pokrývá kompletní životní cyklus vývoje nástrojů od návrhu až po nasazení.\n\nNáš maloobchodní MCP server implementuje komplexní sadu nástrojů, které umožňují přirozené jazykové dotazování na prodejní data, katalogy produktů a obchodní analýzy, přičemž zachovává přísné bezpečnostní hranice a optimální výkon.\n\n## Cíle učení\n\nNa konci tohoto labu budete schopni:\n\n- **Navrhnout** pokročilé MCP nástroje s komplexní validací parametrů\n- **Implementovat** bezpečné nástroje pro dotazování do databází s ochranou proti SQL injekcím\n- **Vytvořit** schopnosti introspekce schémat pro dynamické dotazy\n- **Postavit** vlastní analytické nástroje pro obchodní inteligenci\n- **Aplikovat** důkladné zpracování chyb a postupné zhoršování funkcí\n- **Optimalizovat** výkon nástrojů pro produkční zátěže\n\n## 🛠️ Základní architektura nástrojů\n\n### Principy návrhu nástrojů\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Validace dotazů a bezpečnost\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Nástroje pro dotazování do databází\n\n### Nástroj pro analýzu prodeje\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Nástroj pro introspekci schémat\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytické a užitkové nástroje\n\n### Nástroj pro obchodní inteligenci\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Klíčové poznatky\n\nPo dokončení tohoto labu byste měli mít:\n\n✅ **Pokročilou architekturu nástrojů**: Implementované sofistikované MCP nástroje s důkladným zpracováním chyb  \n✅ **Validaci dotazů**: Vytvořenou bezpečnou SQL validaci pro prevenci útoků injekcí  \n✅ **Databázové nástroje**: Vyvinuté výkonné schopnosti analýzy prodeje a introspekce schémat","translations/es/11-MCPServerHandsOnLabs/06-Tools/README.md":"# Desarrollo de Herramientas\n\n## 🎯 Qué Cubre Este Laboratorio\n\nEste laboratorio profundiza en la creación de herramientas MCP avanzadas que proporcionan a los asistentes de IA capacidades poderosas para consultas de bases de datos, introspección de esquemas y funciones analíticas. Aprenderás a construir herramientas que sean tanto potentes como seguras, con manejo de errores integral y optimización del rendimiento.\n\n## Descripción General\n\nLas herramientas MCP son la interfaz entre los asistentes de IA y tus sistemas de datos. Las herramientas bien diseñadas ofrecen acceso estructurado y validado a operaciones complejas mientras mantienen la seguridad y el rendimiento. Este laboratorio cubre el ciclo completo de desarrollo de herramientas, desde el diseño hasta la implementación.\n\nNuestro servidor MCP para retail implementa un conjunto completo de herramientas que permiten consultas en lenguaje natural sobre datos de ventas, catálogos de productos y análisis de negocios, manteniendo estrictos límites de seguridad y un rendimiento óptimo.\n\n## Objetivos de Aprendizaje\n\nAl finalizar este laboratorio, podrás:\n\n- **Diseñar** herramientas MCP avanzadas con validación compleja de parámetros  \n- **Implementar** herramientas seguras de consulta de bases de datos con protección contra inyección SQL  \n- **Crear** capacidades de introspección de esquemas para consultas dinámicas  \n- **Construir** herramientas analíticas personalizadas para inteligencia empresarial  \n- **Aplicar** manejo de errores integral y degradación controlada  \n- **Optimizar** el rendimiento de las herramientas para cargas de trabajo en producción  \n\n## 🛠️ Arquitectura Central de Herramientas\n\n### Principios de Diseño de Herramientas\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Validación de Consultas y Seguridad\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Herramientas de Consulta de Bases de Datos\n\n### Herramienta de Análisis de Ventas\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Herramienta de Introspección de Esquemas\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Herramientas Analíticas y de Utilidad\n\n### Herramienta de Inteligencia Empresarial\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Puntos Clave\n\nDespués ","translations/et/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/et/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/et/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/fa/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/fa/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/fa/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/fi/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/fi/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/fi/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/fr/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/fr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/fr/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/he/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/he/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/he/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/hi/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/hi/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/hi/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/hr/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/hr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/hr/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/hu/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/hu/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/hu/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/id/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/id/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/id/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/it/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/it/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/it/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ja/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ja/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ja/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/km/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/km/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/km/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/kn/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/kn/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/kn/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ko/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ko/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ko/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/lt/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/lt/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/lt/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ml/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ml/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ml/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/mr/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/mr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/mr/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ms/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ms/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ms/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/my/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/my/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/my/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ne/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ne/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ne/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/nl/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/nl/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/nl/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/no/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/no/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/no/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/pa/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/pa/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pa/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/pcm/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/pcm/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pcm/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/pl/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/pl/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pl/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/pt-BR/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/pt-BR/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pt-BR/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/pt-PT/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/pt-PT/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pt-PT/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ro/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ro/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ro/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ru/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ru/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ru/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/sk/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/sk/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sk/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/sl/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/sl/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sl/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/sr/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/sr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sr/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/sv/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/sv/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sv/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/sw/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/sw/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sw/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ta/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ta/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ta/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/te/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/te/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/te/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/th/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/th/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/th/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/tl/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/tl/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/tl/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/tr/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/tr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/tr/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/uk/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/uk/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/uk/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ur/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ur/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ur/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/vi/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/vi/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/vi/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/zh-CN/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/zh-CN/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-CN/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/zh-HK/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/zh-HK/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-HK/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/zh-MO/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/zh-MO/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-MO/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/zh-TW/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/zh-TW/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-TW/11-MCPServerHandsOnLabs/06-Tools/README.md)"},"files":{"translations/bg/11-MCPServerHandsOnLabs/06-Tools/README.md":"# Разработка на инструменти\n\n## 🎯 Какво обхваща този лабораторен урок\n\nТози лабораторен урок се фокусира върху създаването на усъвършенствани MCP инструменти, които предоставят на AI асистентите мощни възможности за заявки към бази данни, инспекция на схеми и аналитични функции. Ще научите как да изграждате инструменти, които са едновременно мощни и безопасни, с изчерпателно управление на грешки и оптимизация на производителността.\n\n## Обзор\n\nMCP инструментите са интерфейсът между AI асистентите и вашите системи за данни. Добре проектираните инструменти осигуряват структурирани и валидирани операции, като същевременно поддържат сигурност и производителност. Този лабораторен урок обхваща целия жизнен цикъл на разработката на инструменти – от проектиране до внедряване.\n\nНашият MCP сървър за търговия на дребно включва цялостен набор от инструменти, които позволяват заявки на естествен език за данни за продажби, продуктови каталози и бизнес анализи, като същевременно поддържат строги граници на сигурност и оптимална производителност.\n\n## Цели на обучението\n\nДо края на този лабораторен урок ще можете:\n\n- **Да проектирате** усъвършенствани MCP инструменти с комплексна валидация на параметри\n- **Да внедрите** сигурни инструменти за заявки към бази данни с защита срещу SQL инжекции\n- **Да създадете** възможности за инспекция на схеми за динамични заявки\n- **Да изградите** персонализирани аналитични инструменти за бизнес интелигентност\n- **Да приложите** изчерпателно управление на грешки и плавна деградация\n- **Да оптимизирате** производителността на инструментите за работни натоварвания в продукция\n\n## 🛠️ Основна архитектура на инструментите\n\n### Принципи на проектиране на инструменти\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Валидация на заявки и сигурност\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Инструменти за заявки към бази данни\n\n### Инструмент за анализ на продажби\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Инструмент за инспекция на схеми\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Аналитични и помощни инструменти\n\n### Инструмент за бизнес интелигентност\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Основни изводи\n\nСлед завършване на този лабораторен урок, трябва да","09-CaseStudy/docs-mcp/solution/scenario3/mcp.json":"{\n    \"servers\": {\n        \"LearnDocsMCP\": {\n            \"url\": \"https://learn.microsoft.com/api/mcp\"\n        }\n    }\n}","translations/bn/11-MCPServerHandsOnLabs/06-Tools/README.md":"# টুল ডেভেলপমেন্ট\n\n## 🎯 এই ল্যাবে কী শেখানো হবে\n\nএই ল্যাবে আপনি শিখবেন কীভাবে উন্নত MCP টুল তৈরি করতে হয় যা AI অ্যাসিস্ট্যান্টকে শক্তিশালী ডাটাবেস কোয়েরি করার ক্ষমতা, স্কিমা ইনট্রোস্পেকশন এবং অ্যানালিটিক্স ফাংশন প্রদান করে। আপনি এমন টুল তৈরি করতে শিখবেন যা শক্তিশালী এবং নিরাপদ, যেখানে ব্যাপক ত্রুটি পরিচালনা এবং পারফরম্যান্স অপ্টিমাইজেশন অন্তর্ভুক্ত থাকবে।\n\n## ওভারভিউ\n\nMCP টুল হলো AI অ্যাসিস্ট্যান্ট এবং আপনার ডেটা সিস্টেমের মধ্যে ইন্টারফেস। ভালোভাবে ডিজাইন করা টুল জটিল অপারেশনের জন্য গঠনমূলক এবং যাচাই করা অ্যাক্সেস প্রদান করে, একইসঙ্গে নিরাপত্তা এবং পারফরম্যান্স বজায় রাখে। এই ল্যাবে টুল ডেভেলপমেন্টের সম্পূর্ণ জীবনচক্র, ডিজাইন থেকে ডিপ্লয়মেন্ট পর্যন্ত, কভার করা হয়েছে।\n\nআমাদের রিটেইল MCP সার্ভার একটি বিস্তৃত টুলের সেট বাস্তবায়ন করে যা সেলস ডেটা, প্রোডাক্ট ক্যাটালগ এবং বিজনেস অ্যানালিটিক্সের প্রাকৃতিক ভাষায় কোয়েরি করার ক্ষমতা প্রদান করে, কঠোর নিরাপত্তা সীমা এবং সর্বোত্তম পারফরম্যান্স বজায় রেখে।\n\n## শেখার লক্ষ্য\n\nএই ল্যাব শেষে আপনি সক্ষম হবেন:\n\n- **ডিজাইন**: জটিল প্যারামিটার যাচাই সহ উন্নত MCP টুল ডিজাইন করা  \n- **ইমপ্লিমেন্ট**: SQL ইনজেকশন প্রতিরোধ সহ নিরাপদ ডাটাবেস কোয়েরি টুল তৈরি করা  \n- **ক্রিয়েট**: ডায়নামিক কোয়েরির জন্য স্কিমা ইনট্রোস্পেকশন ক্ষমতা তৈরি করা  \n- **বিল্ড**: বিজনেস ইন্টেলিজেন্সের জন্য কাস্টম অ্যানালিটিক্স টুল তৈরি করা  \n- **অ্যাপ্লাই**: ব্যাপক ত্রুটি পরিচালনা এবং গ্রেসফুল ডিগ্রেডেশন প্রয়োগ করা  \n- **অপ্টিমাইজ**: প্রোডাকশন ওয়ার্কলোডের জন্য টুল পারফরম্যান্স অপ্টিমাইজ করা  \n\n## 🛠️ মূল টুল আর্কিটেকচার\n\n### টুল ডিজাইন নীতিমালা\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n  \n### কোয়েরি যাচাই এবং নিরাপত্তা\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n  \n\n## 🗃️ ডাটাবেস কোয়েরি টুল\n\n### সেলস অ্যানালাইসিস টুল\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n  \n### স্কিমা ইনট্রোস্পেকশন টুল\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 📊 অ্যানালিটিক্স এবং ইউটিলিটি টুল\n\n### বিজনেস ইন্টেলিজেন্স টুল\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 🎯 মূল বিষয়গুলো\n\nএই ল্যাব সম্পন্ন করার পর আপনি অর্জন করবেন:\n\n✅ **উন্নত টুল আর্কিটেকচার**: ব্যাপক ত্রুটি পরিচালনা সহ উন্নত MCP টুল বাস্তবায়ন  \n✅ **কোয়েরি যাচাই**: SQL ইনজেকশন আক্রমণ প্রতিরোধের জন্য নিরাপদ SQL যাচাই তৈরি  \n✅ **ডাটাবেস টুল**: শক্তিশালী সেলস অ্যানালাইসিস এবং স্কিমা ইনট্রোস্পেকশন ক্ষমতা তৈরি  \n✅ **বিজনেস ইন্টেলিজেন্স**: ব্যাপক","translations/ar/11-MCPServerHandsOnLabs/06-Tools/README.md":"# تطوير الأدوات\n\n## 🎯 ما يغطيه هذا المختبر\n\nيستعرض هذا المختبر كيفية إنشاء أدوات MCP متقدمة تمنح المساعدين الذكيين قدرات قوية لاستعلام قواعد البيانات، استكشاف المخططات، ووظائف التحليل. ستتعلم كيفية بناء أدوات قوية وآمنة مع معالجة شاملة للأخطاء وتحسين الأداء.\n\n## نظرة عامة\n\nأدوات MCP هي الواجهة بين المساعدين الذكيين وأنظمة البيانات الخاصة بك. الأدوات المصممة بشكل جيد توفر وصولاً منظمًا ومُتحققًا إلى العمليات المعقدة مع الحفاظ على الأمان والأداء. يغطي هذا المختبر دورة حياة تطوير الأدوات بالكامل من التصميم إلى النشر.\n\nيُنفذ خادم MCP الخاص بالتجزئة مجموعة شاملة من الأدوات التي تُمكّن الاستعلام الطبيعي عن بيانات المبيعات، كتالوجات المنتجات، وتحليلات الأعمال مع الحفاظ على حدود أمان صارمة وأداء مثالي.\n\n## أهداف التعلم\n\nبنهاية هذا المختبر، ستكون قادرًا على:\n\n- **تصميم** أدوات MCP متقدمة مع تحقق معقد من المعلمات  \n- **تنفيذ** أدوات استعلام قواعد بيانات آمنة مع حماية من حقن SQL  \n- **إنشاء** قدرات استكشاف المخططات للاستعلامات الديناميكية  \n- **بناء** أدوات تحليل مخصصة لذكاء الأعمال  \n- **تطبيق** معالجة شاملة للأخطاء وتدهور سلس  \n- **تحسين** أداء الأدوات لتناسب أعباء العمل الإنتاجية  \n\n## 🛠️ بنية الأدوات الأساسية\n\n### مبادئ تصميم الأدوات\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n  \n### التحقق من الاستعلام والأمان\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n  \n\n## 🗃️ أدوات استعلام قواعد البيانات\n\n### أداة تحليل المبيعات\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n  \n### أداة استكشاف المخططات\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 📊 أدوات التحليل والمرافق\n\n### أداة ذكاء الأعمال\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 🎯 النقاط الرئيسية\n\nبعد إكمال هذا المختبر، يجب أن تكون قد حققت:\n\n✅ **بنية أدوات متقدمة**: تنفيذ أدوات MCP متطورة مع معالجة شاملة للأخطاء  \n✅ **التحقق من الاستعلام**: بناء تحقق SQL آمن لمنع هجمات الحقن  \n✅ **أدوات قواعد البيانات**: إنشاء قدرات قوية لتحليل المبيعات واستكشاف المخططات  \n✅ **ذكاء الأعمال**: تطوير أدوات تحليل للحصول على رؤى شاملة للأعمال  \n✅ **تحسين الأداء**: تطبيق التخزين المؤقت، إدارة الاتصال، وتحسين الاستعلام  \n✅ **تكامل الأمان**: تنفيذ التحكم في الوصول بناءً على الأدوار وتسجيل التدقيق  \n\n## 🚀 ما التالي\n\nتابع مع **[مختبر 07: تكامل البحث الدلالي](../07-Semantic-Search/README.md)** لـ:\n\n- دمج قدرات البحث المتجهي مع أدوات MCP  \n- بناء وظيفة البحث الدلالي عن المن","11-MCPServerHandsOnLabs/06-Tools/README.md":"# Tool Development\n\n## 🎯 What This Lab Covers\n\nThis lab dives deep into creating sophisticated MCP tools that provide AI assistants with powerful database query capabilities, schema introspection, and analytics functions. You'll learn to build tools that are both powerful and safe, with comprehensive error handling and performance optimization.\n\n## Overview\n\nMCP tools are the interface between AI assistants and your data systems. Well-designed tools provide structured, validated access to complex operations while maintaining security and performance. This lab covers the complete lifecycle of tool development from design to deployment.\n\nOur retail MCP server implements a comprehensive suite of tools that enable natural language querying of sales data, product catalogs, and business analytics while maintaining strict security boundaries and optimal performance.\n\n## Learning Objectives\n\nBy the end of this lab, you will be able to:\n\n- **Design** advanced MCP tools with complex parameter validation\n- **Implement** secure database query tools with SQL injection protection\n- **Create** schema introspection capabilities for dynamic queries\n- **Build** custom analytics tools for business intelligence\n- **Apply** comprehensive error handling and graceful degradation\n- **Optimize** tool performance for production workloads\n\n## 🛠️ Core Tool Architecture\n\n### Tool Design Principles\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Query Validation and Security\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Database Query Tools\n\n### Sales Analysis Tool\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Schema Introspection Tool\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytics and Utility Tools\n\n### Business Intelligence Tool\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Key Takeaways\n\nAfter completing this lab, you should have:\n\n✅ **Advanced Tool Architecture**: Implemented sophisticated MCP tools with comprehensive error handling  \n✅ **Query Validation**: Built secure SQL validation to prevent injection attacks  \n✅ **Database Tools**: Created powerful sales analysis and schema introspection capabilities  \n✅ **Business Intelligence**: Developed analytics tools for comprehensive business insights","AGENTS.md":"# AGENTS.md\n\n## Project Overview\n\n**MCP for Beginners** is an open-source educational curriculum for learning the Model Context Protocol (MCP) - a standardized framework for interactions between AI models and client applications. This repository provides comprehensive learning materials with hands-on code examples across multiple programming languages.\n\n### Key Technologies\n\n- **Programming Languages**: C#, Java, JavaScript, TypeScript, Python, Rust\n- **Frameworks & SDKs**: \n  - MCP SDK (`@modelcontextprotocol/sdk`)\n  - Spring Boot (Java)\n  - FastMCP (Python)\n  - LangChain4j (Java)\n- **Databases**: PostgreSQL with pgvector extension\n- **Cloud Platforms**: Azure (Container Apps, OpenAI, Content Safety, Application Insights)\n- **Build Tools**: npm, Maven, pip, Cargo\n- **Documentation**: Markdown with automated multi-language translation (48+ languages)\n\n### Architecture\n\n- **11 Core Modules (00-11)**: Sequential learning path from fundamentals to advanced topics\n- **Hands-on Labs**: Practical exercises with complete solution code in multiple languages\n- **Sample Projects**: Working MCP server and client implementations\n- **Translation System**: Automated GitHub Actions workflow for multi-language support\n- **Image Assets**: Centralized images directory with translated versions\n\n## Setup Commands\n\nThis is a documentation-focused repository. Most setup occurs within individual sample projects and labs.\n\n### Repository Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/microsoft/mcp-for-beginners.git\ncd mcp-for-beginners\n```\n\n### Working with Sample Projects\n\nSample projects are located in:\n- `03-GettingStarted/samples/` - Language-specific examples\n- `03-GettingStarted/01-first-server/solution/` - First server implementations\n- `03-GettingStarted/02-client/solution/` - Client implementations\n- `11-MCPServerHandsOnLabs/` - Comprehensive database integration labs\n\nEach sample project contains its own setup instructions:\n\n#### TypeScript/JavaScript Projects\n```bash\ncd <project-directory>\nnpm install\nnpm start\n```\n\n#### Python Projects\n```bash\ncd <project-directory>\npip install -r requirements.txt\n# or\npip install -e .\npython main.py\n```\n\n#### Java Projects\n```bash\ncd <project-directory>\nmvn clean install\nmvn spring-boot:run\n```\n\n## Development Workflow\n\n### MCP 7-28 Readiness\n\n#### Repo readiness checklist\n\n- [x] **New contributor clarity**: This file defines repository purpose,\n  structure, contribution rules, and sample setup paths.\n- [x] **Build/test/lint commands with exact flags**:\n  - Repository docs lint:\n    `npx --yes markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#translations\" \"#translated_images\"`\n  - Repository docs link pattern audit:\n    `find . -name \"*.md\" -not -path \"*/node_modules/*\" -not -path \"./translations/*\" -not -path \"./translated_images/*\" -print0 | xargs -0 grep -En \"\\[.*\\]\\(.*\\)\"`\n  - TypeScript sample validation:\n    `cd 03-GettingStarted/samples/typescript && npm ci && npm test && npm run build`\n  - Python sample validation:\n    `cd 10-StreamliningAIWorkflowsBuildingAnMCPServerWithAIToolkit/lab3/code/weather_mcp && python -m pip install -e . && pytest -q`\n  - Java sample validation:\n    `cd 03-GettingStarted/samples/java/calculator && mvn -B -ntp test verify`\n- [x] **One realistic workflow that can become an MCP tool**:\n  `validate_curriculum_change`\n- [x] **Inputs/outputs are explicit** (see specification below).\n- [x] **Permissions and failure modes are documented** (see specification below).\n- [x] **CI testability is explicit** (deterministic commands, explicit\n  exit codes, and machine-readable outputs).\n\n#### Candidate MCP tool workflow: `validate_curriculum_change`\n\n##### Goal\n\nValidate curriculum documentation changes and representative sample code\nhealth before merge.\n\n##### Inputs\n\n- `changed_paths: string[]` (required) - relative paths changed in PR.\n- `run_docs_lint: boolean` (default `true`)\n- `run_links_audit: boolean` (default `true`)\n- `run_samples: { typescript?: boolean, python?: boolean, java?: boolean }`\n  (default all `false`)\n\n##### Outputs\n\n- `status: \"ok\" | \"failed\"`\n- `checks: Array<{ name: string, command: string, exit_code: number,\n  summary: string }>`\n- `artifacts: Array<{ type: \"log\" | \"report\", path: string }>`\n- `failed_checks: string[]`\n\n##### Permissions\n\n- Read workspace files and write tool-generated artifacts (e.g., lint\n  reports, test logs) only; no writes to `translations/` or\n  `translated_images/`.\n- Execute local shell commands.\n- Optional network access only for package restore (`npm ci`,\n  `python -m pip install`, `mvn` dependency resolution).\n- No permission to push, merge, or modify `translations/` or\n  `translated_images/`.\n\n##### Failure modes\n\n- `E_NO_INPUT_PATHS`: `changed_paths` empty.\n- `E_INVALID_PATH`: input path escapes repository root.\n- `E_LINT_FAILED`: markdown lint exits non-zero.\n- `E_LINK_AUDIT_FAILED`: link audit command exits non-zero.\n- `E_SAMPLE_TEST_FAILED`: sample test/build exits non-zero.\n- `E_TIMEOUT`: command exceeded configured timeout.\n\n##### Recommended CI contract\n\nTo automate validation, configure a CI job that:\n\n- Triggers on pull requests touching `*.md`, sample code, or this file.\n- Runs the exact commands listed above.\n- Persists logs as artifacts.\n- Fails the job on any non-zero exit code.\n\n#### If you ship an MCP server from this repo\n\n- [ ] Read the final MCP `2026-07-28` changelog:\n  <https://modelcontextprotocol.io/specification/2026-07-28/changelog>\n- [ ] Verify that the selected SDK release supports MCP `2026-07-28`:\n  <https://modelcontextprotocol.io/docs/sdk>\n- [ ] Remove session and handshake assumptions; treat each request as\n  self-contained:\n  <https://modelcontextprotocol.io/specification/2026-07-28/basic/lifecycle>\n- [ ] Send `Mcp-Method` and `Mcp-Name` headers for raw HTTP requests:\n  <https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http>\n- [ ] Audit hardcoded error codes (`missing resource` moved from `-32002` to `-32602`).\n- [ ] Migrate deprecated Roots, Sampling, Logging, and Dynamic Client\n  Registration:\n  <https://modelcontextprotocol.io/specification/2026-07-28/deprecated>\n- [ ] Migrate off the experimental `2025-11-25` Tasks API:\n  <https://modelcontextprotocol.io/extensions/tasks>\n- [ ] Review authorization for OAuth and OpenID Connect hardening:\n  <https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization>\n\n### Documentation Structure\n\n- **Modules 00-11**: Core curriculum content in sequential order\n- **translations/**: Language-specific versions (auto-generated, do not edit directly)\n- **translated_images/**: Localized image versions (auto-generated)\n- **images/**: Source images and diagrams\n\n### Making Documentation Changes\n\n1. Edit only the English markdown files in the root module directories (00-11)\n2. Update images in the `images/` directory if needed\n3. The co-op-translator GitHub Action will automatically generate translations\n4. Translations are regenerated on push to main branch\n\n### Working with Translations\n\n- **Automated Translation**: GitHub Actions workflow handles all translations\n- **Do NOT manually edit** files in `translations/` directory\n- Translation metadata is embedded in each translated file\n- Supported languages: 48+ languages including Arabic, Chinese, French, German, Hindi, Japanese, Korean, Portuguese, Russian, Spanish, and many more\n\n## Testing Instructions\n\n### Documentation Validation\n\nSince this is primarily a documentation repository, testing focuses on:\n\n1. **Link Pattern Audit**: List Markdown links for review\n\n   ```bash\n   # List Markdown links (pattern audit)\n   find . -name \"*.md\" -not -path \"*/node_modules/*\" -not -path \"./translations/*\" -not -path \"./translated_images/*\" -print0 | xargs -0 grep -En \"\\[.*\\]\\(.*\\)\"\n   ```\n\n2. **Code Sample Validation**: Test that code examples compile/run\n\n   ```bash\n   # Navigate to specific sample and run its tests\n   cd 03-GettingStarted/samples/typescript\n   npm install && npm test\n   ```\n\n3. **Markdown Linting**: Check formatting consistency\n\n   ```bash\n   # Use markdownlint if needed\n   npx --yes markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#translations\" \"#translated_images\"\n   ```\n\n### Sample Project Testing\n\nEach language-specific sample includes its own testing approach:\n\n#### TypeScript/JavaScript\n```bash\nnpm test\nnpm run build\n```\n\n#### Python\n```bash\npytest\npython -m pytest tests/\n```\n\n#### Java\n```bash\nmvn test\nmvn verify\n```\n\n## Code Style Guidelines\n\n### Documentation Style\n\n- Use clear, beginner-friendly language\n- Include code examples in multiple languages where applicable\n- Follow markdown best practices:\n  - Use ATX-style headers (`#` syntax)\n  - Use fenced code blocks with language identifiers\n  - Include descriptive alt text for images\n  - Keep line lengths reasonable (no hard limit, but be sensible)\n\n### Code Sample Style\n\n#### TypeScript/JavaScript\n- Use ES modules (`import`/`export`)\n- Follow TypeScript strict mode conventions\n- Include type annotations\n- Target ES2022\n\n#### Python\n- Follow PEP 8 style guidelines\n- Use type hints where appropriate\n- Include docstrings for functions and classes\n- Use modern Python features (3.8+)\n\n#### Java\n- Follow Spring Boot conventions\n- Use Java 21 features\n- Follow standard Maven project structure\n- Include Javadoc comments\n\n### File Organization\n\n```\n<module-number>-<ModuleName>/\n├── README.md              # Main module content\n├── samples/               # Code examples (if applicable)\n│   ├── typescript/\n│   ├── python/\n│   ├── java/\n│   └── ...\n└── solution/              # Complete working solutions\n    └── <language>/\n```\n\n## Build and Deployment\n\n### Documentation Deployment\n\nThe repository uses GitHub Pages or similar for documentation hosting (if applicable). Changes to the main branch trigger:\n\n1. Translation workflow (`.github/workflows/co-op-translator.yml`)\n2. Automated translation of all English markdown files\n3. Image localization as needed\n\n### No Build Process Required\n\nThis repository primarily contains markdown documentation. No compilation or build step is needed for the core curriculum content.\n\n### Sample Project Deployment\n\nIndividual sample projects may have deployment instructions:\n- See `03-GettingStarted/09-deployment/` for MCP server deployment guidance\n- Azure Container Apps deployment examples in `11-MCPServerHandsOnLabs/`\n\n## Contributing Guidelines\n\n### Pull Request Process\n\n1. **Fork and Clone**: Fork the repository and clone your fork locally\n2. **Create a Branch**: Use descriptive branch names (e.g., `fix/typo-module-3`, `add/python-example`)\n3. **Make Changes**: Edit English markdown files only (not translations)\n4. **Test Locally**: Verify markdown renders correctly\n5. **Submit PR**: Use clear PR titles and descriptions\n6. **CLA**: Sign the Microsoft Contributor License Agreement when prompted\n\n### PR Title Format\n\nUse clear, descriptive titles:\n- `[Module XX] Brief description` for module-specific changes\n- `[Samples] Description` for sample code changes\n- `[Docs] Description` for general documentation updates\n\n### What to Contribute\n\n- Bug fixes in documentation or code samples\n- New code examples in additional languages\n- Clarifications and improvements to existing content\n- New case studies or practical examples\n- Issue reports for unclear or incorrect content\n\n### What NOT to Do\n\n- Do not directly edit files in `translations/` directory\n- Do not edit `translated_images/` directory\n- Do not add large binary files without discussion\n- Do not change translation workflow files without coordination\n\n## Additional Notes\n\n### Repository Maintenance\n\n- **Changelog**: All significant changes are documented in `changelog.md`\n- **Study Guide**: Use `study_guide.md` for curriculum navigation overview\n- **Issue Templates**: Use GitHub issue templates for bug reports and feature requests\n- **Code of Conduct**: All contributors must follow the Microsoft Open Source Code of Conduct\n\n### Learning Path\n\nFollow modules in sequential order (00-11) for optimal learning:\n1. **00-02**: Fundamentals (Introduction, Core Concepts, Security)\n2. **03**: Getting Started with hands-on implementation\n3. **04-05**: Practical implementation and advanced topics\n4. **06-10**: Community, best practices, and real-world applications\n5. **11**: Comprehensive database integration labs (13 sequential labs)\n\n### Support Resources\n\n- **Documentation**: https://modelcontextprotocol.io/\n- **Specification**: https://modelcontextprotocol.io/specification/2026-07-28/\n- **Community**: https://github.com/orgs/modelcontextprotocol/discussions\n- **Discord**: Microsoft Foundry Discord server\n- **Related Courses**: See README.md for other Microsoft learning paths\n\n### Common Troubleshooting\n\n**Q: My PR is failing the translation check**\nA: Ensure you only edited English markdown files in the root module directories, not translated versions.\n\n**Q: How do I add a new language?**\nA: Language support is managed through the co-op-translator workflow. Open an issue to discuss adding new languages.\n\n**Q: Code samples aren't working**\nA: Ensure you've followed the setup instructions in the specific sample's README. Check that you have the correct versions of dependencies installed.\n\n**Q: Images aren't displaying**\nA: Verify image paths are relative and use forward slashes. Images should be in the `images/` directory or `translated_images/` for localized versions.\n\n### Performance Considerations\n\n- Translation workflow may take several minutes to complete\n- Large images should be optimized before committing\n- Keep individual markdown files focused and reasonably sized\n- Use relative links for better portability\n\n### Project Governance\n\nThis project follows Microsoft open source practices:\n- MIT License for code and documentation\n- Microsoft Open Source Code of Conduct\n- CLA required for contributions\n- Security issues: Follow SECURITY.md guidelines\n- Support: See SUPPORT.md for help resources\n","translations/de/11-MCPServerHandsOnLabs/06-Tools/README.md":"# Tool-Entwicklung\n\n## 🎯 Was dieses Lab abdeckt\n\nDieses Lab beschäftigt sich intensiv mit der Entwicklung fortschrittlicher MCP-Tools, die KI-Assistenten leistungsstarke Datenbankabfragefunktionen, Schema-Inspektion und Analysefunktionen bieten. Sie lernen, Tools zu erstellen, die sowohl leistungsstark als auch sicher sind, mit umfassender Fehlerbehandlung und Leistungsoptimierung.\n\n## Überblick\n\nMCP-Tools sind die Schnittstelle zwischen KI-Assistenten und Ihren Datensystemen. Gut gestaltete Tools bieten strukturierten, validierten Zugriff auf komplexe Operationen und gewährleisten dabei Sicherheit und Leistung. Dieses Lab behandelt den gesamten Lebenszyklus der Tool-Entwicklung, von der Konzeption bis zur Bereitstellung.\n\nUnser MCP-Server für den Einzelhandel implementiert eine umfassende Suite von Tools, die natürliche Sprachabfragen von Verkaufsdaten, Produktkatalogen und Geschäftsanalyse ermöglichen, während strenge Sicherheitsgrenzen und optimale Leistung eingehalten werden.\n\n## Lernziele\n\nAm Ende dieses Labs werden Sie in der Lage sein:\n\n- **Entwerfen** fortschrittlicher MCP-Tools mit komplexer Parametervalidierung  \n- **Implementieren** sicherer Datenbankabfragetools mit Schutz vor SQL-Injection  \n- **Erstellen** von Schema-Inspektionsfunktionen für dynamische Abfragen  \n- **Entwickeln** benutzerdefinierter Analysetools für Business Intelligence  \n- **Anwenden** umfassender Fehlerbehandlung und sanfter Degradierung  \n- **Optimieren** der Tool-Leistung für Produktionsumgebungen  \n\n## 🛠️ Kernarchitektur der Tools\n\n### Prinzipien des Tool-Designs\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n  \n### Abfragevalidierung und Sicherheit\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n  \n\n## 🗃️ Datenbankabfragetools\n\n### Verkaufsanalyse-Tool\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n  \n### Schema-Inspektions-Tool\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 📊 Analyse- und Utility-Tools\n\n### Business-Intelligence-Tool\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 🎯 Wichtige Erkenntnisse\n\nNach Abschluss dieses Labs sollten Sie:\n\n✅ **Fortschrittliche Tool-Architektur**: Komplexe MCP-Tools mit umfassender Fehlerbehandlung implementiert haben  \n✅ **Abfragevalidierung**: Sichere SQL-Validierung zur","translations/en/11-MCPServerHandsOnLabs/06-Tools/README.md":"# Tool Development\n\n## 🎯 What This Lab Covers\n\nThis lab focuses on creating advanced MCP tools that equip AI assistants with robust capabilities for database querying, schema exploration, and analytics. You'll learn how to design tools that are both powerful and secure, incorporating thorough error handling and performance optimization.\n\n## Overview\n\nMCP tools act as the bridge between AI assistants and your data systems. Properly designed tools enable structured and validated access to complex operations while ensuring security and efficiency. This lab guides you through the entire process of tool development, from initial design to final deployment.\n\nOur retail MCP server includes a comprehensive set of tools that allow natural language querying of sales data, product catalogs, and business analytics, all while maintaining strict security protocols and high performance.\n\n## Learning Objectives\n\nBy the end of this lab, you will be able to:\n\n- **Design** advanced MCP tools with robust parameter validation\n- **Implement** secure database query tools that protect against SQL injection\n- **Create** schema exploration features for dynamic queries\n- **Develop** custom analytics tools for business intelligence\n- **Apply** thorough error handling and graceful fallback mechanisms\n- **Optimize** tool performance for production environments\n\n## 🛠️ Core Tool Architecture\n\n### Tool Design Principles\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Query Validation and Security\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Database Query Tools\n\n### Sales Analysis Tool\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Schema Introspection Tool\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytics and Utility Tools\n\n### Business Intelligence Tool\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Key Takeaways\n\nAfter completing this lab, you should have:\n\n✅ **Advanced Tool Architecture**: Designed and implemented sophisticated MCP tools with robust error handling  \n✅ **Query Validation**: Developed secure SQL validation mechanisms to prevent injection attacks  \n✅ **Database Tools**: Built powerful tools for sales analysis and schema exploration  \n✅ **Business Intelligence**: Created analytics tools for ","translations/da/11-MCPServerHandsOnLabs/06-Tools/README.md":"# Udvikling af Værktøjer\n\n## 🎯 Hvad Dækker Dette Lab\n\nDette lab går i dybden med at skabe avancerede MCP-værktøjer, der giver AI-assistenter kraftfulde muligheder for databaseforespørgsler, skemainspektion og analytiske funktioner. Du vil lære at bygge værktøjer, der både er effektive og sikre, med omfattende fejlhåndtering og optimering af ydeevne.\n\n## Oversigt\n\nMCP-værktøjer er grænsefladen mellem AI-assistenter og dine datasystemer. Veludformede værktøjer giver struktureret og valideret adgang til komplekse operationer, samtidig med at sikkerhed og ydeevne opretholdes. Dette lab dækker hele livscyklussen for værktøjsudvikling fra design til implementering.\n\nVores MCP-server til detailhandel implementerer en omfattende suite af værktøjer, der muliggør naturlig sprogforespørgsel af salgsdata, produktkataloger og forretningsanalyse, samtidig med at strenge sikkerhedsgrænser og optimal ydeevne opretholdes.\n\n## Læringsmål\n\nVed afslutningen af dette lab vil du kunne:\n\n- **Designe** avancerede MCP-værktøjer med kompleks parametervalidering\n- **Implementere** sikre databaseforespørgselsværktøjer med beskyttelse mod SQL-injektion\n- **Skabe** skemainspektionsfunktioner til dynamiske forespørgsler\n- **Bygge** skræddersyede analytiske værktøjer til forretningsintelligens\n- **Anvende** omfattende fejlhåndtering og elegant nedgradering\n- **Optimere** værktøjets ydeevne til produktionsarbejdsbelastninger\n\n## 🛠️ Grundlæggende Værktøjsarkitektur\n\n### Principper for Værktøjsdesign\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Validering af Forespørgsler og Sikkerhed\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Databaseforespørgselsværktøjer\n\n### Værktøj til Salgsanalyse\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Skemainspektionsværktøj\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytiske og Hjælpeværktøjer\n\n### Værktøj til Forretningsintelligens\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Vigtige Læringspunkter\n\nEfter at have gennemført dette lab, bør du have:\n\n✅ **Avanceret Værktøjsarkitektur**: Implementeret sofistikerede MCP-værktøjer med omfattende fejlhåndtering  \n✅ **Validering af Forespørgsler**: Bygget sikker SQL-validering for at forhindre injektionsangreb  \n✅ **Databaseværk","translations/el/11-MCPServerHandsOnLabs/06-Tools/README.md":"# Ανάπτυξη Εργαλείων\n\n## 🎯 Τι Καλύπτει Αυτό το Εργαστήριο\n\nΑυτό το εργαστήριο εμβαθύνει στη δημιουργία εξελιγμένων εργαλείων MCP που παρέχουν στους βοηθούς AI ισχυρές δυνατότητες ερωτημάτων βάσεων δεδομένων, ανάλυσης σχημάτων και λειτουργιών ανάλυσης. Θα μάθετε να δημιουργείτε εργαλεία που είναι τόσο ισχυρά όσο και ασφαλή, με ολοκληρωμένο χειρισμό σφαλμάτων και βελτιστοποίηση απόδοσης.\n\n## Επισκόπηση\n\nΤα εργαλεία MCP είναι η διεπαφή μεταξύ των βοηθών AI και των συστημάτων δεδομένων σας. Καλά σχεδιασμένα εργαλεία παρέχουν δομημένη, επικυρωμένη πρόσβαση σε σύνθετες λειτουργίες, διατηρώντας παράλληλα την ασφάλεια και την απόδοση. Αυτό το εργαστήριο καλύπτει τον πλήρη κύκλο ζωής της ανάπτυξης εργαλείων, από τον σχεδιασμό έως την υλοποίηση.\n\nΟ MCP server μας για το λιανικό εμπόριο υλοποιεί μια ολοκληρωμένη σουίτα εργαλείων που επιτρέπουν ερωτήματα φυσικής γλώσσας για δεδομένα πωλήσεων, καταλόγους προϊόντων και επιχειρηματική ανάλυση, ενώ διατηρούν αυστηρά όρια ασφαλείας και βέλτιστη απόδοση.\n\n## Στόχοι Μάθησης\n\nΜέχρι το τέλος αυτού του εργαστηρίου, θα μπορείτε να:\n\n- **Σχεδιάσετε** εξελιγμένα εργαλεία MCP με σύνθετη επικύρωση παραμέτρων\n- **Υλοποιήσετε** ασφαλή εργαλεία ερωτημάτων βάσεων δεδομένων με προστασία από SQL injection\n- **Δημιουργήσετε** δυνατότητες ανάλυσης σχημάτων για δυναμικά ερωτήματα\n- **Κατασκευάσετε** προσαρμοσμένα εργαλεία ανάλυσης για επιχειρηματική ευφυΐα\n- **Εφαρμόσετε** ολοκληρωμένο χειρισμό σφαλμάτων και ομαλή υποβάθμιση λειτουργιών\n- **Βελτιστοποιήσετε** την απόδοση εργαλείων για παραγωγικά φορτία\n\n## 🛠️ Βασική Αρχιτεκτονική Εργαλείων\n\n### Αρχές Σχεδιασμού Εργαλείων\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Επικύρωση Ερωτημάτων και Ασφάλεια\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Εργαλεία Ερωτημάτων Βάσεων Δεδομένων\n\n### Εργαλείο Ανάλυσης Πωλήσεων\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Εργαλείο Ανάλυσης Σχημάτων\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Εργαλεία Ανάλυσης και Χρησιμότητας\n\n### Εργαλείο Επιχειρηματικής Ευφυΐας\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Βασικά Συμπεράσματα\n\nΜετά την ολοκλήρωση αυτού του εργαστηρίου, θα έχετε:\n\n✅ **Προηγμένη Αρχιτεκτονική Εργαλείων**: Υλοποιήσει εξελιγμένα εργαλεία MCP με ολοκληρωμένο χειρ","translations/cs/11-MCPServerHandsOnLabs/06-Tools/README.md":"# Vývoj nástrojů\n\n## 🎯 Co tento lab pokrývá\n\nTento lab se podrobně zabývá tvorbou sofistikovaných MCP nástrojů, které poskytují AI asistentům výkonné schopnosti dotazování do databází, introspekce schémat a analytické funkce. Naučíte se vytvářet nástroje, které jsou nejen výkonné, ale také bezpečné, s důkladným zpracováním chyb a optimalizací výkonu.\n\n## Přehled\n\nMCP nástroje představují rozhraní mezi AI asistenty a vašimi datovými systémy. Dobře navržené nástroje poskytují strukturovaný a validovaný přístup ke složitým operacím při zachování bezpečnosti a výkonu. Tento lab pokrývá kompletní životní cyklus vývoje nástrojů od návrhu až po nasazení.\n\nNáš maloobchodní MCP server implementuje komplexní sadu nástrojů, které umožňují přirozené jazykové dotazování na prodejní data, katalogy produktů a obchodní analýzy, přičemž zachovává přísné bezpečnostní hranice a optimální výkon.\n\n## Cíle učení\n\nNa konci tohoto labu budete schopni:\n\n- **Navrhnout** pokročilé MCP nástroje s komplexní validací parametrů\n- **Implementovat** bezpečné nástroje pro dotazování do databází s ochranou proti SQL injekcím\n- **Vytvořit** schopnosti introspekce schémat pro dynamické dotazy\n- **Postavit** vlastní analytické nástroje pro obchodní inteligenci\n- **Aplikovat** důkladné zpracování chyb a postupné zhoršování funkcí\n- **Optimalizovat** výkon nástrojů pro produkční zátěže\n\n## 🛠️ Základní architektura nástrojů\n\n### Principy návrhu nástrojů\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Validace dotazů a bezpečnost\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Nástroje pro dotazování do databází\n\n### Nástroj pro analýzu prodeje\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Nástroj pro introspekci schémat\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytické a užitkové nástroje\n\n### Nástroj pro obchodní inteligenci\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Klíčové poznatky\n\nPo dokončení tohoto labu byste měli mít:\n\n✅ **Pokročilou architekturu nástrojů**: Implementované sofistikované MCP nástroje s důkladným zpracováním chyb  \n✅ **Validaci dotazů**: Vytvořenou bezpečnou SQL validaci pro prevenci útoků injekcí  \n✅ **Databázové nástroje**: Vyvinuté výkonné schopnosti analýzy prodeje a introspekce schémat","translations/es/11-MCPServerHandsOnLabs/06-Tools/README.md":"# Desarrollo de Herramientas\n\n## 🎯 Qué Cubre Este Laboratorio\n\nEste laboratorio profundiza en la creación de herramientas MCP avanzadas que proporcionan a los asistentes de IA capacidades poderosas para consultas de bases de datos, introspección de esquemas y funciones analíticas. Aprenderás a construir herramientas que sean tanto potentes como seguras, con manejo de errores integral y optimización del rendimiento.\n\n## Descripción General\n\nLas herramientas MCP son la interfaz entre los asistentes de IA y tus sistemas de datos. Las herramientas bien diseñadas ofrecen acceso estructurado y validado a operaciones complejas mientras mantienen la seguridad y el rendimiento. Este laboratorio cubre el ciclo completo de desarrollo de herramientas, desde el diseño hasta la implementación.\n\nNuestro servidor MCP para retail implementa un conjunto completo de herramientas que permiten consultas en lenguaje natural sobre datos de ventas, catálogos de productos y análisis de negocios, manteniendo estrictos límites de seguridad y un rendimiento óptimo.\n\n## Objetivos de Aprendizaje\n\nAl finalizar este laboratorio, podrás:\n\n- **Diseñar** herramientas MCP avanzadas con validación compleja de parámetros  \n- **Implementar** herramientas seguras de consulta de bases de datos con protección contra inyección SQL  \n- **Crear** capacidades de introspección de esquemas para consultas dinámicas  \n- **Construir** herramientas analíticas personalizadas para inteligencia empresarial  \n- **Aplicar** manejo de errores integral y degradación controlada  \n- **Optimizar** el rendimiento de las herramientas para cargas de trabajo en producción  \n\n## 🛠️ Arquitectura Central de Herramientas\n\n### Principios de Diseño de Herramientas\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Validación de Consultas y Seguridad\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Herramientas de Consulta de Bases de Datos\n\n### Herramienta de Análisis de Ventas\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Herramienta de Introspección de Esquemas\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Herramientas Analíticas y de Utilidad\n\n### Herramienta de Inteligencia Empresarial\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Puntos Clave\n\nDespués ","translations/et/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/et/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/et/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/fa/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/fa/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/fa/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/fi/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/fi/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/fi/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/fr/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/fr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/fr/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/he/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/he/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/he/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/hi/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/hi/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/hi/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/hr/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/hr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/hr/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/hu/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/hu/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/hu/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/id/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/id/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/id/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/it/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/it/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/it/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ja/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ja/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ja/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/km/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/km/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/km/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/kn/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/kn/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/kn/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ko/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ko/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ko/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/lt/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/lt/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/lt/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ml/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ml/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ml/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/mr/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/mr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/mr/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ms/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ms/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ms/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/my/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/my/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/my/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ne/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ne/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ne/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/nl/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/nl/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/nl/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/no/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/no/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/no/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/pa/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/pa/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pa/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/pcm/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/pcm/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pcm/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/pl/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/pl/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pl/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/pt-BR/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/pt-BR/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pt-BR/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/pt-PT/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/pt-PT/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pt-PT/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ro/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ro/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ro/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ru/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ru/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ru/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/sk/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/sk/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sk/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/sl/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/sl/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sl/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/sr/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/sr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sr/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/sv/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/sv/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sv/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/sw/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/sw/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sw/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ta/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ta/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ta/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/te/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/te/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/te/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/th/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/th/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/th/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/tl/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/tl/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/tl/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/tr/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/tr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/tr/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/uk/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/uk/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/uk/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/ur/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/ur/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ur/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/vi/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/vi/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/vi/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/zh-CN/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/zh-CN/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-CN/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/zh-HK/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/zh-HK/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-HK/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/zh-MO/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/zh-MO/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-MO/11-MCPServerHandsOnLabs/06-Tools/README.md)","translations/zh-TW/11-MCPServerHandsOnLabs/06-Tools/README.md":"# 06-Tools Documentation\n\nPath: `translations/zh-TW/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-TW/11-MCPServerHandsOnLabs/06-Tools/README.md)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\n## Project Overview\n\n**MCP for Beginners** is an open-source educational curriculum for learning the Model Context Protocol (MCP) - a standardized framework for interactions between AI models and client applications. This repository provides comprehensive learning materials with hands-on code examples across multiple programming languages.\n\n### Key Technologies\n\n- **Programming Languages**: C#, Java, JavaScript, TypeScript, Python, Rust\n- **Frameworks & SDKs**: \n  - MCP SDK (`@modelcontextprotocol/sdk`)\n  - Spring Boot (Java)\n  - FastMCP (Python)\n  - LangChain4j (Java)\n- **Databases**: PostgreSQL with pgvector extension\n- **Cloud Platforms**: Azure (Container Apps, OpenAI, Content Safety, Application Insights)\n- **Build Tools**: npm, Maven, pip, Cargo\n- **Documentation**: Markdown with automated multi-language translation (48+ languages)\n\n### Architecture\n\n- **11 Core Modules (00-11)**: Sequential learning path from fundamentals to advanced topics\n- **Hands-on Labs**: Practical exercises with complete solution code in multiple languages\n- **Sample Projects**: Working MCP server and client implementations\n- **Translation System**: Automated GitHub Actions workflow for multi-language support\n- **Image Assets**: Centralized images directory with translated versions\n\n## Setup Commands\n\nThis is a documentation-focused repository. Most setup occurs within individual sample projects and labs.\n\n### Repository Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/microsoft/mcp-for-beginners.git\ncd mcp-for-beginners\n```\n\n### Working with Sample Projects\n\nSample projects are located in:\n- `03-GettingStarted/samples/` - Language-specific examples\n- `03-GettingStarted/01-first-server/solution/` - First server implementations\n- `03-GettingStarted/02-client/solution/` - Client implementations\n- `11-MCPServerHandsOnLabs/` - Comprehensive database integration labs\n\nEach sample project contains its own setup instructions:\n\n#### TypeScript/JavaScript Projects\n```bash\ncd <project-directory>\nnpm install\nnpm start\n```\n\n#### Python Projects\n```bash\ncd <project-directory>\npip install -r requirements.txt\n# or\npip install -e .\npython main.py\n```\n\n#### Java Projects\n```bash\ncd <project-directory>\nmvn clean install\nmvn spring-boot:run\n```\n\n## Development Workflow\n\n### MCP 7-28 Readiness\n\n#### Repo readiness checklist\n\n- [x] **New contributor clarity**: This file defines repository purpose,\n  structure, contribution rules, and sample setup paths.\n- [x] **Build/test/lint commands with exact flags**:\n  - Repository docs lint:\n    `npx --yes markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#translations\" \"#translated_images\"`\n  - Repository docs link pattern audit:\n    `find . -name \"*.md\" -not -path \"*/node_modules/*\" -not -path \"./translations/*\" -not -path \"./translated_images/*\" -print0 | xargs -0 grep -En \"\\[.*\\]\\(.*\\)\"`\n  - TypeScript sample validation:\n    `cd 03-GettingStarted/samples/typescript && npm ci && npm test && npm run build`\n  - Python sample validation:\n    `cd 10-StreamliningAIWorkflowsBuildingAnMCPServerWithAIToolkit/lab3/code/weather_mcp && python -m pip install -e . && pytest -q`\n  - Java sample validation:\n    `cd 03-GettingStarted/samples/java/calculator && mvn -B -ntp test verify`\n- [x] **One realistic workflow that can become an MCP tool**:\n  `validate_curriculum_change`\n- [x] **Inputs/outputs are explicit** (see specification below).\n- [x] **Permissions and failure modes are documented** (see specification below).\n- [x] **CI testability is explicit** (deterministic commands, explicit\n  exit codes, and machine-readable outputs).\n\n#### Candidate MCP tool workflow: `validate_curriculum_change`\n\n##### Goal\n\nValidate curriculum documentation changes and representative sample code\nhealth before merge.\n\n##### Inputs\n\n- `changed_paths: string[]` (required) - relative paths changed in PR.\n- `run_docs_lint: boolean` (default `true`)\n- `run_links_audit: boolean` (default `true`)\n- `run_samples: { typescript?: boolean, python?: boolean, java?: boolean }`\n  (default all `false`)\n\n##### Outputs\n\n- `status: \"ok\" | \"failed\"`\n- `checks: Array<{ name: string, command: string, exit_code: number,\n  summary: string }>`\n- `artifacts: Array<{ type: \"log\" | \"report\", path: string }>`\n- `failed_checks: string[]`\n\n##### Permissions\n\n- Read workspace files and write tool-generated artifacts (e.g., lint\n  reports, test logs) only; no writes to `translations/` or\n  `translated_images/`.\n- Execute local shell commands.\n- Optional network access only for package restore (`npm ci`,\n  `python -m pip install`, `mvn` dependency resolution).\n- No permission to push, merge, or modify `translations/` or\n  `translated_images/`.\n\n##### Failure modes\n\n- `E_NO_INPUT_PATHS`: `changed_paths` empty.\n- `E_INVALID_PATH`: input path escapes repository root.\n- `E_LINT_FAILED`: markdown lint exits non-zero.\n- `E_LINK_AUDIT_FAILED`: link audit command exits non-zero.\n- `E_SAMPLE_TEST_FAILED`: sample test/build exits non-zero.\n- `E_TIMEOUT`: command exceeded configured timeout.\n\n##### Recommended CI contract\n\nTo automate validation, configure a CI job that:\n\n- Triggers on pull requests touching `*.md`, sample code, or this file.\n- Runs the exact commands listed above.\n- Persists logs as artifacts.\n- Fails the job on any non-zero exit code.\n\n#### If you ship an MCP server from this repo\n\n- [ ] Read the final MCP `2026-07-28` changelog:\n  <https://modelcontextprotocol.io/specification/2026-07-28/changelog>\n- [ ] Verify that the selected SDK release supports MCP `2026-07-28`:\n  <https://modelcontextprotocol.io/docs/sdk>\n- [ ] Remove session and handshake assumptions; treat each request as\n  self-contained:\n  <https://modelcontextprotocol.io/specification/2026-07-28/basic/lifecycle>\n- [ ] Send `Mcp-Method` and `Mcp-Name` headers for raw HTTP requests:\n  <https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http>\n- [ ] Audit hardcoded error codes (`missing resource` moved from `-32002` to `-32602`).\n- [ ] Migrate deprecated Roots, Sampling, Logging, and Dynamic Client\n  Registration:\n  <https://modelcontextprotocol.io/specification/2026-07-28/deprecated>\n- [ ] Migrate off the experimental `2025-11-25` Tasks API:\n  <https://modelcontextprotocol.io/extensions/tasks>\n- [ ] Review authorization for OAuth and OpenID Connect hardening:\n  <https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization>\n\n### Documentation Structure\n\n- **Modules 00-11**: Core curriculum content in sequential order\n- **translations/**: Language-specific versions (auto-generated, do not edit directly)\n- **translated_images/**: Localized image versions (auto-generated)\n- **images/**: Source images and diagrams\n\n### Making Documentation Changes\n\n1. Edit only the English markdown files in the root module directories (00-11)\n2. Update images in the `images/` directory if needed\n3. The co-op-translator GitHub Action will automatically generate translations\n4. Translations are regenerated on push to main branch\n\n### Working with Translations\n\n- **Automated Translation**: GitHub Actions workflow handles all translations\n- **Do NOT manually edit** files in `translations/` directory\n- Translation metadata is embedded in each translated file\n- Supported languages: 48+ languages including Arabic, Chinese, French, German, Hindi, Japanese, Korean, Portuguese, Russian, Spanish, and many more\n\n## Testing Instructions\n\n### Documentation Validation\n\nSince this is primarily a documentation repository, testing focuses on:\n\n1. **Link Pattern Audit**: List Markdown links for review\n\n   ```bash\n   # List Markdown links (pattern audit)\n   find . -name \"*.md\" -not -path \"*/node_modules/*\" -not -path \"./translations/*\" -not -path \"./translated_images/*\" -print0 | xargs -0 grep -En \"\\[.*\\]\\(.*\\)\"\n   ```\n\n2. **Code Sample Validation**: Test that code examples compile/run\n\n   ```bash\n   # Navigate to specific sample and run its tests\n   cd 03-GettingStarted/samples/typescript\n   npm install && npm test\n   ```\n\n3. **Markdown Linting**: Check formatting consistency\n\n   ```bash\n   # Use markdownlint if needed\n   npx --yes markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#translations\" \"#translated_images\"\n   ```\n\n### Sample Project Testing\n\nEach language-specific sample includes its own testing approach:\n\n#### TypeScript/JavaScript\n```bash\nnpm test\nnpm run build\n```\n\n#### Python\n```bash\npytest\npython -m pytest tests/\n```\n\n#### Java\n```bash\nmvn test\nmvn verify\n```\n\n## Code Style Guidelines\n\n### Documentation Style\n\n- Use clear, beginner-friendly language\n- Include code examples in multiple languages where applicable\n- Follow markdown best practices:\n  - Use ATX-style headers (`#` syntax)\n  - Use fenced code blocks with language identifiers\n  - Include descriptive alt text for images\n  - Keep line lengths reasonable (no hard limit, but be sensible)\n\n### Code Sample Style\n\n#### TypeScript/JavaScript\n- Use ES modules (`import`/`export`)\n- Follow TypeScript strict mode conventions\n- Include type annotations\n- Target ES2022\n\n#### Python\n- Follow PEP 8 style guidelines\n- Use type hints where appropriate\n- Include docstrings for functions and classes\n- Use modern Python features (3.8+)\n\n#### Java\n- Follow Spring Boot conventions\n- Use Java 21 features\n- Follow standard Maven project structure\n- Include Javadoc comments\n\n### File Organization\n\n```\n<module-number>-<ModuleName>/\n├── README.md              # Main module content\n├── samples/               # Code examples (if applicable)\n│   ├── typescript/\n│   ├── python/\n│   ├── java/\n│   └── ...\n└── solution/              # Complete working solutions\n    └── <language>/\n```\n\n## Build and Deployment\n\n### Documentation Deployment\n\nThe repository uses GitHub Pages or similar for documentation hosting (if applicable). Changes to the main branch trigger:\n\n1. Translation workflow (`.github/workflows/co-op-translator.yml`)\n2. Automated translation of all English markdown files\n3. Image localization as needed\n\n### No Build Process Required\n\nThis repository primarily contains markdown documentation. No compilation or build step is needed for the core curriculum content.\n\n### Sample Project Deployment\n\nIndividual sample projects may have deployment instructions:\n- See `03-GettingStarted/09-deployment/` for MCP server deployment guidance\n- Azure Container Apps deployment examples in `11-MCPServerHandsOnLabs/`\n\n## Contributing Guidelines\n\n### Pull Request Process\n\n1. **Fork and Clone**: Fork the repository and clone your fork locally\n2. **Create a Branch**: Use descriptive branch names (e.g., `fix/typo-module-3`, `add/python-example`)\n3. **Make Changes**: Edit English markdown files only (not translations)\n4. **Test Locally**: Verify markdown renders correctly\n5. **Submit PR**: Use clear PR titles and descriptions\n6. **CLA**: Sign the Microsoft Contributor License Agreement when prompted\n\n### PR Title Format\n\nUse clear, descriptive titles:\n- `[Module XX] Brief description` for module-specific changes\n- `[Samples] Description` for sample code changes\n- `[Docs] Description` for general documentation updates\n\n### What to Contribute\n\n- Bug fixes in documentation or code samples\n- New code examples in additional languages\n- Clarifications and improvements to existing content\n- New case studies or practical examples\n- Issue reports for unclear or incorrect content\n\n### What NOT to Do\n\n- Do not directly edit files in `translations/` directory\n- Do not edit `translated_images/` directory\n- Do not add large binary files without discussion\n- Do not change translation workflow files without coordination\n\n## Additional Notes\n\n### Repository Maintenance\n\n- **Changelog**: All significant changes are documented in `changelog.md`\n- **Study Guide**: Use `study_guide.md` for curriculum navigation overview\n- **Issue Templates**: Use GitHub issue templates for bug reports and feature requests\n- **Code of Conduct**: All contributors must follow the Microsoft Open Source Code of Conduct\n\n### Learning Path\n\nFollow modules in sequential order (00-11) for optimal learning:\n1. **00-02**: Fundamentals (Introduction, Core Concepts, Security)\n2. **03**: Getting Started with hands-on implementation\n3. **04-05**: Practical implementation and advanced topics\n4. **06-10**: Community, best practices, and real-world applications\n5. **11**: Comprehensive database integration labs (13 sequential labs)\n\n### Support Resources\n\n- **Documentation**: https://modelcontextprotocol.io/\n- **Specification**: https://modelcontextprotocol.io/specification/2026-07-28/\n- **Community**: https://github.com/orgs/modelcontextprotocol/discussions\n- **Discord**: Microsoft Foundry Discord server\n- **Related Courses**: See README.md for other Microsoft learning paths\n\n### Common Troubleshooting\n\n**Q: My PR is failing the translation check**\nA: Ensure you only edited English markdown files in the root module directories, not translated versions.\n\n**Q: How do I add a new language?**\nA: Language support is managed through the co-op-translator workflow. Open an issue to discuss adding new languages.\n\n**Q: Code samples aren't working**\nA: Ensure you've followed the setup instructions in the specific sample's README. Check that you have the correct versions of dependencies installed.\n\n**Q: Images aren't displaying**\nA: Verify image paths are relative and use forward slashes. Images should be in the `images/` directory or `translated_images/` for localized versions.\n\n### Performance Considerations\n\n- Translation workflow may take several minutes to complete\n- Large images should be optimized before committing\n- Keep individual markdown files focused and reasonably sized\n- Use relative links for better portability\n\n### Project Governance\n\nThis project follows Microsoft open source practices:\n- MIT License for code and documentation\n- Microsoft Open Source Code of Conduct\n- CLA required for contributions\n- Security issues: Follow SECURITY.md guidelines\n- Support: See SUPPORT.md for help resources\n","isInternal":false,"tokens":3150,"sizeBytes":14028},{"name":"README.md","path":"11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# Tool Development\n\n## 🎯 What This Lab Covers\n\nThis lab dives deep into creating sophisticated MCP tools that provide AI assistants with powerful database query capabilities, schema introspection, and analytics functions. You'll learn to build tools that are both powerful and safe, with comprehensive error handling and performance optimization.\n\n## Overview\n\nMCP tools are the interface between AI assistants and your data systems. Well-designed tools provide structured, validated access to complex operations while maintaining security and performance. This lab covers the complete lifecycle of tool development from design to deployment.\n\nOur retail MCP server implements a comprehensive suite of tools that enable natural language querying of sales data, product catalogs, and business analytics while maintaining strict security boundaries and optimal performance.\n\n## Learning Objectives\n\nBy the end of this lab, you will be able to:\n\n- **Design** advanced MCP tools with complex parameter validation\n- **Implement** secure database query tools with SQL injection protection\n- **Create** schema introspection capabilities for dynamic queries\n- **Build** custom analytics tools for business intelligence\n- **Apply** comprehensive error handling and graceful degradation\n- **Optimize** tool performance for production workloads\n\n## 🛠️ Core Tool Architecture\n\n### Tool Design Principles\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Query Validation and Security\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Database Query Tools\n\n### Sales Analysis Tool\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Schema Introspection Tool\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytics and Utility Tools\n\n### Business Intelligence Tool\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Key Takeaways\n\nAfter completing this lab, you should have:\n\n✅ **Advanced Tool Architecture**: Implemented sophisticated MCP tools with comprehensive error handling  \n✅ **Query Validation**: Built secure SQL validation to prevent injection attacks  \n✅ **Database Tools**: Created powerful sales analysis and schema introspection capabilities  \n✅ **Business Intelligence**: Developed analytics tools for comprehensive business insights","isInternal":false,"tokens":9231,"sizeBytes":50022},{"name":"README.md","path":"translations/ar/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ar/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# تطوير الأدوات\n\n## 🎯 ما يغطيه هذا المختبر\n\nيستعرض هذا المختبر كيفية إنشاء أدوات MCP متقدمة تمنح المساعدين الذكيين قدرات قوية لاستعلام قواعد البيانات، استكشاف المخططات، ووظائف التحليل. ستتعلم كيفية بناء أدوات قوية وآمنة مع معالجة شاملة للأخطاء وتحسين الأداء.\n\n## نظرة عامة\n\nأدوات MCP هي الواجهة بين المساعدين الذكيين وأنظمة البيانات الخاصة بك. الأدوات المصممة بشكل جيد توفر وصولاً منظمًا ومُتحققًا إلى العمليات المعقدة مع الحفاظ على الأمان والأداء. يغطي هذا المختبر دورة حياة تطوير الأدوات بالكامل من التصميم إلى النشر.\n\nيُنفذ خادم MCP الخاص بالتجزئة مجموعة شاملة من الأدوات التي تُمكّن الاستعلام الطبيعي عن بيانات المبيعات، كتالوجات المنتجات، وتحليلات الأعمال مع الحفاظ على حدود أمان صارمة وأداء مثالي.\n\n## أهداف التعلم\n\nبنهاية هذا المختبر، ستكون قادرًا على:\n\n- **تصميم** أدوات MCP متقدمة مع تحقق معقد من المعلمات  \n- **تنفيذ** أدوات استعلام قواعد بيانات آمنة مع حماية من حقن SQL  \n- **إنشاء** قدرات استكشاف المخططات للاستعلامات الديناميكية  \n- **بناء** أدوات تحليل مخصصة لذكاء الأعمال  \n- **تطبيق** معالجة شاملة للأخطاء وتدهور سلس  \n- **تحسين** أداء الأدوات لتناسب أعباء العمل الإنتاجية  \n\n## 🛠️ بنية الأدوات الأساسية\n\n### مبادئ تصميم الأدوات\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n  \n### التحقق من الاستعلام والأمان\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n  \n\n## 🗃️ أدوات استعلام قواعد البيانات\n\n### أداة تحليل المبيعات\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n  \n### أداة استكشاف المخططات\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 📊 أدوات التحليل والمرافق\n\n### أداة ذكاء الأعمال\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 🎯 النقاط الرئيسية\n\nبعد إكمال هذا المختبر، يجب أن تكون قد حققت:\n\n✅ **بنية أدوات متقدمة**: تنفيذ أدوات MCP متطورة مع معالجة شاملة للأخطاء  \n✅ **التحقق من الاستعلام**: بناء تحقق SQL آمن لمنع هجمات الحقن  \n✅ **أدوات قواعد البيانات**: إنشاء قدرات قوية لتحليل المبيعات واستكشاف المخططات  \n✅ **ذكاء الأعمال**: تطوير أدوات تحليل للحصول على رؤى شاملة للأعمال  \n✅ **تحسين الأداء**: تطبيق التخزين المؤقت، إدارة الاتصال، وتحسين الاستعلام  \n✅ **تكامل الأمان**: تنفيذ التحكم في الوصول بناءً على الأدوار وتسجيل التدقيق  \n\n## 🚀 ما التالي\n\nتابع مع **[مختبر 07: تكامل البحث الدلالي](../07-Semantic-Search/README.md)** لـ:\n\n- دمج قدرات البحث المتجهي مع أدوات MCP  \n- بناء وظيفة البحث الدلالي عن المن","isInternal":false,"tokens":10169,"sizeBytes":51485},{"name":"README.md","path":"translations/bg/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/bg/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# Разработка на инструменти\n\n## 🎯 Какво обхваща този лабораторен урок\n\nТози лабораторен урок се фокусира върху създаването на усъвършенствани MCP инструменти, които предоставят на AI асистентите мощни възможности за заявки към бази данни, инспекция на схеми и аналитични функции. Ще научите как да изграждате инструменти, които са едновременно мощни и безопасни, с изчерпателно управление на грешки и оптимизация на производителността.\n\n## Обзор\n\nMCP инструментите са интерфейсът между AI асистентите и вашите системи за данни. Добре проектираните инструменти осигуряват структурирани и валидирани операции, като същевременно поддържат сигурност и производителност. Този лабораторен урок обхваща целия жизнен цикъл на разработката на инструменти – от проектиране до внедряване.\n\nНашият MCP сървър за търговия на дребно включва цялостен набор от инструменти, които позволяват заявки на естествен език за данни за продажби, продуктови каталози и бизнес анализи, като същевременно поддържат строги граници на сигурност и оптимална производителност.\n\n## Цели на обучението\n\nДо края на този лабораторен урок ще можете:\n\n- **Да проектирате** усъвършенствани MCP инструменти с комплексна валидация на параметри\n- **Да внедрите** сигурни инструменти за заявки към бази данни с защита срещу SQL инжекции\n- **Да създадете** възможности за инспекция на схеми за динамични заявки\n- **Да изградите** персонализирани аналитични инструменти за бизнес интелигентност\n- **Да приложите** изчерпателно управление на грешки и плавна деградация\n- **Да оптимизирате** производителността на инструментите за работни натоварвания в продукция\n\n## 🛠️ Основна архитектура на инструментите\n\n### Принципи на проектиране на инструменти\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Валидация на заявки и сигурност\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Инструменти за заявки към бази данни\n\n### Инструмент за анализ на продажби\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Инструмент за инспекция на схеми\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Аналитични и помощни инструменти\n\n### Инструмент за бизнес интелигентност\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Основни изводи\n\nСлед завършване на този лабораторен урок, трябва да","isInternal":false,"tokens":9837,"sizeBytes":51630},{"name":"README.md","path":"translations/bn/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/bn/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# টুল ডেভেলপমেন্ট\n\n## 🎯 এই ল্যাবে কী শেখানো হবে\n\nএই ল্যাবে আপনি শিখবেন কীভাবে উন্নত MCP টুল তৈরি করতে হয় যা AI অ্যাসিস্ট্যান্টকে শক্তিশালী ডাটাবেস কোয়েরি করার ক্ষমতা, স্কিমা ইনট্রোস্পেকশন এবং অ্যানালিটিক্স ফাংশন প্রদান করে। আপনি এমন টুল তৈরি করতে শিখবেন যা শক্তিশালী এবং নিরাপদ, যেখানে ব্যাপক ত্রুটি পরিচালনা এবং পারফরম্যান্স অপ্টিমাইজেশন অন্তর্ভুক্ত থাকবে।\n\n## ওভারভিউ\n\nMCP টুল হলো AI অ্যাসিস্ট্যান্ট এবং আপনার ডেটা সিস্টেমের মধ্যে ইন্টারফেস। ভালোভাবে ডিজাইন করা টুল জটিল অপারেশনের জন্য গঠনমূলক এবং যাচাই করা অ্যাক্সেস প্রদান করে, একইসঙ্গে নিরাপত্তা এবং পারফরম্যান্স বজায় রাখে। এই ল্যাবে টুল ডেভেলপমেন্টের সম্পূর্ণ জীবনচক্র, ডিজাইন থেকে ডিপ্লয়মেন্ট পর্যন্ত, কভার করা হয়েছে।\n\nআমাদের রিটেইল MCP সার্ভার একটি বিস্তৃত টুলের সেট বাস্তবায়ন করে যা সেলস ডেটা, প্রোডাক্ট ক্যাটালগ এবং বিজনেস অ্যানালিটিক্সের প্রাকৃতিক ভাষায় কোয়েরি করার ক্ষমতা প্রদান করে, কঠোর নিরাপত্তা সীমা এবং সর্বোত্তম পারফরম্যান্স বজায় রেখে।\n\n## শেখার লক্ষ্য\n\nএই ল্যাব শেষে আপনি সক্ষম হবেন:\n\n- **ডিজাইন**: জটিল প্যারামিটার যাচাই সহ উন্নত MCP টুল ডিজাইন করা  \n- **ইমপ্লিমেন্ট**: SQL ইনজেকশন প্রতিরোধ সহ নিরাপদ ডাটাবেস কোয়েরি টুল তৈরি করা  \n- **ক্রিয়েট**: ডায়নামিক কোয়েরির জন্য স্কিমা ইনট্রোস্পেকশন ক্ষমতা তৈরি করা  \n- **বিল্ড**: বিজনেস ইন্টেলিজেন্সের জন্য কাস্টম অ্যানালিটিক্স টুল তৈরি করা  \n- **অ্যাপ্লাই**: ব্যাপক ত্রুটি পরিচালনা এবং গ্রেসফুল ডিগ্রেডেশন প্রয়োগ করা  \n- **অপ্টিমাইজ**: প্রোডাকশন ওয়ার্কলোডের জন্য টুল পারফরম্যান্স অপ্টিমাইজ করা  \n\n## 🛠️ মূল টুল আর্কিটেকচার\n\n### টুল ডিজাইন নীতিমালা\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n  \n### কোয়েরি যাচাই এবং নিরাপত্তা\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n  \n\n## 🗃️ ডাটাবেস কোয়েরি টুল\n\n### সেলস অ্যানালাইসিস টুল\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n  \n### স্কিমা ইনট্রোস্পেকশন টুল\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 📊 অ্যানালিটিক্স এবং ইউটিলিটি টুল\n\n### বিজনেস ইন্টেলিজেন্স টুল\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 🎯 মূল বিষয়গুলো\n\nএই ল্যাব সম্পন্ন করার পর আপনি অর্জন করবেন:\n\n✅ **উন্নত টুল আর্কিটেকচার**: ব্যাপক ত্রুটি পরিচালনা সহ উন্নত MCP টুল বাস্তবায়ন  \n✅ **কোয়েরি যাচাই**: SQL ইনজেকশন আক্রমণ প্রতিরোধের জন্য নিরাপদ SQL যাচাই তৈরি  \n✅ **ডাটাবেস টুল**: শক্তিশালী সেলস অ্যানালাইসিস এবং স্কিমা ইনট্রোস্পেকশন ক্ষমতা তৈরি  \n✅ **বিজনেস ইন্টেলিজেন্স**: ব্যাপক","isInternal":false,"tokens":11142,"sizeBytes":53102},{"name":"README.md","path":"translations/cs/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/cs/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# Vývoj nástrojů\n\n## 🎯 Co tento lab pokrývá\n\nTento lab se podrobně zabývá tvorbou sofistikovaných MCP nástrojů, které poskytují AI asistentům výkonné schopnosti dotazování do databází, introspekce schémat a analytické funkce. Naučíte se vytvářet nástroje, které jsou nejen výkonné, ale také bezpečné, s důkladným zpracováním chyb a optimalizací výkonu.\n\n## Přehled\n\nMCP nástroje představují rozhraní mezi AI asistenty a vašimi datovými systémy. Dobře navržené nástroje poskytují strukturovaný a validovaný přístup ke složitým operacím při zachování bezpečnosti a výkonu. Tento lab pokrývá kompletní životní cyklus vývoje nástrojů od návrhu až po nasazení.\n\nNáš maloobchodní MCP server implementuje komplexní sadu nástrojů, které umožňují přirozené jazykové dotazování na prodejní data, katalogy produktů a obchodní analýzy, přičemž zachovává přísné bezpečnostní hranice a optimální výkon.\n\n## Cíle učení\n\nNa konci tohoto labu budete schopni:\n\n- **Navrhnout** pokročilé MCP nástroje s komplexní validací parametrů\n- **Implementovat** bezpečné nástroje pro dotazování do databází s ochranou proti SQL injekcím\n- **Vytvořit** schopnosti introspekce schémat pro dynamické dotazy\n- **Postavit** vlastní analytické nástroje pro obchodní inteligenci\n- **Aplikovat** důkladné zpracování chyb a postupné zhoršování funkcí\n- **Optimalizovat** výkon nástrojů pro produkční zátěže\n\n## 🛠️ Základní architektura nástrojů\n\n### Principy návrhu nástrojů\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Validace dotazů a bezpečnost\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Nástroje pro dotazování do databází\n\n### Nástroj pro analýzu prodeje\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Nástroj pro introspekci schémat\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytické a užitkové nástroje\n\n### Nástroj pro obchodní inteligenci\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Klíčové poznatky\n\nPo dokončení tohoto labu byste měli mít:\n\n✅ **Pokročilou architekturu nástrojů**: Implementované sofistikované MCP nástroje s důkladným zpracováním chyb  \n✅ **Validaci dotazů**: Vytvořenou bezpečnou SQL validaci pro prevenci útoků injekcí  \n✅ **Databázové nástroje**: Vyvinuté výkonné schopnosti analýzy prodeje a introspekce schémat","isInternal":false,"tokens":9710,"sizeBytes":50223},{"name":"README.md","path":"translations/da/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/da/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# Udvikling af Værktøjer\n\n## 🎯 Hvad Dækker Dette Lab\n\nDette lab går i dybden med at skabe avancerede MCP-værktøjer, der giver AI-assistenter kraftfulde muligheder for databaseforespørgsler, skemainspektion og analytiske funktioner. Du vil lære at bygge værktøjer, der både er effektive og sikre, med omfattende fejlhåndtering og optimering af ydeevne.\n\n## Oversigt\n\nMCP-værktøjer er grænsefladen mellem AI-assistenter og dine datasystemer. Veludformede værktøjer giver struktureret og valideret adgang til komplekse operationer, samtidig med at sikkerhed og ydeevne opretholdes. Dette lab dækker hele livscyklussen for værktøjsudvikling fra design til implementering.\n\nVores MCP-server til detailhandel implementerer en omfattende suite af værktøjer, der muliggør naturlig sprogforespørgsel af salgsdata, produktkataloger og forretningsanalyse, samtidig med at strenge sikkerhedsgrænser og optimal ydeevne opretholdes.\n\n## Læringsmål\n\nVed afslutningen af dette lab vil du kunne:\n\n- **Designe** avancerede MCP-værktøjer med kompleks parametervalidering\n- **Implementere** sikre databaseforespørgselsværktøjer med beskyttelse mod SQL-injektion\n- **Skabe** skemainspektionsfunktioner til dynamiske forespørgsler\n- **Bygge** skræddersyede analytiske værktøjer til forretningsintelligens\n- **Anvende** omfattende fejlhåndtering og elegant nedgradering\n- **Optimere** værktøjets ydeevne til produktionsarbejdsbelastninger\n\n## 🛠️ Grundlæggende Værktøjsarkitektur\n\n### Principper for Værktøjsdesign\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Validering af Forespørgsler og Sikkerhed\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Databaseforespørgselsværktøjer\n\n### Værktøj til Salgsanalyse\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Skemainspektionsværktøj\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytiske og Hjælpeværktøjer\n\n### Værktøj til Forretningsintelligens\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Vigtige Læringspunkter\n\nEfter at have gennemført dette lab, bør du have:\n\n✅ **Avanceret Værktøjsarkitektur**: Implementeret sofistikerede MCP-værktøjer med omfattende fejlhåndtering  \n✅ **Validering af Forespørgsler**: Bygget sikker SQL-validering for at forhindre injektionsangreb  \n✅ **Databaseværk","isInternal":false,"tokens":9589,"sizeBytes":50087},{"name":"README.md","path":"translations/de/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/de/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# Tool-Entwicklung\n\n## 🎯 Was dieses Lab abdeckt\n\nDieses Lab beschäftigt sich intensiv mit der Entwicklung fortschrittlicher MCP-Tools, die KI-Assistenten leistungsstarke Datenbankabfragefunktionen, Schema-Inspektion und Analysefunktionen bieten. Sie lernen, Tools zu erstellen, die sowohl leistungsstark als auch sicher sind, mit umfassender Fehlerbehandlung und Leistungsoptimierung.\n\n## Überblick\n\nMCP-Tools sind die Schnittstelle zwischen KI-Assistenten und Ihren Datensystemen. Gut gestaltete Tools bieten strukturierten, validierten Zugriff auf komplexe Operationen und gewährleisten dabei Sicherheit und Leistung. Dieses Lab behandelt den gesamten Lebenszyklus der Tool-Entwicklung, von der Konzeption bis zur Bereitstellung.\n\nUnser MCP-Server für den Einzelhandel implementiert eine umfassende Suite von Tools, die natürliche Sprachabfragen von Verkaufsdaten, Produktkatalogen und Geschäftsanalyse ermöglichen, während strenge Sicherheitsgrenzen und optimale Leistung eingehalten werden.\n\n## Lernziele\n\nAm Ende dieses Labs werden Sie in der Lage sein:\n\n- **Entwerfen** fortschrittlicher MCP-Tools mit komplexer Parametervalidierung  \n- **Implementieren** sicherer Datenbankabfragetools mit Schutz vor SQL-Injection  \n- **Erstellen** von Schema-Inspektionsfunktionen für dynamische Abfragen  \n- **Entwickeln** benutzerdefinierter Analysetools für Business Intelligence  \n- **Anwenden** umfassender Fehlerbehandlung und sanfter Degradierung  \n- **Optimieren** der Tool-Leistung für Produktionsumgebungen  \n\n## 🛠️ Kernarchitektur der Tools\n\n### Prinzipien des Tool-Designs\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n  \n### Abfragevalidierung und Sicherheit\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n  \n\n## 🗃️ Datenbankabfragetools\n\n### Verkaufsanalyse-Tool\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n  \n### Schema-Inspektions-Tool\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 📊 Analyse- und Utility-Tools\n\n### Business-Intelligence-Tool\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 🎯 Wichtige Erkenntnisse\n\nNach Abschluss dieses Labs sollten Sie:\n\n✅ **Fortschrittliche Tool-Architektur**: Komplexe MCP-Tools mit umfassender Fehlerbehandlung implementiert haben  \n✅ **Abfragevalidierung**: Sichere SQL-Validierung zur","isInternal":false,"tokens":9435,"sizeBytes":50029},{"name":"README.md","path":"translations/el/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/el/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# Ανάπτυξη Εργαλείων\n\n## 🎯 Τι Καλύπτει Αυτό το Εργαστήριο\n\nΑυτό το εργαστήριο εμβαθύνει στη δημιουργία εξελιγμένων εργαλείων MCP που παρέχουν στους βοηθούς AI ισχυρές δυνατότητες ερωτημάτων βάσεων δεδομένων, ανάλυσης σχημάτων και λειτουργιών ανάλυσης. Θα μάθετε να δημιουργείτε εργαλεία που είναι τόσο ισχυρά όσο και ασφαλή, με ολοκληρωμένο χειρισμό σφαλμάτων και βελτιστοποίηση απόδοσης.\n\n## Επισκόπηση\n\nΤα εργαλεία MCP είναι η διεπαφή μεταξύ των βοηθών AI και των συστημάτων δεδομένων σας. Καλά σχεδιασμένα εργαλεία παρέχουν δομημένη, επικυρωμένη πρόσβαση σε σύνθετες λειτουργίες, διατηρώντας παράλληλα την ασφάλεια και την απόδοση. Αυτό το εργαστήριο καλύπτει τον πλήρη κύκλο ζωής της ανάπτυξης εργαλείων, από τον σχεδιασμό έως την υλοποίηση.\n\nΟ MCP server μας για το λιανικό εμπόριο υλοποιεί μια ολοκληρωμένη σουίτα εργαλείων που επιτρέπουν ερωτήματα φυσικής γλώσσας για δεδομένα πωλήσεων, καταλόγους προϊόντων και επιχειρηματική ανάλυση, ενώ διατηρούν αυστηρά όρια ασφαλείας και βέλτιστη απόδοση.\n\n## Στόχοι Μάθησης\n\nΜέχρι το τέλος αυτού του εργαστηρίου, θα μπορείτε να:\n\n- **Σχεδιάσετε** εξελιγμένα εργαλεία MCP με σύνθετη επικύρωση παραμέτρων\n- **Υλοποιήσετε** ασφαλή εργαλεία ερωτημάτων βάσεων δεδομένων με προστασία από SQL injection\n- **Δημιουργήσετε** δυνατότητες ανάλυσης σχημάτων για δυναμικά ερωτήματα\n- **Κατασκευάσετε** προσαρμοσμένα εργαλεία ανάλυσης για επιχειρηματική ευφυΐα\n- **Εφαρμόσετε** ολοκληρωμένο χειρισμό σφαλμάτων και ομαλή υποβάθμιση λειτουργιών\n- **Βελτιστοποιήσετε** την απόδοση εργαλείων για παραγωγικά φορτία\n\n## 🛠️ Βασική Αρχιτεκτονική Εργαλείων\n\n### Αρχές Σχεδιασμού Εργαλείων\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Επικύρωση Ερωτημάτων και Ασφάλεια\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Εργαλεία Ερωτημάτων Βάσεων Δεδομένων\n\n### Εργαλείο Ανάλυσης Πωλήσεων\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Εργαλείο Ανάλυσης Σχημάτων\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Εργαλεία Ανάλυσης και Χρησιμότητας\n\n### Εργαλείο Επιχειρηματικής Ευφυΐας\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Βασικά Συμπεράσματα\n\nΜετά την ολοκλήρωση αυτού του εργαστηρίου, θα έχετε:\n\n✅ **Προηγμένη Αρχιτεκτονική Εργαλείων**: Υλοποιήσει εξελιγμένα εργαλεία MCP με ολοκληρωμένο χειρ","isInternal":false,"tokens":10644,"sizeBytes":51622},{"name":"README.md","path":"translations/en/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/en/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# Tool Development\n\n## 🎯 What This Lab Covers\n\nThis lab focuses on creating advanced MCP tools that equip AI assistants with robust capabilities for database querying, schema exploration, and analytics. You'll learn how to design tools that are both powerful and secure, incorporating thorough error handling and performance optimization.\n\n## Overview\n\nMCP tools act as the bridge between AI assistants and your data systems. Properly designed tools enable structured and validated access to complex operations while ensuring security and efficiency. This lab guides you through the entire process of tool development, from initial design to final deployment.\n\nOur retail MCP server includes a comprehensive set of tools that allow natural language querying of sales data, product catalogs, and business analytics, all while maintaining strict security protocols and high performance.\n\n## Learning Objectives\n\nBy the end of this lab, you will be able to:\n\n- **Design** advanced MCP tools with robust parameter validation\n- **Implement** secure database query tools that protect against SQL injection\n- **Create** schema exploration features for dynamic queries\n- **Develop** custom analytics tools for business intelligence\n- **Apply** thorough error handling and graceful fallback mechanisms\n- **Optimize** tool performance for production environments\n\n## 🛠️ Core Tool Architecture\n\n### Tool Design Principles\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Query Validation and Security\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Database Query Tools\n\n### Sales Analysis Tool\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Schema Introspection Tool\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytics and Utility Tools\n\n### Business Intelligence Tool\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Key Takeaways\n\nAfter completing this lab, you should have:\n\n✅ **Advanced Tool Architecture**: Designed and implemented sophisticated MCP tools with robust error handling  \n✅ **Query Validation**: Developed secure SQL validation mechanisms to prevent injection attacks  \n✅ **Database Tools**: Built powerful tools for sales analysis and schema exploration  \n✅ **Business Intelligence**: Created analytics tools for ","isInternal":false,"tokens":9240,"sizeBytes":50022},{"name":"README.md","path":"translations/es/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/es/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# Desarrollo de Herramientas\n\n## 🎯 Qué Cubre Este Laboratorio\n\nEste laboratorio profundiza en la creación de herramientas MCP avanzadas que proporcionan a los asistentes de IA capacidades poderosas para consultas de bases de datos, introspección de esquemas y funciones analíticas. Aprenderás a construir herramientas que sean tanto potentes como seguras, con manejo de errores integral y optimización del rendimiento.\n\n## Descripción General\n\nLas herramientas MCP son la interfaz entre los asistentes de IA y tus sistemas de datos. Las herramientas bien diseñadas ofrecen acceso estructurado y validado a operaciones complejas mientras mantienen la seguridad y el rendimiento. Este laboratorio cubre el ciclo completo de desarrollo de herramientas, desde el diseño hasta la implementación.\n\nNuestro servidor MCP para retail implementa un conjunto completo de herramientas que permiten consultas en lenguaje natural sobre datos de ventas, catálogos de productos y análisis de negocios, manteniendo estrictos límites de seguridad y un rendimiento óptimo.\n\n## Objetivos de Aprendizaje\n\nAl finalizar este laboratorio, podrás:\n\n- **Diseñar** herramientas MCP avanzadas con validación compleja de parámetros  \n- **Implementar** herramientas seguras de consulta de bases de datos con protección contra inyección SQL  \n- **Crear** capacidades de introspección de esquemas para consultas dinámicas  \n- **Construir** herramientas analíticas personalizadas para inteligencia empresarial  \n- **Aplicar** manejo de errores integral y degradación controlada  \n- **Optimizar** el rendimiento de las herramientas para cargas de trabajo en producción  \n\n## 🛠️ Arquitectura Central de Herramientas\n\n### Principios de Diseño de Herramientas\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Validación de Consultas y Seguridad\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Herramientas de Consulta de Bases de Datos\n\n### Herramienta de Análisis de Ventas\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Herramienta de Introspección de Esquemas\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Herramientas Analíticas y de Utilidad\n\n### Herramienta de Inteligencia Empresarial\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Puntos Clave\n\nDespués ","isInternal":false,"tokens":9372,"sizeBytes":50045},{"name":"README.md","path":"translations/et/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/et/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/et/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/et/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/fa/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/fa/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/fa/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/fa/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/fi/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/fi/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/fi/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/fi/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/fr/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/fr/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/fr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/fr/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/he/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/he/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/he/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/he/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/hi/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/hi/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/hi/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/hi/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/hr/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/hr/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/hr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/hr/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/hu/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/hu/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/hu/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/hu/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/id/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/id/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/id/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/id/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/it/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/it/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/it/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/it/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/ja/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ja/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/ja/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ja/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/km/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/km/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/km/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/km/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/kn/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/kn/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/kn/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/kn/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/ko/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ko/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/ko/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ko/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/lt/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/lt/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/lt/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/lt/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/ml/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ml/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/ml/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ml/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/mr/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/mr/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/mr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/mr/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/ms/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ms/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/ms/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ms/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/my/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/my/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/my/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/my/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/ne/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ne/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/ne/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ne/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/nl/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/nl/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/nl/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/nl/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/no/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/no/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/no/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/no/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/pa/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pa/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/pa/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pa/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/pcm/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pcm/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/pcm/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pcm/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":251},{"name":"README.md","path":"translations/pl/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pl/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/pl/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pl/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/pt-BR/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pt-BR/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/pt-BR/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pt-BR/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":255},{"name":"README.md","path":"translations/pt-PT/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pt-PT/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/pt-PT/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/pt-PT/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":255},{"name":"README.md","path":"translations/ro/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ro/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/ro/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ro/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/ru/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ru/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/ru/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ru/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/sk/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sk/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/sk/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sk/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/sl/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sl/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/sl/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sl/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/sr/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sr/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/sr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sr/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/sv/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sv/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/sv/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sv/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/sw/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sw/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/sw/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/sw/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/ta/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ta/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/ta/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ta/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/te/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/te/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/te/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/te/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/th/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/th/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/th/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/th/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/tl/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/tl/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/tl/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/tl/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/tr/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/tr/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/tr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/tr/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/uk/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/uk/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/uk/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/uk/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/ur/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ur/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/ur/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ur/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/vi/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/vi/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/vi/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/vi/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":249},{"name":"README.md","path":"translations/zh-CN/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-CN/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/zh-CN/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-CN/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":255},{"name":"README.md","path":"translations/zh-HK/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-HK/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/zh-HK/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-HK/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":255},{"name":"README.md","path":"translations/zh-MO/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-MO/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/zh-MO/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-MO/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":255},{"name":"README.md","path":"translations/zh-TW/11-MCPServerHandsOnLabs/06-Tools/README.md","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-TW/11-MCPServerHandsOnLabs/06-Tools/README.md","title":"06-Tools Documentation","category":"plugin-manifest","format":"markdown","content":"# 06-Tools Documentation\n\nPath: `translations/zh-TW/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/zh-TW/11-MCPServerHandsOnLabs/06-Tools/README.md)","tokens":300,"sizeBytes":255},{"name":"mcp.json","path":"09-CaseStudy/docs-mcp/solution/scenario3/mcp.json","rawUrl":"https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/09-CaseStudy/docs-mcp/solution/scenario3/mcp.json","title":"Model Context Protocol (MCP) Configuration","category":"mcp-config","format":"json","content":"{\n    \"servers\": {\n        \"LearnDocsMCP\": {\n            \"url\": \"https://learn.microsoft.com/api/mcp\"\n        }\n    }\n}","frontmatter":{"servers":{"LearnDocsMCP":{"url":"https://learn.microsoft.com/api/mcp"}}},"isInternal":false,"tokens":33,"sizeBytes":119}],"systemPromptSnippet":"<yakaai_skills repo=\"microsoft/mcp-for-beginners\">\n<!-- File: AGENTS.md (Tokens: ~3150 | Category: root-instruction) -->\n# AGENTS.md\n\n## Project Overview\n\n**MCP for Beginners** is an open-source educational curriculum for learning the Model Context Protocol (MCP) - a standardized framework for interactions between AI models and client applications. This repository provides comprehensive learning materials with hands-on code examples across multiple programming languages.\n\n### Key Technologies\n\n- **Programming Languages**: C#, Java, JavaScript, TypeScript, Python, Rust\n- **Frameworks & SDKs**: \n  - MCP SDK (`@modelcontextprotocol/sdk`)\n  - Spring Boot (Java)\n  - FastMCP (Python)\n  - LangChain4j (Java)\n- **Databases**: PostgreSQL with pgvector extension\n- **Cloud Platforms**: Azure (Container Apps, OpenAI, Content Safety, Application Insights)\n- **Build Tools**: npm, Maven, pip, Cargo\n- **Documentation**: Markdown with automated multi-language translation (48+ languages)\n\n### Architecture\n\n- **11 Core Modules (00-11)**: Sequential learning path from fundamentals to advanced topics\n- **Hands-on Labs**: Practical exercises with complete solution code in multiple languages\n- **Sample Projects**: Working MCP server and client implementations\n- **Translation System**: Automated GitHub Actions workflow for multi-language support\n- **Image Assets**: Centralized images directory with translated versions\n\n## Setup Commands\n\nThis is a documentation-focused repository. Most setup occurs within individual sample projects and labs.\n\n### Repository Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/microsoft/mcp-for-beginners.git\ncd mcp-for-beginners\n```\n\n### Working with Sample Projects\n\nSample projects are located in:\n- `03-GettingStarted/samples/` - Language-specific examples\n- `03-GettingStarted/01-first-server/solution/` - First server implementations\n- `03-GettingStarted/02-client/solution/` - Client implementations\n- `11-MCPServerHandsOnLabs/` - Comprehensive database integration labs\n\nEach sample project contains its own setup instructions:\n\n#### TypeScript/JavaScript Projects\n```bash\ncd <project-directory>\nnpm install\nnpm start\n```\n\n#### Python Projects\n```bash\ncd <project-directory>\npip install -r requirements.txt\n# or\npip install -e .\npython main.py\n```\n\n#### Java Projects\n```bash\ncd <project-directory>\nmvn clean install\nmvn spring-boot:run\n```\n\n## Development Workflow\n\n### MCP 7-28 Readiness\n\n#### Repo readiness checklist\n\n- [x] **New contributor clarity**: This file defines repository purpose,\n  structure, contribution rules, and sample setup paths.\n- [x] **Build/test/lint commands with exact flags**:\n  - Repository docs lint:\n    `npx --yes markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#translations\" \"#translated_images\"`\n  - Repository docs link pattern audit:\n    `find . -name \"*.md\" -not -path \"*/node_modules/*\" -not -path \"./translations/*\" -not -path \"./translated_images/*\" -print0 | xargs -0 grep -En \"\\[.*\\]\\(.*\\)\"`\n  - TypeScript sample validation:\n    `cd 03-GettingStarted/samples/typescript && npm ci && npm test && npm run build`\n  - Python sample validation:\n    `cd 10-StreamliningAIWorkflowsBuildingAnMCPServerWithAIToolkit/lab3/code/weather_mcp && python -m pip install -e . && pytest -q`\n  - Java sample validation:\n    `cd 03-GettingStarted/samples/java/calculator && mvn -B -ntp test verify`\n- [x] **One realistic workflow that can become an MCP tool**:\n  `validate_curriculum_change`\n- [x] **Inputs/outputs are explicit** (see specification below).\n- [x] **Permissions and failure modes are documented** (see specification below).\n- [x] **CI testability is explicit** (deterministic commands, explicit\n  exit codes, and machine-readable outputs).\n\n#### Candidate MCP tool workflow: `validate_curriculum_change`\n\n##### Goal\n\nValidate curriculum documentation changes and representative sample code\nhealth before merge.\n\n##### Inputs\n\n- `changed_paths: string[]` (required) - relative paths changed in PR.\n- `run_docs_lint: boolean` (default `true`)\n- `run_links_audit: boolean` (default `true`)\n- `run_samples: { typescript?: boolean, python?: boolean, java?: boolean }`\n  (default all `false`)\n\n##### Outputs\n\n- `status: \"ok\" | \"failed\"`\n- `checks: Array<{ name: string, command: string, exit_code: number,\n  summary: string }>`\n- `artifacts: Array<{ type: \"log\" | \"report\", path: string }>`\n- `failed_checks: string[]`\n\n##### Permissions\n\n- Read workspace files and write tool-generated artifacts (e.g., lint\n  reports, test logs) only; no writes to `translations/` or\n  `translated_images/`.\n- Execute local shell commands.\n- Optional network access only for package restore (`npm ci`,\n  `python -m pip install`, `mvn` dependency resolution).\n- No permission to push, merge, or modify `translations/` or\n  `translated_images/`.\n\n##### Failure modes\n\n- `E_NO_INPUT_PATHS`: `changed_paths` empty.\n- `E_INVALID_PATH`: input path escapes repository root.\n- `E_LINT_FAILED`: markdown lint exits non-zero.\n- `E_LINK_AUDIT_FAILED`: link audit command exits non-zero.\n- `E_SAMPLE_TEST_FAILED`: sample test/build exits non-zero.\n- `E_TIMEOUT`: command exceeded configured timeout.\n\n##### Recommended CI contract\n\nTo automate validation, configure a CI job that:\n\n- Triggers on pull requests touching `*.md`, sample code, or this file.\n- Runs the exact commands listed above.\n- Persists logs as artifacts.\n- Fails the job on any non-zero exit code.\n\n#### If you ship an MCP server from this repo\n\n- [ ] Read the final MCP `2026-07-28` changelog:\n  <https://modelcontextprotocol.io/specification/2026-07-28/changelog>\n- [ ] Verify that the selected SDK release supports MCP `2026-07-28`:\n  <https://modelcontextprotocol.io/docs/sdk>\n- [ ] Remove session and handshake assumptions; treat each request as\n  self-contained:\n  <https://modelcontextprotocol.io/specification/2026-07-28/basic/lifecycle>\n- [ ] Send `Mcp-Method` and `Mcp-Name` headers for raw HTTP requests:\n  <https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http>\n- [ ] Audit hardcoded error codes (`missing resource` moved from `-32002` to `-32602`).\n- [ ] Migrate deprecated Roots, Sampling, Logging, and Dynamic Client\n  Registration:\n  <https://modelcontextprotocol.io/specification/2026-07-28/deprecated>\n- [ ] Migrate off the experimental `2025-11-25` Tasks API:\n  <https://modelcontextprotocol.io/extensions/tasks>\n- [ ] Review authorization for OAuth and OpenID Connect hardening:\n  <https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization>\n\n### Documentation Structure\n\n- **Modules 00-11**: Core curriculum content in sequential order\n- **translations/**: Language-specific versions (auto-generated, do not edit directly)\n- **translated_images/**: Localized image versions (auto-generated)\n- **images/**: Source images and diagrams\n\n### Making Documentation Changes\n\n1. Edit only the English markdown files in the root module directories (00-11)\n2. Update images in the `images/` directory if needed\n3. The co-op-translator GitHub Action will automatically generate translations\n4. Translations are regenerated on push to main branch\n\n### Working with Translations\n\n- **Automated Translation**: GitHub Actions workflow handles all translations\n- **Do NOT manually edit** files in `translations/` directory\n- Translation metadata is embedded in each translated file\n- Supported languages: 48+ languages including Arabic, Chinese, French, German, Hindi, Japanese, Korean, Portuguese, Russian, Spanish, and many more\n\n## Testing Instructions\n\n### Documentation Validation\n\nSince this is primarily a documentation repository, testing focuses on:\n\n1. **Link Pattern Audit**: List Markdown links for review\n\n   ```bash\n   # List Markdown links (pattern audit)\n   find . -name \"*.md\" -not -path \"*/node_modules/*\" -not -path \"./translations/*\" -not -path \"./translated_images/*\" -print0 | xargs -0 grep -En \"\\[.*\\]\\(.*\\)\"\n   ```\n\n2. **Code Sample Validation**: Test that code examples compile/run\n\n   ```bash\n   # Navigate to specific sample and run its tests\n   cd 03-GettingStarted/samples/typescript\n   npm install && npm test\n   ```\n\n3. **Markdown Linting**: Check formatting consistency\n\n   ```bash\n   # Use markdownlint if needed\n   npx --yes markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#translations\" \"#translated_images\"\n   ```\n\n### Sample Project Testing\n\nEach language-specific sample includes its own testing approach:\n\n#### TypeScript/JavaScript\n```bash\nnpm test\nnpm run build\n```\n\n#### Python\n```bash\npytest\npython -m pytest tests/\n```\n\n#### Java\n```bash\nmvn test\nmvn verify\n```\n\n## Code Style Guidelines\n\n### Documentation Style\n\n- Use clear, beginner-friendly language\n- Include code examples in multiple languages where applicable\n- Follow markdown best practices:\n  - Use ATX-style headers (`#` syntax)\n  - Use fenced code blocks with language identifiers\n  - Include descriptive alt text for images\n  - Keep line lengths reasonable (no hard limit, but be sensible)\n\n### Code Sample Style\n\n#### TypeScript/JavaScript\n- Use ES modules (`import`/`export`)\n- Follow TypeScript strict mode conventions\n- Include type annotations\n- Target ES2022\n\n#### Python\n- Follow PEP 8 style guidelines\n- Use type hints where appropriate\n- Include docstrings for functions and classes\n- Use modern Python features (3.8+)\n\n#### Java\n- Follow Spring Boot conventions\n- Use Java 21 features\n- Follow standard Maven project structure\n- Include Javadoc comments\n\n### File Organization\n\n```\n<module-number>-<ModuleName>/\n├── README.md              # Main module content\n├── samples/               # Code examples (if applicable)\n│   ├── typescript/\n│   ├── python/\n│   ├── java/\n│   └── ...\n└── solution/              # Complete working solutions\n    └── <language>/\n```\n\n## Build and Deployment\n\n### Documentation Deployment\n\nThe repository uses GitHub Pages or similar for documentation hosting (if applicable). Changes to the main branch trigger:\n\n1. Translation workflow (`.github/workflows/co-op-translator.yml`)\n2. Automated translation of all English markdown files\n3. Image localization as needed\n\n### No Build Process Required\n\nThis repository primarily contains markdown documentation. No compilation or build step is needed for the core curriculum content.\n\n### Sample Project Deployment\n\nIndividual sample projects may have deployment instructions:\n- See `03-GettingStarted/09-deployment/` for MCP server deployment guidance\n- Azure Container Apps deployment examples in `11-MCPServerHandsOnLabs/`\n\n## Contributing Guidelines\n\n### Pull Request Process\n\n1. **Fork and Clone**: Fork the repository and clone your fork locally\n2. **Create a Branch**: Use descriptive branch names (e.g., `fix/typo-module-3`, `add/python-example`)\n3. **Make Changes**: Edit English markdown files only (not translations)\n4. **Test Locally**: Verify markdown renders correctly\n5. **Submit PR**: Use clear PR titles and descriptions\n6. **CLA**: Sign the Microsoft Contributor License Agreement when prompted\n\n### PR Title Format\n\nUse clear, descriptive titles:\n- `[Module XX] Brief description` for module-specific changes\n- `[Samples] Description` for sample code changes\n- `[Docs] Description` for general documentation updates\n\n### What to Contribute\n\n- Bug fixes in documentation or code samples\n- New code examples in additional languages\n- Clarifications and improvements to existing content\n- New case studies or practical examples\n- Issue reports for unclear or incorrect content\n\n### What NOT to Do\n\n- Do not directly edit files in `translations/` directory\n- Do not edit `translated_images/` directory\n- Do not add large binary files without discussion\n- Do not change translation workflow files without coordination\n\n## Additional Notes\n\n### Repository Maintenance\n\n- **Changelog**: All significant changes are documented in `changelog.md`\n- **Study Guide**: Use `study_guide.md` for curriculum navigation overview\n- **Issue Templates**: Use GitHub issue templates for bug reports and feature requests\n- **Code of Conduct**: All contributors must follow the Microsoft Open Source Code of Conduct\n\n### Learning Path\n\nFollow modules in sequential order (00-11) for optimal learning:\n1. **00-02**: Fundamentals (Introduction, Core Concepts, Security)\n2. **03**: Getting Started with hands-on implementation\n3. **04-05**: Practical implementation and advanced topics\n4. **06-10**: Community, best practices, and real-world applications\n5. **11**: Comprehensive database integration labs (13 sequential labs)\n\n### Support Resources\n\n- **Documentation**: https://modelcontextprotocol.io/\n- **Specification**: https://modelcontextprotocol.io/specification/2026-07-28/\n- **Community**: https://github.com/orgs/modelcontextprotocol/discussions\n- **Discord**: Microsoft Foundry Discord server\n- **Related Courses**: See README.md for other Microsoft learning paths\n\n### Common Troubleshooting\n\n**Q: My PR is failing the translation check**\nA: Ensure you only edited English markdown files in the root module directories, not translated versions.\n\n**Q: How do I add a new language?**\nA: Language support is managed through the co-op-translator workflow. Open an issue to discuss adding new languages.\n\n**Q: Code samples aren't working**\nA: Ensure you've followed the setup instructions in the specific sample's README. Check that you have the correct versions of dependencies installed.\n\n**Q: Images aren't displaying**\nA: Verify image paths are relative and use forward slashes. Images should be in the `images/` directory or `translated_images/` for localized versions.\n\n### Performance Considerations\n\n- Translation workflow may take several minutes to complete\n- Large images should be optimized before committing\n- Keep individual markdown files focused and reasonably sized\n- Use relative links for better portability\n\n### Project Governance\n\nThis project follows Microsoft open source practices:\n- MIT License for code and documentation\n- Microsoft Open Source Code of Conduct\n- CLA required for contributions\n- Security issues: Follow SECURITY.md guidelines\n- Support: See SUPPORT.md for help resources\n\n<!-- File: 11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~9231 | Category: plugin-manifest) -->\n# Tool Development\n\n## 🎯 What This Lab Covers\n\nThis lab dives deep into creating sophisticated MCP tools that provide AI assistants with powerful database query capabilities, schema introspection, and analytics functions. You'll learn to build tools that are both powerful and safe, with comprehensive error handling and performance optimization.\n\n## Overview\n\nMCP tools are the interface between AI assistants and your data systems. Well-designed tools provide structured, validated access to complex operations while maintaining security and performance. This lab covers the complete lifecycle of tool development from design to deployment.\n\nOur retail MCP server implements a comprehensive suite of tools that enable natural language querying of sales data, product catalogs, and business analytics while maintaining strict security boundaries and optimal performance.\n\n## Learning Objectives\n\nBy the end of this lab, you will be able to:\n\n- **Design** advanced MCP tools with complex parameter validation\n- **Implement** secure database query tools with SQL injection protection\n- **Create** schema introspection capabilities for dynamic queries\n- **Build** custom analytics tools for business intelligence\n- **Apply** comprehensive error handling and graceful degradation\n- **Optimize** tool performance for production workloads\n\n## 🛠️ Core Tool Architecture\n\n### Tool Design Principles\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Query Validation and Security\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Database Query Tools\n\n### Sales Analysis Tool\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Schema Introspection Tool\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytics and Utility Tools\n\n### Business Intelligence Tool\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Key Takeaways\n\nAfter completing this lab, you should have:\n\n✅ **Advanced Tool Architecture**: Implemented sophisticated MCP tools with comprehensive error handling  \n✅ **Query Validation**: Built secure SQL validation to prevent injection attacks  \n✅ **Database Tools**: Created powerful sales analysis and schema introspection capabilities  \n✅ **Business Intelligence**: Developed analytics tools for comprehensive business insights\n\n<!-- File: translations/ar/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~10169 | Category: plugin-manifest) -->\n# تطوير الأدوات\n\n## 🎯 ما يغطيه هذا المختبر\n\nيستعرض هذا المختبر كيفية إنشاء أدوات MCP متقدمة تمنح المساعدين الذكيين قدرات قوية لاستعلام قواعد البيانات، استكشاف المخططات، ووظائف التحليل. ستتعلم كيفية بناء أدوات قوية وآمنة مع معالجة شاملة للأخطاء وتحسين الأداء.\n\n## نظرة عامة\n\nأدوات MCP هي الواجهة بين المساعدين الذكيين وأنظمة البيانات الخاصة بك. الأدوات المصممة بشكل جيد توفر وصولاً منظمًا ومُتحققًا إلى العمليات المعقدة مع الحفاظ على الأمان والأداء. يغطي هذا المختبر دورة حياة تطوير الأدوات بالكامل من التصميم إلى النشر.\n\nيُنفذ خادم MCP الخاص بالتجزئة مجموعة شاملة من الأدوات التي تُمكّن الاستعلام الطبيعي عن بيانات المبيعات، كتالوجات المنتجات، وتحليلات الأعمال مع الحفاظ على حدود أمان صارمة وأداء مثالي.\n\n## أهداف التعلم\n\nبنهاية هذا المختبر، ستكون قادرًا على:\n\n- **تصميم** أدوات MCP متقدمة مع تحقق معقد من المعلمات  \n- **تنفيذ** أدوات استعلام قواعد بيانات آمنة مع حماية من حقن SQL  \n- **إنشاء** قدرات استكشاف المخططات للاستعلامات الديناميكية  \n- **بناء** أدوات تحليل مخصصة لذكاء الأعمال  \n- **تطبيق** معالجة شاملة للأخطاء وتدهور سلس  \n- **تحسين** أداء الأدوات لتناسب أعباء العمل الإنتاجية  \n\n## 🛠️ بنية الأدوات الأساسية\n\n### مبادئ تصميم الأدوات\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n  \n### التحقق من الاستعلام والأمان\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n  \n\n## 🗃️ أدوات استعلام قواعد البيانات\n\n### أداة تحليل المبيعات\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n  \n### أداة استكشاف المخططات\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 📊 أدوات التحليل والمرافق\n\n### أداة ذكاء الأعمال\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 🎯 النقاط الرئيسية\n\nبعد إكمال هذا المختبر، يجب أن تكون قد حققت:\n\n✅ **بنية أدوات متقدمة**: تنفيذ أدوات MCP متطورة مع معالجة شاملة للأخطاء  \n✅ **التحقق من الاستعلام**: بناء تحقق SQL آمن لمنع هجمات الحقن  \n✅ **أدوات قواعد البيانات**: إنشاء قدرات قوية لتحليل المبيعات واستكشاف المخططات  \n✅ **ذكاء الأعمال**: تطوير أدوات تحليل للحصول على رؤى شاملة للأعمال  \n✅ **تحسين الأداء**: تطبيق التخزين المؤقت، إدارة الاتصال، وتحسين الاستعلام  \n✅ **تكامل الأمان**: تنفيذ التحكم في الوصول بناءً على الأدوار وتسجيل التدقيق  \n\n## 🚀 ما التالي\n\nتابع مع **[مختبر 07: تكامل البحث الدلالي](../07-Semantic-Search/README.md)** لـ:\n\n- دمج قدرات البحث المتجهي مع أدوات MCP  \n- بناء وظيفة البحث الدلالي عن المن\n\n<!-- File: translations/bg/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~9837 | Category: plugin-manifest) -->\n# Разработка на инструменти\n\n## 🎯 Какво обхваща този лабораторен урок\n\nТози лабораторен урок се фокусира върху създаването на усъвършенствани MCP инструменти, които предоставят на AI асистентите мощни възможности за заявки към бази данни, инспекция на схеми и аналитични функции. Ще научите как да изграждате инструменти, които са едновременно мощни и безопасни, с изчерпателно управление на грешки и оптимизация на производителността.\n\n## Обзор\n\nMCP инструментите са интерфейсът между AI асистентите и вашите системи за данни. Добре проектираните инструменти осигуряват структурирани и валидирани операции, като същевременно поддържат сигурност и производителност. Този лабораторен урок обхваща целия жизнен цикъл на разработката на инструменти – от проектиране до внедряване.\n\nНашият MCP сървър за търговия на дребно включва цялостен набор от инструменти, които позволяват заявки на естествен език за данни за продажби, продуктови каталози и бизнес анализи, като същевременно поддържат строги граници на сигурност и оптимална производителност.\n\n## Цели на обучението\n\nДо края на този лабораторен урок ще можете:\n\n- **Да проектирате** усъвършенствани MCP инструменти с комплексна валидация на параметри\n- **Да внедрите** сигурни инструменти за заявки към бази данни с защита срещу SQL инжекции\n- **Да създадете** възможности за инспекция на схеми за динамични заявки\n- **Да изградите** персонализирани аналитични инструменти за бизнес интелигентност\n- **Да приложите** изчерпателно управление на грешки и плавна деградация\n- **Да оптимизирате** производителността на инструментите за работни натоварвания в продукция\n\n## 🛠️ Основна архитектура на инструментите\n\n### Принципи на проектиране на инструменти\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Валидация на заявки и сигурност\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Инструменти за заявки към бази данни\n\n### Инструмент за анализ на продажби\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Инструмент за инспекция на схеми\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Аналитични и помощни инструменти\n\n### Инструмент за бизнес интелигентност\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Основни изводи\n\nСлед завършване на този лабораторен урок, трябва да\n\n<!-- File: translations/bn/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~11142 | Category: plugin-manifest) -->\n# টুল ডেভেলপমেন্ট\n\n## 🎯 এই ল্যাবে কী শেখানো হবে\n\nএই ল্যাবে আপনি শিখবেন কীভাবে উন্নত MCP টুল তৈরি করতে হয় যা AI অ্যাসিস্ট্যান্টকে শক্তিশালী ডাটাবেস কোয়েরি করার ক্ষমতা, স্কিমা ইনট্রোস্পেকশন এবং অ্যানালিটিক্স ফাংশন প্রদান করে। আপনি এমন টুল তৈরি করতে শিখবেন যা শক্তিশালী এবং নিরাপদ, যেখানে ব্যাপক ত্রুটি পরিচালনা এবং পারফরম্যান্স অপ্টিমাইজেশন অন্তর্ভুক্ত থাকবে।\n\n## ওভারভিউ\n\nMCP টুল হলো AI অ্যাসিস্ট্যান্ট এবং আপনার ডেটা সিস্টেমের মধ্যে ইন্টারফেস। ভালোভাবে ডিজাইন করা টুল জটিল অপারেশনের জন্য গঠনমূলক এবং যাচাই করা অ্যাক্সেস প্রদান করে, একইসঙ্গে নিরাপত্তা এবং পারফরম্যান্স বজায় রাখে। এই ল্যাবে টুল ডেভেলপমেন্টের সম্পূর্ণ জীবনচক্র, ডিজাইন থেকে ডিপ্লয়মেন্ট পর্যন্ত, কভার করা হয়েছে।\n\nআমাদের রিটেইল MCP সার্ভার একটি বিস্তৃত টুলের সেট বাস্তবায়ন করে যা সেলস ডেটা, প্রোডাক্ট ক্যাটালগ এবং বিজনেস অ্যানালিটিক্সের প্রাকৃতিক ভাষায় কোয়েরি করার ক্ষমতা প্রদান করে, কঠোর নিরাপত্তা সীমা এবং সর্বোত্তম পারফরম্যান্স বজায় রেখে।\n\n## শেখার লক্ষ্য\n\nএই ল্যাব শেষে আপনি সক্ষম হবেন:\n\n- **ডিজাইন**: জটিল প্যারামিটার যাচাই সহ উন্নত MCP টুল ডিজাইন করা  \n- **ইমপ্লিমেন্ট**: SQL ইনজেকশন প্রতিরোধ সহ নিরাপদ ডাটাবেস কোয়েরি টুল তৈরি করা  \n- **ক্রিয়েট**: ডায়নামিক কোয়েরির জন্য স্কিমা ইনট্রোস্পেকশন ক্ষমতা তৈরি করা  \n- **বিল্ড**: বিজনেস ইন্টেলিজেন্সের জন্য কাস্টম অ্যানালিটিক্স টুল তৈরি করা  \n- **অ্যাপ্লাই**: ব্যাপক ত্রুটি পরিচালনা এবং গ্রেসফুল ডিগ্রেডেশন প্রয়োগ করা  \n- **অপ্টিমাইজ**: প্রোডাকশন ওয়ার্কলোডের জন্য টুল পারফরম্যান্স অপ্টিমাইজ করা  \n\n## 🛠️ মূল টুল আর্কিটেকচার\n\n### টুল ডিজাইন নীতিমালা\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n  \n### কোয়েরি যাচাই এবং নিরাপত্তা\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n  \n\n## 🗃️ ডাটাবেস কোয়েরি টুল\n\n### সেলস অ্যানালাইসিস টুল\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n  \n### স্কিমা ইনট্রোস্পেকশন টুল\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 📊 অ্যানালিটিক্স এবং ইউটিলিটি টুল\n\n### বিজনেস ইন্টেলিজেন্স টুল\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 🎯 মূল বিষয়গুলো\n\nএই ল্যাব সম্পন্ন করার পর আপনি অর্জন করবেন:\n\n✅ **উন্নত টুল আর্কিটেকচার**: ব্যাপক ত্রুটি পরিচালনা সহ উন্নত MCP টুল বাস্তবায়ন  \n✅ **কোয়েরি যাচাই**: SQL ইনজেকশন আক্রমণ প্রতিরোধের জন্য নিরাপদ SQL যাচাই তৈরি  \n✅ **ডাটাবেস টুল**: শক্তিশালী সেলস অ্যানালাইসিস এবং স্কিমা ইনট্রোস্পেকশন ক্ষমতা তৈরি  \n✅ **বিজনেস ইন্টেলিজেন্স**: ব্যাপক\n\n<!-- File: translations/cs/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~9710 | Category: plugin-manifest) -->\n# Vývoj nástrojů\n\n## 🎯 Co tento lab pokrývá\n\nTento lab se podrobně zabývá tvorbou sofistikovaných MCP nástrojů, které poskytují AI asistentům výkonné schopnosti dotazování do databází, introspekce schémat a analytické funkce. Naučíte se vytvářet nástroje, které jsou nejen výkonné, ale také bezpečné, s důkladným zpracováním chyb a optimalizací výkonu.\n\n## Přehled\n\nMCP nástroje představují rozhraní mezi AI asistenty a vašimi datovými systémy. Dobře navržené nástroje poskytují strukturovaný a validovaný přístup ke složitým operacím při zachování bezpečnosti a výkonu. Tento lab pokrývá kompletní životní cyklus vývoje nástrojů od návrhu až po nasazení.\n\nNáš maloobchodní MCP server implementuje komplexní sadu nástrojů, které umožňují přirozené jazykové dotazování na prodejní data, katalogy produktů a obchodní analýzy, přičemž zachovává přísné bezpečnostní hranice a optimální výkon.\n\n## Cíle učení\n\nNa konci tohoto labu budete schopni:\n\n- **Navrhnout** pokročilé MCP nástroje s komplexní validací parametrů\n- **Implementovat** bezpečné nástroje pro dotazování do databází s ochranou proti SQL injekcím\n- **Vytvořit** schopnosti introspekce schémat pro dynamické dotazy\n- **Postavit** vlastní analytické nástroje pro obchodní inteligenci\n- **Aplikovat** důkladné zpracování chyb a postupné zhoršování funkcí\n- **Optimalizovat** výkon nástrojů pro produkční zátěže\n\n## 🛠️ Základní architektura nástrojů\n\n### Principy návrhu nástrojů\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Validace dotazů a bezpečnost\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Nástroje pro dotazování do databází\n\n### Nástroj pro analýzu prodeje\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Nástroj pro introspekci schémat\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytické a užitkové nástroje\n\n### Nástroj pro obchodní inteligenci\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Klíčové poznatky\n\nPo dokončení tohoto labu byste měli mít:\n\n✅ **Pokročilou architekturu nástrojů**: Implementované sofistikované MCP nástroje s důkladným zpracováním chyb  \n✅ **Validaci dotazů**: Vytvořenou bezpečnou SQL validaci pro prevenci útoků injekcí  \n✅ **Databázové nástroje**: Vyvinuté výkonné schopnosti analýzy prodeje a introspekce schémat\n\n<!-- File: translations/da/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~9589 | Category: plugin-manifest) -->\n# Udvikling af Værktøjer\n\n## 🎯 Hvad Dækker Dette Lab\n\nDette lab går i dybden med at skabe avancerede MCP-værktøjer, der giver AI-assistenter kraftfulde muligheder for databaseforespørgsler, skemainspektion og analytiske funktioner. Du vil lære at bygge værktøjer, der både er effektive og sikre, med omfattende fejlhåndtering og optimering af ydeevne.\n\n## Oversigt\n\nMCP-værktøjer er grænsefladen mellem AI-assistenter og dine datasystemer. Veludformede værktøjer giver struktureret og valideret adgang til komplekse operationer, samtidig med at sikkerhed og ydeevne opretholdes. Dette lab dækker hele livscyklussen for værktøjsudvikling fra design til implementering.\n\nVores MCP-server til detailhandel implementerer en omfattende suite af værktøjer, der muliggør naturlig sprogforespørgsel af salgsdata, produktkataloger og forretningsanalyse, samtidig med at strenge sikkerhedsgrænser og optimal ydeevne opretholdes.\n\n## Læringsmål\n\nVed afslutningen af dette lab vil du kunne:\n\n- **Designe** avancerede MCP-værktøjer med kompleks parametervalidering\n- **Implementere** sikre databaseforespørgselsværktøjer med beskyttelse mod SQL-injektion\n- **Skabe** skemainspektionsfunktioner til dynamiske forespørgsler\n- **Bygge** skræddersyede analytiske værktøjer til forretningsintelligens\n- **Anvende** omfattende fejlhåndtering og elegant nedgradering\n- **Optimere** værktøjets ydeevne til produktionsarbejdsbelastninger\n\n## 🛠️ Grundlæggende Værktøjsarkitektur\n\n### Principper for Værktøjsdesign\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Validering af Forespørgsler og Sikkerhed\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Databaseforespørgselsværktøjer\n\n### Værktøj til Salgsanalyse\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Skemainspektionsværktøj\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytiske og Hjælpeværktøjer\n\n### Værktøj til Forretningsintelligens\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Vigtige Læringspunkter\n\nEfter at have gennemført dette lab, bør du have:\n\n✅ **Avanceret Værktøjsarkitektur**: Implementeret sofistikerede MCP-værktøjer med omfattende fejlhåndtering  \n✅ **Validering af Forespørgsler**: Bygget sikker SQL-validering for at forhindre injektionsangreb  \n✅ **Databaseværk\n\n<!-- File: translations/de/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~9435 | Category: plugin-manifest) -->\n# Tool-Entwicklung\n\n## 🎯 Was dieses Lab abdeckt\n\nDieses Lab beschäftigt sich intensiv mit der Entwicklung fortschrittlicher MCP-Tools, die KI-Assistenten leistungsstarke Datenbankabfragefunktionen, Schema-Inspektion und Analysefunktionen bieten. Sie lernen, Tools zu erstellen, die sowohl leistungsstark als auch sicher sind, mit umfassender Fehlerbehandlung und Leistungsoptimierung.\n\n## Überblick\n\nMCP-Tools sind die Schnittstelle zwischen KI-Assistenten und Ihren Datensystemen. Gut gestaltete Tools bieten strukturierten, validierten Zugriff auf komplexe Operationen und gewährleisten dabei Sicherheit und Leistung. Dieses Lab behandelt den gesamten Lebenszyklus der Tool-Entwicklung, von der Konzeption bis zur Bereitstellung.\n\nUnser MCP-Server für den Einzelhandel implementiert eine umfassende Suite von Tools, die natürliche Sprachabfragen von Verkaufsdaten, Produktkatalogen und Geschäftsanalyse ermöglichen, während strenge Sicherheitsgrenzen und optimale Leistung eingehalten werden.\n\n## Lernziele\n\nAm Ende dieses Labs werden Sie in der Lage sein:\n\n- **Entwerfen** fortschrittlicher MCP-Tools mit komplexer Parametervalidierung  \n- **Implementieren** sicherer Datenbankabfragetools mit Schutz vor SQL-Injection  \n- **Erstellen** von Schema-Inspektionsfunktionen für dynamische Abfragen  \n- **Entwickeln** benutzerdefinierter Analysetools für Business Intelligence  \n- **Anwenden** umfassender Fehlerbehandlung und sanfter Degradierung  \n- **Optimieren** der Tool-Leistung für Produktionsumgebungen  \n\n## 🛠️ Kernarchitektur der Tools\n\n### Prinzipien des Tool-Designs\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n  \n### Abfragevalidierung und Sicherheit\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n  \n\n## 🗃️ Datenbankabfragetools\n\n### Verkaufsanalyse-Tool\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n  \n### Schema-Inspektions-Tool\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 📊 Analyse- und Utility-Tools\n\n### Business-Intelligence-Tool\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n  \n\n## 🎯 Wichtige Erkenntnisse\n\nNach Abschluss dieses Labs sollten Sie:\n\n✅ **Fortschrittliche Tool-Architektur**: Komplexe MCP-Tools mit umfassender Fehlerbehandlung implementiert haben  \n✅ **Abfragevalidierung**: Sichere SQL-Validierung zur\n\n<!-- File: translations/el/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~10644 | Category: plugin-manifest) -->\n# Ανάπτυξη Εργαλείων\n\n## 🎯 Τι Καλύπτει Αυτό το Εργαστήριο\n\nΑυτό το εργαστήριο εμβαθύνει στη δημιουργία εξελιγμένων εργαλείων MCP που παρέχουν στους βοηθούς AI ισχυρές δυνατότητες ερωτημάτων βάσεων δεδομένων, ανάλυσης σχημάτων και λειτουργιών ανάλυσης. Θα μάθετε να δημιουργείτε εργαλεία που είναι τόσο ισχυρά όσο και ασφαλή, με ολοκληρωμένο χειρισμό σφαλμάτων και βελτιστοποίηση απόδοσης.\n\n## Επισκόπηση\n\nΤα εργαλεία MCP είναι η διεπαφή μεταξύ των βοηθών AI και των συστημάτων δεδομένων σας. Καλά σχεδιασμένα εργαλεία παρέχουν δομημένη, επικυρωμένη πρόσβαση σε σύνθετες λειτουργίες, διατηρώντας παράλληλα την ασφάλεια και την απόδοση. Αυτό το εργαστήριο καλύπτει τον πλήρη κύκλο ζωής της ανάπτυξης εργαλείων, από τον σχεδιασμό έως την υλοποίηση.\n\nΟ MCP server μας για το λιανικό εμπόριο υλοποιεί μια ολοκληρωμένη σουίτα εργαλείων που επιτρέπουν ερωτήματα φυσικής γλώσσας για δεδομένα πωλήσεων, καταλόγους προϊόντων και επιχειρηματική ανάλυση, ενώ διατηρούν αυστηρά όρια ασφαλείας και βέλτιστη απόδοση.\n\n## Στόχοι Μάθησης\n\nΜέχρι το τέλος αυτού του εργαστηρίου, θα μπορείτε να:\n\n- **Σχεδιάσετε** εξελιγμένα εργαλεία MCP με σύνθετη επικύρωση παραμέτρων\n- **Υλοποιήσετε** ασφαλή εργαλεία ερωτημάτων βάσεων δεδομένων με προστασία από SQL injection\n- **Δημιουργήσετε** δυνατότητες ανάλυσης σχημάτων για δυναμικά ερωτήματα\n- **Κατασκευάσετε** προσαρμοσμένα εργαλεία ανάλυσης για επιχειρηματική ευφυΐα\n- **Εφαρμόσετε** ολοκληρωμένο χειρισμό σφαλμάτων και ομαλή υποβάθμιση λειτουργιών\n- **Βελτιστοποιήσετε** την απόδοση εργαλείων για παραγωγικά φορτία\n\n## 🛠️ Βασική Αρχιτεκτονική Εργαλείων\n\n### Αρχές Σχεδιασμού Εργαλείων\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Επικύρωση Ερωτημάτων και Ασφάλεια\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Εργαλεία Ερωτημάτων Βάσεων Δεδομένων\n\n### Εργαλείο Ανάλυσης Πωλήσεων\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Εργαλείο Ανάλυσης Σχημάτων\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Εργαλεία Ανάλυσης και Χρησιμότητας\n\n### Εργαλείο Επιχειρηματικής Ευφυΐας\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Βασικά Συμπεράσματα\n\nΜετά την ολοκλήρωση αυτού του εργαστηρίου, θα έχετε:\n\n✅ **Προηγμένη Αρχιτεκτονική Εργαλείων**: Υλοποιήσει εξελιγμένα εργαλεία MCP με ολοκληρωμένο χειρ\n\n<!-- File: translations/en/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~9240 | Category: plugin-manifest) -->\n# Tool Development\n\n## 🎯 What This Lab Covers\n\nThis lab focuses on creating advanced MCP tools that equip AI assistants with robust capabilities for database querying, schema exploration, and analytics. You'll learn how to design tools that are both powerful and secure, incorporating thorough error handling and performance optimization.\n\n## Overview\n\nMCP tools act as the bridge between AI assistants and your data systems. Properly designed tools enable structured and validated access to complex operations while ensuring security and efficiency. This lab guides you through the entire process of tool development, from initial design to final deployment.\n\nOur retail MCP server includes a comprehensive set of tools that allow natural language querying of sales data, product catalogs, and business analytics, all while maintaining strict security protocols and high performance.\n\n## Learning Objectives\n\nBy the end of this lab, you will be able to:\n\n- **Design** advanced MCP tools with robust parameter validation\n- **Implement** secure database query tools that protect against SQL injection\n- **Create** schema exploration features for dynamic queries\n- **Develop** custom analytics tools for business intelligence\n- **Apply** thorough error handling and graceful fallback mechanisms\n- **Optimize** tool performance for production environments\n\n## 🛠️ Core Tool Architecture\n\n### Tool Design Principles\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Query Validation and Security\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Database Query Tools\n\n### Sales Analysis Tool\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Schema Introspection Tool\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Analytics and Utility Tools\n\n### Business Intelligence Tool\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Key Takeaways\n\nAfter completing this lab, you should have:\n\n✅ **Advanced Tool Architecture**: Designed and implemented sophisticated MCP tools with robust error handling  \n✅ **Query Validation**: Developed secure SQL validation mechanisms to prevent injection attacks  \n✅ **Database Tools**: Built powerful tools for sales analysis and schema exploration  \n✅ **Business Intelligence**: Created analytics tools for\n\n<!-- File: translations/es/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~9372 | Category: plugin-manifest) -->\n# Desarrollo de Herramientas\n\n## 🎯 Qué Cubre Este Laboratorio\n\nEste laboratorio profundiza en la creación de herramientas MCP avanzadas que proporcionan a los asistentes de IA capacidades poderosas para consultas de bases de datos, introspección de esquemas y funciones analíticas. Aprenderás a construir herramientas que sean tanto potentes como seguras, con manejo de errores integral y optimización del rendimiento.\n\n## Descripción General\n\nLas herramientas MCP son la interfaz entre los asistentes de IA y tus sistemas de datos. Las herramientas bien diseñadas ofrecen acceso estructurado y validado a operaciones complejas mientras mantienen la seguridad y el rendimiento. Este laboratorio cubre el ciclo completo de desarrollo de herramientas, desde el diseño hasta la implementación.\n\nNuestro servidor MCP para retail implementa un conjunto completo de herramientas que permiten consultas en lenguaje natural sobre datos de ventas, catálogos de productos y análisis de negocios, manteniendo estrictos límites de seguridad y un rendimiento óptimo.\n\n## Objetivos de Aprendizaje\n\nAl finalizar este laboratorio, podrás:\n\n- **Diseñar** herramientas MCP avanzadas con validación compleja de parámetros  \n- **Implementar** herramientas seguras de consulta de bases de datos con protección contra inyección SQL  \n- **Crear** capacidades de introspección de esquemas para consultas dinámicas  \n- **Construir** herramientas analíticas personalizadas para inteligencia empresarial  \n- **Aplicar** manejo de errores integral y degradación controlada  \n- **Optimizar** el rendimiento de las herramientas para cargas de trabajo en producción  \n\n## 🛠️ Arquitectura Central de Herramientas\n\n### Principios de Diseño de Herramientas\n\n```python\n# mcp_server/tools/base.py\n\"\"\"\nBase classes and patterns for MCP tool development.\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport asyncio\nimport time\nimport logging\nfrom contextlib import asynccontextmanager\n\nlogger = logging.getLogger(__name__)\n\nclass ToolCategory(Enum):\n    \"\"\"Tool categorization for organization and discovery.\"\"\"\n    DATABASE_QUERY = \"database_query\"\n    SCHEMA_INTROSPECTION = \"schema_introspection\"\n    ANALYTICS = \"analytics\"\n    UTILITY = \"utility\"\n    ADMINISTRATIVE = \"administrative\"\n\n@dataclass\nclass ToolResult:\n    \"\"\"Standardized tool result structure.\"\"\"\n    success: bool\n    data: Any = None\n    error: Optional[str] = None\n    metadata: Optional[Dict[str, Any]] = None\n    execution_time_ms: Optional[float] = None\n    row_count: Optional[int] = None\n\nclass BaseTool(ABC):\n    \"\"\"Abstract base class for all MCP tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, category: ToolCategory):\n        self.name = name\n        self.description = description\n        self.category = category\n        self.call_count = 0\n        self.total_execution_time = 0.0\n        \n    @abstractmethod\n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute the tool with given parameters.\"\"\"\n        pass\n    \n    @abstractmethod\n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get JSON schema for tool input validation.\"\"\"\n        pass\n    \n    async def call(self, **kwargs) -> ToolResult:\n        \"\"\"Wrapper for tool execution with metrics and error handling.\"\"\"\n        \n        start_time = time.time()\n        self.call_count += 1\n        \n        try:\n            # Validate input parameters\n            self._validate_input(kwargs)\n            \n            # Log tool execution\n            logger.info(\n                f\"Executing tool: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'tool_category': self.category.value,\n                    'parameters': self._sanitize_parameters(kwargs)\n                }\n            )\n            \n            # Execute the tool\n            result = await self.execute(**kwargs)\n            \n            # Record execution time\n            execution_time = (time.time() - start_time) * 1000\n            result.execution_time_ms = execution_time\n            self.total_execution_time += execution_time\n            \n            # Log success\n            logger.info(\n                f\"Tool execution completed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'success': result.success,\n                    'row_count': result.row_count\n                }\n            )\n            \n            return result\n            \n        except Exception as e:\n            execution_time = (time.time() - start_time) * 1000\n            \n            logger.error(\n                f\"Tool execution failed: {self.name}\",\n                extra={\n                    'tool_name': self.name,\n                    'execution_time_ms': execution_time,\n                    'error': str(e)\n                },\n                exc_info=True\n            )\n            \n            return ToolResult(\n                success=False,\n                error=f\"Tool execution failed: {str(e)}\",\n                execution_time_ms=execution_time\n            )\n    \n    def _validate_input(self, kwargs: Dict[str, Any]):\n        \"\"\"Validate input parameters against schema.\"\"\"\n        \n        schema = self.get_input_schema()\n        required_props = schema.get('required', [])\n        properties = schema.get('properties', {})\n        \n        # Check required parameters\n        missing_required = [prop for prop in required_props if prop not in kwargs]\n        if missing_required:\n            raise ValueError(f\"Missing required parameters: {missing_required}\")\n        \n        # Type validation would go here\n        # For production, use jsonschema library for comprehensive validation\n    \n    def _sanitize_parameters(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Sanitize parameters for logging (remove sensitive data).\"\"\"\n        \n        # Remove or mask sensitive parameters\n        sanitized = kwargs.copy()\n        sensitive_keys = ['password', 'token', 'secret', 'key']\n        \n        for key in sanitized:\n            if any(sensitive in key.lower() for sensitive in sensitive_keys):\n                sanitized[key] = \"***MASKED***\"\n        \n        return sanitized\n    \n    def get_statistics(self) -> Dict[str, Any]:\n        \"\"\"Get tool usage statistics.\"\"\"\n        \n        return {\n            'name': self.name,\n            'category': self.category.value,\n            'call_count': self.call_count,\n            'total_execution_time_ms': self.total_execution_time,\n            'average_execution_time_ms': (\n                self.total_execution_time / self.call_count \n                if self.call_count > 0 else 0\n            )\n        }\n\nclass DatabaseTool(BaseTool):\n    \"\"\"Base class for database-related tools.\"\"\"\n    \n    def __init__(self, name: str, description: str, db_provider):\n        super().__init__(name, description, ToolCategory.DATABASE_QUERY)\n        self.db_provider = db_provider\n    \n    @asynccontextmanager\n    async def get_connection(self):\n        \"\"\"Get database connection with proper context management.\"\"\"\n        \n        conn = None\n        try:\n            conn = await self.db_provider.get_connection()\n            yield conn\n        finally:\n            if conn:\n                await self.db_provider.release_connection(conn)\n    \n    async def execute_query(\n        self, \n        query: str, \n        params: tuple = None,\n        store_id: str = None\n    ) -> ToolResult:\n        \"\"\"Execute database query with security and performance monitoring.\"\"\"\n        \n        async with self.get_connection() as conn:\n            try:\n                # Set store context if provided\n                if store_id:\n                    await conn.execute(\"SELECT retail.set_store_context($1)\", store_id)\n                \n                # Execute query\n                start_time = time.time()\n                \n                if params:\n                    rows = await conn.fetch(query, *params)\n                else:\n                    rows = await conn.fetch(query)\n                \n                execution_time = (time.time() - start_time) * 1000\n                \n                # Convert rows to dictionaries\n                data = [dict(row) for row in rows]\n                \n                return ToolResult(\n                    success=True,\n                    data=data,\n                    row_count=len(data),\n                    execution_time_ms=execution_time\n                )\n                \n            except Exception as e:\n                logger.error(f\"Database query failed: {str(e)}\")\n                return ToolResult(\n                    success=False,\n                    error=f\"Query execution failed: {str(e)}\"\n                )\n```\n\n### Validación de Consultas y Seguridad\n\n```python\n# mcp_server/tools/query_validator.py\n\"\"\"\nSQL query validation and security for MCP tools.\n\"\"\"\nimport re\nimport sqlparse\nfrom typing import List, Dict, Any, Set\nfrom enum import Enum\n\nclass QueryRisk(Enum):\n    \"\"\"Query risk levels.\"\"\"\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass QueryValidator:\n    \"\"\"Validate and analyze SQL queries for security risks.\"\"\"\n    \n    # Dangerous SQL keywords and patterns\n    DANGEROUS_KEYWORDS = {\n        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT',\n        'UPDATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'sp_',\n        'xp_', 'BULK', 'OPENROWSET', 'OPENDATASOURCE'\n    }\n    \n    # Allowed read-only operations\n    SAFE_KEYWORDS = {\n        'SELECT', 'WITH', 'UNION', 'ORDER', 'GROUP', 'HAVING',\n        'WHERE', 'FROM', 'JOIN', 'AS', 'ON', 'IN', 'EXISTS',\n        'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT'\n    }\n    \n    # Allowed schemas and tables\n    ALLOWED_SCHEMAS = {'retail', 'information_schema', 'pg_catalog'}\n    ALLOWED_TABLES = {\n        'customers', 'products', 'sales_transactions', \n        'sales_transaction_items', 'product_categories',\n        'product_embeddings', 'stores'\n    }\n    \n    def __init__(self):\n        self.injection_patterns = [\n            # SQL injection patterns\n            r\"(\\b(UNION|union)\\s+(ALL\\s+)?(SELECT|select))\",\n            r\"(\\b(DROP|drop)\\s+(TABLE|table|DATABASE|database))\",\n            r\"(\\b(DELETE|delete)\\s+(FROM|from))\",\n            r\"(\\b(INSERT|insert)\\s+(INTO|into))\",\n            r\"(\\b(UPDATE|update)\\s+\\w+\\s+(SET|set))\",\n            r\"(\\b(EXEC|exec|EXECUTE|execute)\\s*\\()\",\n            r\"(\\b(sp_|xp_)\\w+)\",\n            r\"(--\\s*$)\",  # SQL comments\n            r\"(/\\*.*?\\*/)\",  # Block comments\n            r\"(;\\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER))\",\n            r\"(\\bOR\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # OR injection\n            r\"(\\bAND\\b\\s+['\\\"]?\\w+['\\\"]?\\s*=\\s*['\\\"]?\\w+['\\\"]?)\",  # AND injection\n        ]\n        \n        self.compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.injection_patterns]\n    \n    def validate_query(self, query: str) -> Dict[str, Any]:\n        \"\"\"Comprehensive query validation.\"\"\"\n        \n        validation_result = {\n            'is_safe': True,\n            'risk_level': QueryRisk.LOW,\n            'issues': [],\n            'warnings': [],\n            'allowed_operations': [],\n            'metadata': {}\n        }\n        \n        try:\n            # Parse the query\n            parsed = sqlparse.parse(query)\n            \n            if not parsed:\n                validation_result['is_safe'] = False\n                validation_result['issues'].append(\"Unable to parse query\")\n                validation_result['risk_level'] = QueryRisk.HIGH\n                return validation_result\n            \n            # Analyze each statement\n            for statement in parsed:\n                self._analyze_statement(statement, validation_result)\n            \n            # Check for injection patterns\n            self._check_injection_patterns(query, validation_result)\n            \n            # Validate table/schema access\n            self._validate_table_access(query, validation_result)\n            \n            # Determine final risk level\n            self._determine_risk_level(validation_result)\n            \n        except Exception as e:\n            validation_result['is_safe'] = False\n            validation_result['issues'].append(f\"Query analysis failed: {str(e)}\")\n            validation_result['risk_level'] = QueryRisk.CRITICAL\n        \n        return validation_result\n    \n    def _analyze_statement(self, statement, validation_result):\n        \"\"\"Analyze individual SQL statement.\"\"\"\n        \n        # Get statement type\n        stmt_type = statement.get_type()\n        \n        # Check if statement type is allowed\n        if stmt_type and stmt_type.upper() not in ['SELECT', 'WITH']:\n            validation_result['issues'].append(f\"Disallowed statement type: {stmt_type}\")\n            validation_result['is_safe'] = False\n            return\n        \n        # Extract tokens and analyze\n        for token in statement.flatten():\n            if token.ttype is sqlparse.tokens.Keyword:\n                keyword = token.value.upper()\n                \n                if keyword in self.DANGEROUS_KEYWORDS:\n                    validation_result['issues'].append(f\"Dangerous keyword detected: {keyword}\")\n                    validation_result['is_safe'] = False\n                elif keyword in self.SAFE_KEYWORDS:\n                    if keyword not in validation_result['allowed_operations']:\n                        validation_result['allowed_operations'].append(keyword)\n    \n    def _check_injection_patterns(self, query: str, validation_result):\n        \"\"\"Check for SQL injection patterns.\"\"\"\n        \n        for pattern in self.compiled_patterns:\n            matches = pattern.findall(query)\n            if matches:\n                validation_result['issues'].append(f\"Potential injection pattern detected\")\n                validation_result['is_safe'] = False\n    \n    def _validate_table_access(self, query: str, validation_result):\n        \"\"\"Validate that only allowed tables/schemas are accessed.\"\"\"\n        \n        # Extract table names (simplified approach)\n        # In production, use proper SQL parsing\n        from_match = re.findall(r'FROM\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        join_match = re.findall(r'JOIN\\s+(\\w+\\.?\\w*)', query, re.IGNORECASE)\n        \n        all_tables = from_match + join_match\n        \n        for table_ref in all_tables:\n            if '.' in table_ref:\n                schema, table = table_ref.split('.', 1)\n                if schema.lower() not in self.ALLOWED_SCHEMAS:\n                    validation_result['issues'].append(f\"Access to unauthorized schema: {schema}\")\n                    validation_result['is_safe'] = False\n                if table.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table}\")\n            else:\n                # Assume retail schema if not specified\n                if table_ref.lower() not in self.ALLOWED_TABLES:\n                    validation_result['warnings'].append(f\"Access to table: {table_ref}\")\n    \n    def _determine_risk_level(self, validation_result):\n        \"\"\"Determine overall risk level.\"\"\"\n        \n        if not validation_result['is_safe']:\n            if any('injection' in issue.lower() for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.CRITICAL\n            elif any('DROP' in issue or 'DELETE' in issue for issue in validation_result['issues']):\n                validation_result['risk_level'] = QueryRisk.HIGH\n            else:\n                validation_result['risk_level'] = QueryRisk.MEDIUM\n        elif validation_result['warnings']:\n            validation_result['risk_level'] = QueryRisk.LOW\n        else:\n            validation_result['risk_level'] = QueryRisk.LOW\n\n# Global validator instance\nquery_validator = QueryValidator()\n```\n\n## 🗃️ Herramientas de Consulta de Bases de Datos\n\n### Herramienta de Análisis de Ventas\n\n```python\n# mcp_server/tools/sales_analysis.py\n\"\"\"\nComprehensive sales analysis tool for retail data querying.\n\"\"\"\nfrom typing import Dict, Any, List, Optional\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult\nfrom .query_validator import query_validator\n\nclass SalesAnalysisTool(DatabaseTool):\n    \"\"\"Advanced sales analysis and reporting tool.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"execute_sales_query\",\n            description=\"Execute sophisticated sales analysis queries with natural language support\",\n            db_provider=db_provider\n        )\n        \n        # Pre-built query templates for common analysis\n        self.query_templates = {\n            'daily_sales': \"\"\"\n                SELECT \n                    DATE(transaction_date) as sales_date,\n                    COUNT(*) as transaction_count,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT customer_id) as unique_customers\n                FROM retail.sales_transactions \n                WHERE transaction_date >= $1 AND transaction_date <= $2\n                  AND transaction_type = 'sale'\n                GROUP BY DATE(transaction_date)\n                ORDER BY sales_date DESC\n            \"\"\",\n            \n            'top_products': \"\"\"\n                SELECT \n                    p.product_name,\n                    p.brand,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count,\n                    AVG(sti.unit_price) as avg_price\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY p.product_id, p.product_name, p.brand\n                ORDER BY total_revenue DESC\n                LIMIT $3\n            \"\"\",\n            \n            'customer_analysis': \"\"\"\n                SELECT \n                    c.customer_id,\n                    c.first_name || ' ' || c.last_name as customer_name,\n                    c.loyalty_tier,\n                    COUNT(st.transaction_id) as transaction_count,\n                    SUM(st.total_amount) as total_spent,\n                    AVG(st.total_amount) as avg_transaction_value,\n                    MAX(st.transaction_date) as last_purchase_date,\n                    DATE_PART('day', CURRENT_DATE - MAX(st.transaction_date)) as days_since_last_purchase\n                FROM retail.customers c\n                LEFT JOIN retail.sales_transactions st ON c.customer_id = st.customer_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY c.customer_id, c.first_name, c.last_name, c.loyalty_tier\n                HAVING COUNT(st.transaction_id) > 0\n                ORDER BY total_spent DESC\n                LIMIT $3\n            \"\"\",\n            \n            'category_performance': \"\"\"\n                SELECT \n                    pc.category_name,\n                    COUNT(DISTINCT p.product_id) as unique_products,\n                    SUM(sti.quantity) as total_quantity_sold,\n                    SUM(sti.total_price) as total_revenue,\n                    AVG(sti.unit_price) as avg_price,\n                    COUNT(DISTINCT st.transaction_id) as transaction_count\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                WHERE st.transaction_date >= $1 AND st.transaction_date <= $2\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_id, pc.category_name\n                ORDER BY total_revenue DESC\n            \"\"\",\n            \n            'sales_trends': \"\"\"\n                WITH daily_sales AS (\n                    SELECT \n                        DATE(transaction_date) as sales_date,\n                        SUM(total_amount) as daily_revenue,\n                        COUNT(*) as daily_transactions\n                    FROM retail.sales_transactions \n                    WHERE transaction_date >= $1 AND transaction_date <= $2\n                      AND transaction_type = 'sale'\n                    GROUP BY DATE(transaction_date)\n                ),\n                trend_analysis AS (\n                    SELECT \n                        sales_date,\n                        daily_revenue,\n                        daily_transactions,\n                        LAG(daily_revenue, 1) OVER (ORDER BY sales_date) as prev_day_revenue,\n                        LAG(daily_revenue, 7) OVER (ORDER BY sales_date) as prev_week_revenue,\n                        AVG(daily_revenue) OVER (\n                            ORDER BY sales_date \n                            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n                        ) as rolling_7day_avg\n                    FROM daily_sales\n                )\n                SELECT \n                    sales_date,\n                    daily_revenue,\n                    daily_transactions,\n                    rolling_7day_avg,\n                    CASE \n                        WHEN prev_day_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_day_revenue) / prev_day_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as day_over_day_growth_pct,\n                    CASE \n                        WHEN prev_week_revenue IS NOT NULL THEN\n                            ROUND(((daily_revenue - prev_week_revenue) / prev_week_revenue * 100)::numeric, 2)\n                        ELSE NULL\n                    END as week_over_week_growth_pct\n                FROM trend_analysis\n                ORDER BY sales_date DESC\n            \"\"\"\n        }\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute sales analysis query.\"\"\"\n        \n        query_type = kwargs.get('query_type', 'custom')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for sales analysis\"\n            )\n        \n        try:\n            if query_type in self.query_templates:\n                return await self._execute_template_query(query_type, kwargs)\n            elif query_type == 'custom':\n                return await self._execute_custom_query(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown query type: {query_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Sales analysis failed: {str(e)}\"\n            )\n    \n    async def _execute_template_query(self, query_type: str, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute pre-built template query.\"\"\"\n        \n        query = self.query_templates[query_type]\n        store_id = kwargs['store_id']\n        \n        # Default parameters for template queries\n        start_date = kwargs.get('start_date', (datetime.now() - timedelta(days=30)).date())\n        end_date = kwargs.get('end_date', datetime.now().date())\n        limit = kwargs.get('limit', 20)\n        \n        # Convert string dates if needed\n        if isinstance(start_date, str):\n            start_date = datetime.fromisoformat(start_date).date()\n        if isinstance(end_date, str):\n            end_date = datetime.fromisoformat(end_date).date()\n        \n        # Execute query with parameters\n        params = (start_date, end_date, limit) if '$3' in query else (start_date, end_date)\n        \n        result = await self.execute_query(query, params, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': query_type,\n                'date_range': f\"{start_date} to {end_date}\",\n                'store_id': store_id,\n                'analysis_type': 'template'\n            }\n        \n        return result\n    \n    async def _execute_custom_query(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Execute custom SQL query with validation.\"\"\"\n        \n        custom_query = kwargs.get('query')\n        store_id = kwargs['store_id']\n        \n        if not custom_query:\n            return ToolResult(\n                success=False,\n                error=\"Custom query is required when query_type is 'custom'\"\n            )\n        \n        # Validate the query for security\n        validation = query_validator.validate_query(custom_query)\n        \n        if not validation['is_safe']:\n            return ToolResult(\n                success=False,\n                error=f\"Query validation failed: {', '.join(validation['issues'])}\",\n                metadata={\n                    'validation_result': validation,\n                    'risk_level': validation['risk_level'].value\n                }\n            )\n        \n        # Execute validated query\n        result = await self.execute_query(custom_query, None, store_id)\n        \n        if result.success:\n            result.metadata = {\n                'query_type': 'custom',\n                'store_id': store_id,\n                'validation_warnings': validation.get('warnings', []),\n                'analysis_type': 'custom'\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for the sales analysis tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"query_type\": {\n                    \"type\": \"string\",\n                    \"enum\": list(self.query_templates.keys()) + [\"custom\"],\n                    \"description\": \"Type of sales analysis to perform\",\n                    \"default\": \"daily_sales\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for data isolation\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"start_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"Start date for analysis (YYYY-MM-DD)\"\n                },\n                \"end_date\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"End date for analysis (YYYY-MM-DD)\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 1000,\n                    \"description\": \"Maximum number of results to return\",\n                    \"default\": 20\n                },\n                \"query\": {\n                    \"type\": \"string\",\n                    \"description\": \"Custom SQL query (required when query_type is 'custom')\"\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n```\n\n### Herramienta de Introspección de Esquemas\n\n```python\n# mcp_server/tools/schema_introspection.py\n\"\"\"\nDatabase schema introspection and metadata tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass SchemaIntrospectionTool(DatabaseTool):\n    \"\"\"Tool for exploring database schema and metadata.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_table_schema\",\n            description=\"Get detailed schema information for database tables\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute schema introspection.\"\"\"\n        \n        table_name = kwargs.get('table_name')\n        include_constraints = kwargs.get('include_constraints', True)\n        include_indexes = kwargs.get('include_indexes', True)\n        include_statistics = kwargs.get('include_statistics', False)\n        \n        try:\n            if table_name:\n                return await self._get_single_table_schema(\n                    table_name, include_constraints, include_indexes, include_statistics\n                )\n            else:\n                return await self._get_all_tables_schema(include_constraints, include_indexes)\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Schema introspection failed: {str(e)}\"\n            )\n    \n    async def _get_single_table_schema(\n        self, \n        table_name: str, \n        include_constraints: bool,\n        include_indexes: bool,\n        include_statistics: bool\n    ) -> ToolResult:\n        \"\"\"Get detailed schema for a single table.\"\"\"\n        \n        schema_info = {\n            'table_name': table_name,\n            'columns': [],\n            'constraints': [],\n            'indexes': [],\n            'statistics': {}\n        }\n        \n        async with self.get_connection() as conn:\n            # Get column information\n            columns_query = \"\"\"\n                SELECT \n                    column_name,\n                    data_type,\n                    is_nullable,\n                    column_default,\n                    character_maximum_length,\n                    numeric_precision,\n                    numeric_scale,\n                    ordinal_position,\n                    udt_name\n                FROM information_schema.columns\n                WHERE table_schema = 'retail' AND table_name = $1\n                ORDER BY ordinal_position\n            \"\"\"\n            \n            columns = await conn.fetch(columns_query, table_name)\n            schema_info['columns'] = [dict(col) for col in columns]\n            \n            # Get constraints if requested\n            if include_constraints:\n                constraints_query = \"\"\"\n                    SELECT \n                        constraint_name,\n                        constraint_type,\n                        column_name,\n                        foreign_table_name,\n                        foreign_column_name\n                    FROM information_schema.table_constraints tc\n                    LEFT JOIN information_schema.key_column_usage kcu \n                        ON tc.constraint_name = kcu.constraint_name\n                    LEFT JOIN information_schema.referential_constraints rc\n                        ON tc.constraint_name = rc.constraint_name\n                    LEFT JOIN information_schema.key_column_usage fkcu\n                        ON rc.unique_constraint_name = fkcu.constraint_name\n                    WHERE tc.table_schema = 'retail' AND tc.table_name = $1\n                \"\"\"\n                \n                constraints = await conn.fetch(constraints_query, table_name)\n                schema_info['constraints'] = [dict(const) for const in constraints]\n            \n            # Get indexes if requested\n            if include_indexes:\n                indexes_query = \"\"\"\n                    SELECT \n                        indexname as index_name,\n                        indexdef as index_definition,\n                        tablespace\n                    FROM pg_indexes\n                    WHERE schemaname = 'retail' AND tablename = $1\n                \"\"\"\n                \n                indexes = await conn.fetch(indexes_query, table_name)\n                schema_info['indexes'] = [dict(idx) for idx in indexes]\n            \n            # Get table statistics if requested\n            if include_statistics:\n                stats_query = \"\"\"\n                    SELECT \n                        n_tup_ins as inserts,\n                        n_tup_upd as updates,\n                        n_tup_del as deletes,\n                        n_live_tup as live_tuples,\n                        n_dead_tup as dead_tuples,\n                        last_vacuum,\n                        last_autovacuum,\n                        last_analyze,\n                        last_autoanalyze\n                    FROM pg_stat_user_tables\n                    WHERE schemaname = 'retail' AND relname = $1\n                \"\"\"\n                \n                stats = await conn.fetchrow(stats_query, table_name)\n                if stats:\n                    schema_info['statistics'] = dict(stats)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'table_name': table_name,\n                'schema': 'retail',\n                'introspection_type': 'single_table'\n            }\n        )\n    \n    async def _get_all_tables_schema(\n        self, \n        include_constraints: bool,\n        include_indexes: bool\n    ) -> ToolResult:\n        \"\"\"Get schema information for all tables.\"\"\"\n        \n        async with self.get_connection() as conn:\n            # Get all tables in retail schema\n            tables_query = \"\"\"\n                SELECT \n                    table_name,\n                    table_type\n                FROM information_schema.tables\n                WHERE table_schema = 'retail'\n                ORDER BY table_name\n            \"\"\"\n            \n            tables = await conn.fetch(tables_query)\n            schema_info = {\n                'schema_name': 'retail',\n                'tables': []\n            }\n            \n            for table in tables:\n                table_info = {\n                    'table_name': table['table_name'],\n                    'table_type': table['table_type'],\n                    'columns': []\n                }\n                \n                # Get columns for each table\n                columns_query = \"\"\"\n                    SELECT \n                        column_name,\n                        data_type,\n                        is_nullable,\n                        column_default\n                    FROM information_schema.columns\n                    WHERE table_schema = 'retail' AND table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\"\n                \n                columns = await conn.fetch(columns_query, table['table_name'])\n                table_info['columns'] = [dict(col) for col in columns]\n                \n                schema_info['tables'].append(table_info)\n        \n        return ToolResult(\n            success=True,\n            data=schema_info,\n            metadata={\n                'schema': 'retail',\n                'table_count': len(schema_info['tables']),\n                'introspection_type': 'all_tables'\n            }\n        )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for schema introspection tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_name\": {\n                    \"type\": \"string\",\n                    \"description\": \"Specific table name to introspect (optional - if not provided, all tables are returned)\",\n                    \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                },\n                \"include_constraints\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include constraint information\",\n                    \"default\": True\n                },\n                \"include_indexes\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include index information\",\n                    \"default\": True\n                },\n                \"include_statistics\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Include table statistics\",\n                    \"default\": False\n                }\n            },\n            \"additionalProperties\": False\n        }\n\nclass MultiTableSchemaTool(DatabaseTool):\n    \"\"\"Tool for getting schema information for multiple tables at once.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_multiple_table_schemas\",\n            description=\"Get schema information for multiple tables efficiently\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.SCHEMA_INTROSPECTION\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute multi-table schema introspection.\"\"\"\n        \n        table_names = kwargs.get('table_names', [])\n        \n        if not table_names:\n            return ToolResult(\n                success=False,\n                error=\"At least one table name is required\"\n            )\n        \n        try:\n            schemas = {}\n            \n            async with self.get_connection() as conn:\n                for table_name in table_names:\n                    # Get table schema\n                    schema_query = \"\"\"\n                        SELECT \n                            c.column_name,\n                            c.data_type,\n                            c.is_nullable,\n                            c.column_default,\n                            c.character_maximum_length,\n                            tc.constraint_type,\n                            kcu.constraint_name\n                        FROM information_schema.columns c\n                        LEFT JOIN information_schema.key_column_usage kcu\n                            ON c.table_name = kcu.table_name \n                            AND c.column_name = kcu.column_name\n                            AND c.table_schema = kcu.table_schema\n                        LEFT JOIN information_schema.table_constraints tc\n                            ON kcu.constraint_name = tc.constraint_name\n                            AND kcu.table_schema = tc.table_schema\n                        WHERE c.table_schema = 'retail' AND c.table_name = $1\n                        ORDER BY c.ordinal_position\n                    \"\"\"\n                    \n                    columns = await conn.fetch(schema_query, table_name)\n                    \n                    if columns:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'columns': [dict(col) for col in columns]\n                        }\n                    else:\n                        schemas[table_name] = {\n                            'table_name': table_name,\n                            'error': 'Table not found or not accessible'\n                        }\n            \n            return ToolResult(\n                success=True,\n                data=schemas,\n                metadata={\n                    'requested_tables': table_names,\n                    'found_tables': [name for name, info in schemas.items() if 'error' not in info],\n                    'missing_tables': [name for name, info in schemas.items() if 'error' in info]\n                }\n            )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Multi-table schema introspection failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for multi-table schema tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"pattern\": \"^[a-zA-Z_][a-zA-Z0-9_]*$\"\n                    },\n                    \"description\": \"List of table names to get schema information for\",\n                    \"minItems\": 1,\n                    \"maxItems\": 20\n                }\n            },\n            \"required\": [\"table_names\"],\n            \"additionalProperties\": False\n        }\n```\n\n## 📊 Herramientas Analíticas y de Utilidad\n\n### Herramienta de Inteligencia Empresarial\n\n```python\n# mcp_server/tools/business_intelligence.py\n\"\"\"\nAdvanced business intelligence and analytics tools.\n\"\"\"\nfrom typing import Dict, Any, List\nfrom datetime import datetime, timedelta\nfrom .base import DatabaseTool, ToolResult, ToolCategory\n\nclass BusinessIntelligenceTool(DatabaseTool):\n    \"\"\"Advanced analytics tool for business intelligence queries.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"generate_business_insights\",\n            description=\"Generate comprehensive business intelligence reports and insights\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.ANALYTICS\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute business intelligence analysis.\"\"\"\n        \n        analysis_type = kwargs.get('analysis_type', 'summary')\n        store_id = kwargs.get('store_id')\n        \n        if not store_id:\n            return ToolResult(\n                success=False,\n                error=\"store_id is required for business intelligence analysis\"\n            )\n        \n        try:\n            if analysis_type == 'summary':\n                return await self._generate_business_summary(kwargs)\n            elif analysis_type == 'customer_segmentation':\n                return await self._analyze_customer_segmentation(kwargs)\n            elif analysis_type == 'product_performance':\n                return await self._analyze_product_performance(kwargs)\n            elif analysis_type == 'seasonal_trends':\n                return await self._analyze_seasonal_trends(kwargs)\n            else:\n                return ToolResult(\n                    success=False,\n                    error=f\"Unknown analysis type: {analysis_type}\"\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Business intelligence analysis failed: {str(e)}\"\n            )\n    \n    async def _generate_business_summary(self, kwargs: Dict[str, Any]) -> ToolResult:\n        \"\"\"Generate comprehensive business summary.\"\"\"\n        \n        store_id = kwargs['store_id']\n        days = kwargs.get('days', 30)\n        \n        summary_query = \"\"\"\n            WITH date_range AS (\n                SELECT CURRENT_DATE - INTERVAL '%s days' as start_date,\n                       CURRENT_DATE as end_date\n            ),\n            sales_summary AS (\n                SELECT \n                    COUNT(*) as total_transactions,\n                    COUNT(DISTINCT customer_id) as unique_customers,\n                    SUM(total_amount) as total_revenue,\n                    AVG(total_amount) as avg_transaction_value,\n                    COUNT(DISTINCT DATE(transaction_date)) as active_days\n                FROM retail.sales_transactions st, date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            product_summary AS (\n                SELECT \n                    COUNT(DISTINCT p.product_id) as products_sold,\n                    SUM(sti.quantity) as total_items_sold\n                FROM retail.sales_transaction_items sti\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                JOIN retail.products p ON sti.product_id = p.product_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n            ),\n            top_category AS (\n                SELECT \n                    pc.category_name,\n                    SUM(sti.total_price) as category_revenue\n                FROM retail.product_categories pc\n                JOIN retail.products p ON pc.category_id = p.category_id\n                JOIN retail.sales_transaction_items sti ON p.product_id = sti.product_id\n                JOIN retail.sales_transactions st ON sti.transaction_id = st.transaction_id\n                CROSS JOIN date_range dr\n                WHERE st.transaction_date >= dr.start_date \n                  AND st.transaction_date <= dr.end_date\n                  AND st.transaction_type = 'sale'\n                GROUP BY pc.category_name\n                ORDER BY category_revenue DESC\n                LIMIT 1\n            )\n            SELECT \n                ss.*,\n                ps.products_sold,\n                ps.total_items_sold,\n                tc.category_name as top_category,\n                tc.category_revenue as top_category_revenue,\n                CASE \n                    WHEN ss.active_days > 0 THEN ss.total_revenue / ss.active_days\n                    ELSE 0\n                END as avg_daily_revenue\n            FROM sales_summary ss\n            CROSS JOIN product_summary ps\n            CROSS JOIN top_category tc\n        \"\"\" % days\n        \n        result = await self.execute_query(summary_query, None, store_id)\n        \n        if result.success and result.data:\n            summary = result.data[0]\n            \n            # Add derived insights\n            insights = {\n                'revenue_trend': 'stable',  # Would calculate based on historical data\n                'customer_retention': f\"{summary.get('unique_customers', 0)} active customers\",\n                'performance_indicators': {\n                    'transactions_per_day': round(summary.get('total_transactions', 0) / max(summary.get('active_days', 1), 1), 2),\n                    'revenue_per_customer': round(summary.get('total_revenue', 0) / max(summary.get('unique_customers', 1), 1), 2),\n                    'items_per_transaction': round(summary.get('total_items_sold', 0) / max(summary.get('total_transactions', 1), 1), 2)\n                }\n            }\n            \n            summary['insights'] = insights\n            \n            result.data = [summary]\n            result.metadata = {\n                'analysis_type': 'business_summary',\n                'period_days': days,\n                'store_id': store_id\n            }\n        \n        return result\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for business intelligence tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"analysis_type\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"summary\", \"customer_segmentation\", \"product_performance\", \"seasonal_trends\"],\n                    \"description\": \"Type of business intelligence analysis to perform\",\n                    \"default\": \"summary\"\n                },\n                \"store_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"Store ID for analysis\",\n                    \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n                },\n                \"days\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 365,\n                    \"description\": \"Number of days to analyze\",\n                    \"default\": 30\n                }\n            },\n            \"required\": [\"store_id\"],\n            \"additionalProperties\": False\n        }\n\nclass UtilityTool(DatabaseTool):\n    \"\"\"Utility tool for common operations.\"\"\"\n    \n    def __init__(self, db_provider):\n        super().__init__(\n            name=\"get_current_utc_date\",\n            description=\"Get current UTC date and time for reference\",\n            db_provider=db_provider\n        )\n        self.category = ToolCategory.UTILITY\n    \n    async def execute(self, **kwargs) -> ToolResult:\n        \"\"\"Execute utility operation.\"\"\"\n        \n        format_type = kwargs.get('format', 'iso')\n        \n        try:\n            async with self.get_connection() as conn:\n                if format_type == 'iso':\n                    query = \"SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' as current_utc_datetime\"\n                elif format_type == 'epoch':\n                    query = \"SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP AT TIME ZONE 'UTC') as current_utc_epoch\"\n                elif format_type == 'date_only':\n                    query = \"SELECT CURRENT_DATE as current_date\"\n                else:\n                    return ToolResult(\n                        success=False,\n                        error=f\"Unknown format type: {format_type}\"\n                    )\n                \n                result = await conn.fetchrow(query)\n                \n                return ToolResult(\n                    success=True,\n                    data=dict(result),\n                    metadata={\n                        'format_type': format_type,\n                        'timezone': 'UTC'\n                    }\n                )\n        \n        except Exception as e:\n            return ToolResult(\n                success=False,\n                error=f\"Utility operation failed: {str(e)}\"\n            )\n    \n    def get_input_schema(self) -> Dict[str, Any]:\n        \"\"\"Get input schema for utility tool.\"\"\"\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"format\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"iso\", \"epoch\", \"date_only\"],\n                    \"description\": \"Format for the returned date/time\",\n                    \"default\": \"iso\"\n                }\n            },\n            \"additionalProperties\": False\n        }\n```\n\n## 🎯 Puntos Clave\n\nDespués\n\n<!-- File: translations/et/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/et/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/et/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/fa/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/fa/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/fa/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/fi/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/fi/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/fi/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/fr/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/fr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/fr/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/he/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/he/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/he/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/hi/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/hi/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/hi/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/hr/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/hr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/hr/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/hu/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/hu/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/hu/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/id/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/id/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/id/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/it/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/it/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/it/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/ja/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/ja/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ja/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/km/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/km/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/km/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/kn/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/kn/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/kn/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/ko/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/ko/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ko/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/lt/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/lt/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/lt/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/ml/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/ml/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ml/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/mr/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/mr/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/mr/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/ms/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/ms/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/ms/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- File: translations/my/11-MCPServerHandsOnLabs/06-Tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# 06-Tools Documentation\n\nPath: `translations/my/11-MCPServerHandsOnLabs/06-Tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/microsoft/mcp-for-beginners/HEAD/translations/my/11-MCPServerHandsOnLabs/06-Tools/README.md)\n\n<!-- ... +28 more modular skills available via /api/skills/microsoft/mcp-for-beginners?path={path} -->\n</yakaai_skills>"}