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
| tool | input | output | price (USD) |
|---|---|---|---|
run_sql | schema (DDL), query, seed? (JSON rows), engine? | rows (≤500, truncation flagged), columns+types, row_count, execution_ms, empty_dataset flag, dialect_notes | $0.005 |
validate_sql | schema, query, engine? | ok / structured error (type, message, position, suggestion), referenced_tables, missing_tables | $0.003 |
explain_plan | schema, query, engine? | engine-native plan, full-scan warnings | $0.003 |
run_sql_batch | schema, queries (≤10), seed? | array of run_sql results — each query runs in its OWN fresh database | $0.02 |
diff_results | schema, seed?, query_a, query_b | equal? (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
- Fresh in-memory database per call, destroyed after. No cross-call state, ever.
- One statement per query slot — stacked statements are rejected (
run_sql_batchexists for multiples). - Limits: 5s CPU, 10 MB seed data, 256 KB schema, 64 KB query, 500 rows returned (flagged when truncated).
- No seed rows → queries run against empty tables and the response sets
empty_dataset: true(zero rows means "no data", not "your logic is wrong"). - Time-dependent SQL (
now(),CURRENT_TIMESTAMP,random()) is flagged viadialect_notes; the call timestamp is reported inmeta.now. - Responses are strict JSON with a
versionfield (currently"1").
Payment flow (x402)
- Call without payment → free-tier if quota remains, else HTTP 402 with
acceptspayment requirements. - Retry with
X-PAYMENTheader → verified and settled via facilitator → tool executes. - 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