MCP SQL verification endpoint

sqlai.dev executes SQL against a real, ephemeral, in-memory database and returns ground truth: rows, typed errors, query plans, or result-set diffs. Nothing is generated by an LLM — the response is what the engine actually did. Every call gets a fresh sandbox that is destroyed afterward; submitted schemas, queries, and data are never stored.

Endpoint

https://mcp.sqlai.dev/mcp          (MCP — Streamable HTTP, stateless)
https://mcp.sqlai.dev/api/<tool>   (plain HTTP POST — same tools, JSON in/out)

Add to an MCP client:

{
  "mcpServers": {
    "sql-verifier": { "type": "http", "url": "https://mcp.sqlai.dev/mcp" }
  }
}

Tools & prices

toolinputoutputprice (USD)
run_sqlschema (DDL), query, seed? (JSON rows), engine?rows (≤500, truncation flagged), columns+types, row_count, execution_ms, empty_dataset flag, dialect_notes$0.005
validate_sqlschema, query, engine?ok / structured error (type, message, position, suggestion), referenced_tables, missing_tables$0.003
explain_planschema, query, engine?engine-native plan, full-scan warnings$0.003
run_sql_batchschema, queries (≤10), seed?array of run_sql results — each query runs in its OWN fresh database$0.02
diff_resultsschema, seed?, query_a, query_bequal? (order-insensitive), order_differs, row-level diff (capped), row counts$0.008

Free tier

20 calls per client per UTC day are free, then requests return HTTP 402 with x402 payment requirements. Payment is per call via x402 (USDC). No account, no API key, no subscription.

Engines — honest scope

Server-side verification runs on SQLite (WASM, same build as the browser demo). DuckDB runs in the browser demo only for now: the DuckDB-WASM binary (~9 MB gzip) exceeds Cloudflare Worker size limits. Queries are validated against SQLite semantics — the dialect_notes field flags constructs that behave differently on Postgres, MySQL, DuckDB, or SQL Server (integer division, NULL ordering, LIKE case sensitivity, quoted identifiers, …). This service does not execute native Postgres or MySQL.

Error taxonomy

Engine errors map to a stable, machine-usable structure — branch on error.type, not message text:

{
  "ok": false,
  "error": {
    "type": "unknown_column" | "unknown_table" | "syntax" | "type_mismatch"
          | "constraint" | "timeout" | "resource_limit" | "unsupported",
    "message": "no such column: oi.price",
    "position": { "line": 1, "col": 8 } | null,
    "suggestion": "did you mean \"unit_price\" (table \"order_items\")?" | null
  }
}

suggestion is rule-based (edit distance / containment against your schema, cross-dialect function maps) — never an LLM.

Execution semantics & limits

Payment flow (x402)

  1. Call without payment → free-tier if quota remains, else HTTP 402 with accepts payment requirements.
  2. Retry with X-PAYMENT header → verified and settled via facilitator → tool executes.
  3. A query that fails after payment is a valid product outcome — the error IS the answer. No refunds for query errors. Our infrastructure faults (5xx, our-side timeout) are not charged.

Plain HTTP (no MCP client)

curl -X POST https://mcp.sqlai.dev/api/run_sql \
  -H "content-type: application/json" \
  -d '{
    "schema": "CREATE TABLE t (id INTEGER, name TEXT);",
    "query": "SELECT name FROM t WHERE id = 1",
    "seed": { "t": [{ "id": 1, "name": "ada" }] }
  }'

Questions: avadayjohnson@gmail.com