Skip to main content
Glama
andyWang1688

sql-query-mcp

explain_query

Run EXPLAIN on read-only SELECT or CTE queries to analyze execution plans. Optionally enable ANALYZE for detailed performance insights.

Instructions

Run EXPLAIN on a read-only SELECT or CTE query.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
connection_idYes
sqlYes
analyzeNo

Implementation Reference

  • Registration of 'explain_query' as an MCP tool via @mcp.tool() decorator. Delegates to executor.explain_query().
    @mcp.tool()
    def explain_query(connection_id: str, sql: str, analyze: bool = False) -> dict:
        """Run EXPLAIN on a read-only SELECT or CTE query."""
    
        return _run_tool(lambda: executor.explain_query(connection_id, sql, analyze))
  • Core handler that validates SQL, builds EXPLAIN via adapter, executes it, extracts plan, and returns results with audit logging.
    def explain_query(
        self, connection_id: str, sql_text: str, analyze: bool = False
    ) -> Dict[str, object]:
        started = time.perf_counter()
        config = None
        try:
            config = self._registry.get_connection_config(connection_id)
            cleaned_sql = validate_select_sql(sql_text, config.engine)
            sql_summary = summarize_sql(cleaned_sql)
            adapter = self._registry.get_adapter(config)
            explain_sql = adapter.build_explain_query(cleaned_sql, analyze=analyze)
            with self._registry.connection_from_config(config) as (conn, adapter):
                _apply_statement_timeout(
                    adapter, conn, self._settings.statement_timeout_ms
                )
                with conn.cursor() as cur:
                    cur.execute(explain_sql)
                    rows = cur.fetchall()
    
                duration_ms = _elapsed_ms(started)
                plan = adapter.extract_plan(rows)
                self._audit.log(
                    tool="explain_query",
                    connection_id=connection_id,
                    success=True,
                    duration_ms=duration_ms,
                    row_count=1 if rows else 0,
                    sql_summary=sql_summary,
                    extra={"engine": config.engine, "analyze": analyze},
                )
                return {
                    "connection_id": connection_id,
                    "engine": config.engine,
                    "plan": plan,
                    "duration_ms": duration_ms,
                    "analyze": analyze,
                }
        except Exception as exc:
            duration_ms = _elapsed_ms(started)
            sql_summary = summarize_sql(sql_text)
            sanitized = sanitize_error_message(str(exc))
            self._audit.log(
                tool="explain_query",
                connection_id=connection_id,
                success=False,
                duration_ms=duration_ms,
                sql_summary=sql_summary,
                error=sanitized,
                extra=_build_audit_extra(config, analyze=analyze),
            )
            raise QueryExecutionError(sanitized) from exc
  • Validation function that ensures the SQL is a SELECT/WITH query and rejects raw EXPLAIN statements (which would conflict with the tool).
    def validate_select_sql(sql: str, engine: str) -> str:
        cleaned = _clean_sql(sql)
        lowered = cleaned.lstrip().lower()
        if lowered.startswith("explain"):
            raise SecurityError(
                "explain_query 会自动包装 SQL,请直接传 SELECT 或 WITH 查询。"
            )
        if not (lowered.startswith("select") or lowered.startswith("with")):
            raise SecurityError("仅允许 SELECT 或 WITH ... SELECT 语句。")
        statement = _parse_statement(cleaned, engine)
        _ensure_read_only_statement(statement)
        return cleaned
  • Postgres adapter: builds EXPLAIN (FORMAT JSON, ANALYZE ...) SQL.
    def build_explain_query(self, sql_text: str, analyze: bool = False) -> str:
        return f"EXPLAIN (FORMAT JSON, ANALYZE {'TRUE' if analyze else 'FALSE'}) {sql_text}"
  • Core handler that validates SQL, builds EXPLAIN via adapter, executes it, extracts plan, and returns results with audit logging.
    def explain_query(
        self, connection_id: str, sql_text: str, analyze: bool = False
    ) -> Dict[str, object]:
        started = time.perf_counter()
        config = None
        try:
            config = self._registry.get_connection_config(connection_id)
            cleaned_sql = validate_select_sql(sql_text, config.engine)
            sql_summary = summarize_sql(cleaned_sql)
            adapter = self._registry.get_adapter(config)
            explain_sql = adapter.build_explain_query(cleaned_sql, analyze=analyze)
            with self._registry.connection_from_config(config) as (conn, adapter):
                _apply_statement_timeout(
                    adapter, conn, self._settings.statement_timeout_ms
                )
                with conn.cursor() as cur:
                    cur.execute(explain_sql)
                    rows = cur.fetchall()
    
                duration_ms = _elapsed_ms(started)
                plan = adapter.extract_plan(rows)
                self._audit.log(
                    tool="explain_query",
                    connection_id=connection_id,
                    success=True,
                    duration_ms=duration_ms,
                    row_count=1 if rows else 0,
                    sql_summary=sql_summary,
                    extra={"engine": config.engine, "analyze": analyze},
                )
                return {
                    "connection_id": connection_id,
                    "engine": config.engine,
                    "plan": plan,
                    "duration_ms": duration_ms,
                    "analyze": analyze,
                }
        except Exception as exc:
            duration_ms = _elapsed_ms(started)
            sql_summary = summarize_sql(sql_text)
            sanitized = sanitize_error_message(str(exc))
            self._audit.log(
                tool="explain_query",
                connection_id=connection_id,
                success=False,
                duration_ms=duration_ms,
                sql_summary=sql_summary,
                error=sanitized,
                extra=_build_audit_extra(config, analyze=analyze),
            )
            raise QueryExecutionError(sanitized) from exc
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full burden. It correctly indicates a read-only operation via 'EXPLAIN' and 'read-only', but does not detail side effects, permissions, or return value.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence, efficient and front-loaded, but lacks structure like bullet points that could improve readability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, no annotations, and 0% parameter coverage, the description is incomplete; it does not explain what EXPLAIN returns, how analyze works, or the role of connection_id.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, and the description adds no information about parameters such as connection_id, sql, or analyze, failing to compensate for the lack of schema details.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Run EXPLAIN' and the resource 'read-only SELECT or CTE query', distinguishing it from siblings like run_select and start_query.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies the tool is for read-only queries but does not explicitly state when to use it over alternatives like get_query or run_select, nor does it provide exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/andyWang1688/sql-query-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server