Download mcp_database_universal/server.py from Lukynnnn/mcp-database-universal: direct link, hf CLI and curl.
- Browser
- Download file 8.45 kB
-
https://huggingface.co/Lukynnnn/mcp-database-universal/resolve/main/mcp_database_universal/server.py
- Command line
-
hf download hf://Lukynnnn/mcp-database-universal/mcp_database_universal/server.py
-
curl -L -o server.py https://huggingface.co/Lukynnnn/mcp-database-universal/resolve/main/mcp_database_universal/server.py
8.45 kB
| """MCP Database Server — main server with 7 reasoning tools.""" | |
| import json | |
| import sys | |
| import logging | |
| from mcp.server.mcpserver import MCPServer | |
| from mcp_database_universal.config import DatabaseConfig | |
| from mcp_database_universal.engines.base import BaseEngine | |
| from mcp_database_universal.safety import SafetyValidator | |
| from mcp_database_universal.schema_inspector import SchemaInspector | |
| from mcp_database_universal.formatters.llm import LLMFormatter | |
| from mcp_database_universal.nl2sql import translate | |
| from mcp_database_universal import __version__ | |
| logger = logging.getLogger("mcp-db") | |
| async def create_server(config: DatabaseConfig, engine: BaseEngine) -> MCPServer: | |
| server = MCPServer( | |
| name="mcp-database-server", | |
| version=__version__, | |
| ) | |
| safety = SafetyValidator( | |
| read_only=config.is_effectively_read_only(), | |
| max_rows=config.max_rows, | |
| max_query_time=config.max_query_time, | |
| ) | |
| inspector = SchemaInspector(engine) | |
| formatter = LLMFormatter(config) | |
| async def test_connection() -> str: | |
| """ | |
| Test database connection and return basic info. | |
| Use when: you need to verify the connection works, | |
| or find out the DB type, version, and size. | |
| Returns: engine type, version, database name, size. | |
| """ | |
| try: | |
| info = await engine.get_db_info() | |
| tables = await engine.get_tables() | |
| result = formatter.format_db_info(info) | |
| result += f"\n- **Tables:** {len(tables)}" | |
| result += f"\n- **Read-only:** {'yes' if engine.is_read_only() else 'no'}" | |
| return result | |
| except Exception as e: | |
| return f"Connection failed: {str(e)}" | |
| async def list_tables(include_stats: bool = True) -> str: | |
| """ | |
| List all tables in the database with metadata. | |
| Use when: you need an overview of what's in the database, | |
| how many tables exist, and their relationships. | |
| This is the first step when working with an unknown database. | |
| Returns: list of tables with row counts, column counts, FK relationships. | |
| """ | |
| try: | |
| tables = await engine.get_tables() | |
| return formatter.format_table_list(tables) | |
| except Exception as e: | |
| return f"Error listing tables: {str(e)}" | |
| async def inspect_table(table_name: str, include_sample: bool = True, sample_size: int = 5) -> str: | |
| """ | |
| Inspect a table's structure: columns, types, indexes, foreign keys, sample data. | |
| Use when: you need to understand a specific table's structure, | |
| what columns exist, their types, and relationships to other tables. | |
| Returns: complete table overview with context for LLM. | |
| """ | |
| try: | |
| detail = await engine.get_table_detail(table_name) | |
| if not include_sample: | |
| detail.sample_data = [] | |
| return formatter.format_table_detail(detail) | |
| except Exception as e: | |
| return f"Error inspecting table '{table_name}': {str(e)}" | |
| async def query(sql: str, params: str = "{}") -> str: | |
| """ | |
| Execute a safe SQL query and return formatted results. | |
| Use when: you need to run a specific SQL query. | |
| All queries are parametrized and pass through safety checks. | |
| SAFETY: | |
| - Read-only by default: no INSERT/UPDATE/DELETE/DROP allowed | |
| - Parametrized queries: no string formatting | |
| - Max 1000 rows, 30s timeout | |
| Params: JSON dict for parameterized queries. | |
| Example: sql="SELECT * FROM users WHERE id = :id", params='{"id": 42}' | |
| """ | |
| validation = safety.validate(sql) | |
| if not validation.approved: | |
| return f"BLOCKED: {validation.reason}" | |
| try: | |
| params_dict = json.loads(params) if params and params != "{}" else None | |
| except json.JSONDecodeError: | |
| return "BLOCKED: Invalid JSON in params" | |
| safe_sql = safety.ensure_limit(sql) | |
| result = await engine.execute_query(safe_sql, params_dict) | |
| return formatter.format_query_result(result) | |
| async def natural_query(question: str) -> str: | |
| """ | |
| Ask a question in natural language and get SQL + results. | |
| Use when: you don't know the exact SQL, or want a quick answer | |
| about the data. Examples: "How many users have orders?", | |
| "What product sells the best?" | |
| Translation: an LLM (OpenAI/Anthropic) is used when OPENAI_API_KEY or | |
| ANTHROPIC_API_KEY is configured; otherwise a built-in rules-based | |
| parser handles common English question shapes. | |
| Returns: generated SQL + results + explanation. | |
| """ | |
| tables = await engine.get_tables() | |
| table_names = [t.name for t in tables] | |
| translation = await translate( | |
| question, | |
| table_names, | |
| openai_key=config.openai_key, | |
| anthropic_key=config.anthropic_key, | |
| ) | |
| sql = translation.sql | |
| if not sql: | |
| reason = translation.error or "no matching pattern" | |
| source_note = f" (LLM: {reason})" if translation.source == "llm" else "" | |
| return ( | |
| "Could not automatically translate your question to SQL" | |
| + source_note | |
| + ".\n\n" | |
| "Try using the `query` tool directly with SQL, or rephrase your question.\n" | |
| f"Available tables: {', '.join(table_names)}\n\n" | |
| "Examples:\n" | |
| "- 'How many users are there?'\n" | |
| "- 'Show me all orders'\n" | |
| "- 'What products cost more than 100?'" | |
| ) | |
| safe_sql = safety.ensure_limit(sql) | |
| validation = safety.validate(safe_sql) | |
| if not validation.approved: | |
| return f"Generated query was blocked: {validation.reason}" | |
| result = await engine.execute_query(safe_sql) | |
| output = formatter.format_query_result(result) | |
| source_note = "LLM" if translation.source == "llm" else "rules-based" | |
| output = f"**Question:** {question}\n**Generated SQL** ({source_note}): `{safe_sql}`\n\n{output}" | |
| return output | |
| async def profile_database(table_name: str = "") -> str: | |
| """ | |
| Get a complete profile of the database or a specific table. | |
| Use when: you need to understand the data — value distributions, | |
| NULL rates, sizes, relationships. Ideal first step before writing queries. | |
| Without parameter: database overview (table summary, relationships, sizes). | |
| With parameter: detailed table profile (distributions, null rates, top values). | |
| """ | |
| try: | |
| db_info = await engine.get_db_info() | |
| tables = await engine.get_tables() | |
| relationships = await inspector.discover_relationships() | |
| junction_tables = inspector.detect_junction_tables(relationships) | |
| profile_data = { | |
| "db_info": db_info, | |
| "table_count": len(tables), | |
| "tables": tables, | |
| "relationships": relationships, | |
| "junction_tables": junction_tables, | |
| } | |
| if table_name: | |
| table_stats = {} | |
| try: | |
| stats = await engine.get_table_stats(table_name) | |
| table_stats[table_name] = stats | |
| except Exception: | |
| pass | |
| profile_data["table_stats"] = table_stats | |
| return formatter.format_profile(profile_data) | |
| except Exception as e: | |
| return f"Error profiling database: {str(e)}" | |
| async def schema_graph() -> str: | |
| """ | |
| Visualize table relationships as a Mermaid ER diagram. | |
| Use when: you need to see how tables are connected, | |
| which have foreign key relationships, and the overall DB structure. | |
| Returns: Mermaid diagram definition (render in markdown). | |
| """ | |
| try: | |
| relationships = await inspector.discover_relationships() | |
| mermaid = inspector.generate_mermaid(relationships) | |
| return formatter.format_schema_graph(mermaid) | |
| except Exception as e: | |
| return f"Error generating schema graph: {str(e)}" | |
| return server | |