Team Ai
Apppublic

srikanthbaskaran/autonomous_software_solutions

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
sql_server.py297 linesDownload Raw Back to root
1from fastmcp import FastMCP2from database import DatabaseExplorer3import logging4import atexit5import asyncio6import pymysql.cursors  # Import pymysql.cursors for explicit DictCursor usage7 8# Configure logging for this module9logging.basicConfig(10    level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"11)12logger = logging.getLogger(__name__)13 14# Initialize database connection globally15db = None16 17 18def initialize_database():19    """Initialize database connection"""20    global db21    logger.info("Attempting to initialize database connection...")22    try:23        db = DatabaseExplorer()24        logger.info("Database connection established successfully.")25    except Exception as e:26        logger.error(27            f"Database connection failed during initialization: {e}", exc_info=True28        )29        raise  # Re-raise to prevent the server from starting without a DB connection30 31 32def cleanup_database():33    """Cleanup database connection"""34    global db35    logger.info("Attempting to clean up database connection...")36    if db:37        try:38            # Check if the connection object exists and has a close method39            if hasattr(db, "connection") and db.connection:40                if db.connection.is_connected():41                    db.connection.close()42                    logger.info("Database connection closed successfully.")43                else:44                    logger.info(45                        "Database connection was already closed or not connected."46                    )47            else:48                logger.info("Database connection object not found or not initialized.")49        except Exception as e:50            logger.warning(51                f"Error closing database connection during cleanup: {e}", exc_info=True52            )53    else:54        logger.info("No database connection to clean up (db object is None).")55 56 57# Register cleanup function to run on program exit58atexit.register(cleanup_database)59 60# Initialize database at startup61try:62    initialize_database()63except Exception:64    # If database initialization fails, log and exit65    logger.critical("Failed to initialize database. Exiting application.")66    exit(1)  # Exit with an error code67 68mcp = FastMCP("sql_report_server")69 70 71@mcp.tool()72async def get_database_schema() -> list:  # Changed return type to list of strings73    """Get a list of all table names in the database."""74    logger.info("Tool 'get_database_schema' called to get table names.")75    try:76        if not db:77            logger.error("Database object is not initialized in get_database_schema.")78            raise RuntimeError("Database not initialized")79 80        conn = db.get_connection()81        cursor = conn.cursor()82 83        try:84            cursor.execute("SHOW TABLES")85            raw_tables = cursor.fetchall()86 87            tables = []88            if raw_tables:89                # Dynamically get the key name for the table name column90                table_column_name = cursor.description[0][0]91                tables = [table[table_column_name] for table in raw_tables]92 93            logger.info(94                f"Successfully fetched {len(tables)} table names from database: {tables}"95            )96            return tables  # Return only table names97        finally:98            cursor.close()99            logger.debug("Cursor closed in get_database_schema.")100 101    except Exception as e:102        logger.error(f"Error in 'get_database_schema': {str(e)}", exc_info=True)103        raise  # Re-raise the exception so the agent knows the tool failed104 105 106@mcp.tool()107async def get_table_details(table_name: str) -> dict:108    """109    Get detailed schema (columns and foreign keys) for a specific table.110 111    Args:112        table_name: The name of the table to get details for.113    """114    logger.info(f"Tool 'get_table_details' called for table: '{table_name}'.")115    try:116        if not db:117            logger.error("Database object is not initialized in get_table_details.")118            raise RuntimeError("Database not initialized")119 120        conn = db.get_connection()121 122        # Use DictCursor for detailed schema queries123        dict_cursor = conn.cursor(pymysql.cursors.DictCursor)124        try:125            # Get columns126            dict_cursor.execute(f"SHOW COLUMNS FROM `{table_name}`")127            columns = {col["Field"]: col["Type"] for col in dict_cursor.fetchall()}128            logger.debug(f"Columns for {table_name}: {columns}")129 130            # Get foreign keys131            dict_cursor.execute(132                f"""133                SELECT COLUMN_NAME, REFERENCED_TABLE_NAME 134                FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE 135                WHERE TABLE_SCHEMA = DATABASE()136                AND TABLE_NAME = '{table_name}' 137                AND REFERENCED_TABLE_NAME IS NOT NULL138                """139            )140            foreign_keys = {141                fk["COLUMN_NAME"]: fk["REFERENCED_TABLE_NAME"]142                for fk in dict_cursor.fetchall()143            }144            logger.debug(f"Foreign keys for {table_name}: {foreign_keys}")145 146            table_schema = {147                "table_name": table_name,148                "columns": columns,149                "foreign_keys": foreign_keys,150            }151            logger.info(f"Successfully retrieved details for table: {table_name}.")152            return table_schema153        except pymysql.Error as e:154            logger.error(155                f"MySQL error fetching details for table '{table_name}': {e}",156                exc_info=True,157            )158            raise ValueError(159                f"Could not retrieve details for table '{table_name}'. It might not exist or there's a permission issue."160            )161        except Exception as e:162            logger.error(163                f"Unexpected error fetching details for table '{table_name}': {e}",164                exc_info=True,165            )166            raise167        finally:168            dict_cursor.close()169            logger.debug(f"DictCursor closed for {table_name}.")170 171    except Exception as e:172        logger.error(f"Error in 'get_table_details': {str(e)}", exc_info=True)173        raise  # Re-raise the exception so the agent knows the tool failed174 175 176@mcp.tool()177async def generate_sql_report(query: str, rationale: str) -> dict:178    """Execute SQL query and return formatted report179 180    Args:181        query: The SQL query to execute182        rationale: Explanation why this query provides the needed report183    """184    logger.info(185        f"Tool 'generate_sql_report' called with query: '{query}' and rationale: '{rationale}'"186    )187    try:188        if not db:189            logger.error("Database object is not initialized in generate_sql_report.")190            raise RuntimeError("Database not initialized")191 192        report = db.execute_report_query(query, rationale)193        logger.info("SQL report generated successfully.")194        return report.model_dump()195    except Exception as e:196        logger.error(f"Error generating SQL report: {str(e)}", exc_info=True)197        raise  # Re-raise the exception198 199 200@mcp.tool()201async def generate_standard_report(report_type: str) -> dict:202    """Generate standard report based on common business needs203 204    Args:205        report_type: Type of report (sales, inventory, financial, etc.)206    """207    logger.info(208        f"Tool 'generate_standard_report' called for report type: '{report_type}')"209    )210    try:211        if not db:212            logger.error(213                "Database object is not initialized in generate_standard_report."214            )215            raise RuntimeError("Database not initialized")216 217        report = db.generate_sample_report(report_type)218        logger.info(f"Standard report '{report_type}' generated successfully.")219        return report.model_dump()220    except Exception as e:221        logger.error(222            f"Error generating standard report for type '{report_type}': {str(e)}",223            exc_info=True,224        )225        raise  # Re-raise the exception226 227 228@mcp.tool()229async def get_report_history() -> list:230    """Get history of all generated reports"""231    logger.info("Tool 'get_report_history' called.")232    try:233        if not db:234            logger.error("Database object is not initialized in get_report_history.")235            raise RuntimeError("Database not initialized")236 237        history = db.get_report_history()238        logger.info(f"Successfully retrieved {len(history)} historical reports.")239        return history240    except Exception as e:241        logger.error(f"Error getting report history: {str(e)}", exc_info=True)242        raise  # Re-raise the exception243 244 245@mcp.resource("sqlreports://schema")246async def read_schema_resource() -> list:  # Changed return type to list247    """Resource endpoint for database schema (returns only table names)"""248    logger.info("Resource 'sqlreports://schema' accessed.")249    try:250        if not db:251            logger.error("Database object is not initialized for schema resource.")252            raise RuntimeError("Database not initialized")253 254        # Call the tool to get just the table names255        table_names = await get_database_schema()256        logger.info(257            f"Successfully prepared table names for schema resource: {len(table_names)} tables."258        )259        return table_names  # Return only table names260    except Exception as e:261        logger.error(f"Error reading schema resource: {str(e)}", exc_info=True)262        raise  # Re-raise the exception263 264 265@mcp.resource("sqlreports://history")266async def read_history_resource() -> list:267    """Resource endpoint for report history"""268    logger.info("Resource 'sqlreports://history' accessed.")269    try:270        if not db:271            logger.error("Database object is not initialized for history resource.")272            raise RuntimeError("Database not initialized")273 274        history = db.get_report_history()275        logger.info(276            f"Successfully prepared history for resource: {len(history)} entries."277        )278        return history279    except Exception as e:280        logger.error(f"Error reading history resource: {str(e)}", exc_info=True)281        raise  # Re-raise the exception282 283 284if __name__ == "__main__":285    logger.info("Starting SQL Report MCP Server")286    try:287        # FastMCP's run method handles the asyncio event loop internally288        mcp.run(transport="stdio")289    except KeyboardInterrupt:290        logger.info("Server interrupted by user (KeyboardInterrupt).")291    except Exception as e:292        logger.critical(293            f"SQL Report MCP Server crashed unexpectedly: {str(e)}", exc_info=True294        )295        # Re-raise to ensure the error is propagated if this is a critical failure296        raise297