Skip to main content
Glama

SQL Check

CI npm downloads OpenSSF Scorecard License: MIT

Agents write SQL for databases they cannot reach, so they guess: whether PostgreSQL accepts a GROUP BY, what a column is called, whether DATE_SUB exists, what round(2.5) returns. SQL Check stops the guessing. It runs your SQL on the real engines, in memory: PostgreSQL 18 (PGlite, the PostgreSQL server compiled to WebAssembly) and SQLite 3.49 (sql.js). No database server, no network.

  • Your schema first: CREATE TABLE statements, migrations, sample rows. Then each statement gets the engine's own verdict: the error with its line and column and PostgreSQL's hint ("Perhaps you meant to reference the column users.email"), the constraint it violates with the failing row, or the rows it returns.

  • Every statement is checked, not just the first: each runs in its own savepoint inside one transaction that is rolled back, so an error does not hide the next one and nothing persists.

  • SQL from other databases: when MySQL or SQL Server syntax fails (backticks, DATE_SUB, LIMIT 0, 10, AUTO_INCREMENT, TOP n), the error says what the engine accepts instead.

  • Portability: dialect: both runs the SQL on PostgreSQL and SQLite and lists where they disagree, including the quiet differences: SQLite fills an INTEGER PRIMARY KEY by itself and does not enforce foreign keys unless asked; PostgreSQL does neither and does.

No key needed.

Built and maintained by Arhan Canli.

Install

Install in Cursor Install in VS Code Install in Goose

Needs Node.js 20 or newer. No account or key.

Claude Code

claude mcp add sql-check -- npx -y sql-check-mcp

Claude Desktop: download sql-check-mcp-<version>.mcpb from the latest release and open it. The bundle is signed; verify it with gh attestation verify <file> --repo arhancanli/sql-check-mcp.

Any other client (Windsurf, Zed, Cline, Continue and others), in its MCP config file:

{
  "mcpServers": {
    "sql-check": {
      "command": "npx",
      "args": [
        "-y",
        "sql-check-mcp"
      ]
    }
  }
}

Docker

docker build -t sql-check-mcp https://github.com/arhancanli/sql-check-mcp.git && docker run -i --rm sql-check-mcp

Hosted (Streamable HTTP): node src/server.mjs --http serves stateless MCP at POST /mcp (port from PORT, default 3000).

Related MCP server: SQLike

Example

An agent calls check_sql with:

{
  "schema": "CREATE TABLE users (\n  id serial PRIMARY KEY,\n  email text NOT NULL UNIQUE,\n  created_at timestamptz DEFAULT now()\n);\nCREATE TABLE orders (\n  id serial PRIMARY KEY,\n  user_id int NOT NULL REFERENCES users(id),\n  total numeric(10,2) NOT NULL,\n  status text NOT NULL CHECK (status IN ('open', 'paid'))\n);\nINSERT INTO users (email) VALUES ('ana@example.com'), ('ben@example.com');\nINSERT INTO orders (user_id, total, status) VALUES (1, 10.50, 'open'), (1, 3.00, 'paid');\n",
  "sql": "-- spend per user\nSELECT u.email, sum(o.total) AS spent\nFROM users u LEFT JOIN orders o ON o.user_id = u.id\nGROUP BY u.email\nORDER BY u.email;\nSELECT u.email, o.total FROM users u JOIN orders o ON o.user_id = u.id GROUP BY u.email;\nSELECT emial FROM users;\nSELECT * FROM orders WHERE placed_at > DATE_SUB(NOW(), INTERVAL 7 DAY);\nINSERT INTO orders (user_id, total, status) VALUES (1, 5.00, 'shipped');\nINSERT INTO orders (user_id, total, status) VALUES (9, 5.00, 'open');\nUPDATE orders SET status = 'paid' WHERE status = 'open' RETURNING id, status;\n"
}

and gets back (recorded from the live server on 2026-09-27):

{
  "results": [
    {
      "dialect": "postgres",
      "version": "PostgreSQL 18.3",
      "counts": {
        "ok": 2,
        "error": 5
      },
      "statements": [
        {
          "line": 2,
          "sql": "SELECT u.email, sum(o.total) AS spent FROM users u LEFT JOIN orders o ON o.user_id = u.id GROUP B...",
          "status": "ok",
          "command": "SELECT",
          "columns": [
            "email text",
            "spent numeric"
          ],
          "row_count": 2,
          "rows": [
            [
              "ana@example.com",
              "13.50"
            ],
            [
              "ben@example.com",
              null
            ]
          ]
        },
        {
          "line": 6,
          "sql": "SELECT u.email, o.total FROM users u JOIN orders o ON o.user_id = u.id GROUP BY u.email",
          "status": "error",
          "column": 17,
          "error": "column \"o.total\" must appear in the GROUP BY clause or be used in an aggregate function",
          "code": "42803"
        },
        {
          "line": 7,
          "sql": "SELECT emial FROM users",
          "status": "error",
          "column": 8,
          "error": "column \"emial\" does not exist",
          "hint": "Perhaps you meant to reference the column \"users.email\".",
          "code": "42703"
        },
        {
          "line": 8,
          "sql": "SELECT * FROM orders WHERE placed_at > DATE_SUB(NOW(), INTERVAL 7 DAY)",
          "status": "error",
          "column": 65,
          "error": "syntax error at or near \"7\"",
          "code": "42601",
          "advice": "DATE_SUB/DATE_ADD are MySQL; PostgreSQL writes now() - interval '1 day'"
        },
        {
          "line": 9,
          "sql": "INSERT INTO orders (user_id, total, status) VALUES (1, 5.00, 'shipped')",
... (35 more lines)

Tools

Tool

What it does

check_sql

Runs SQL on real PostgreSQL 18 or SQLite 3.49 in memory, after your schema (DDL, migrations, sample INSERTs). Each statement gets the engine's verdict: error with line, column and hint, constraint violations, or the rows it returns (up to max_rows). All inside a rolled-back transaction. dialect both compares the two.

How it behaves

  • Nothing leaves your machine: the engines run inside this process, in memory, with in-memory file systems; there is no network access at all (factory.allowHosts is empty).

  • Each call starts from an empty database. PostgreSQL runs every statement in its own savepoint inside one transaction that is rolled back, then discards the session; SQLite gets a new database per call. Statements that would end that transaction (BEGIN, COMMIT, ROLLBACK) are skipped and say so.

  • The engines run in a worker thread with a time limit (10 s per call, SQL_CHECK_TIMEOUT_MS to change it): a statement that never ends is stopped, reported with its number, and the engine is replaced. They start in the background when the server starts, so the first call does not wait for PostgreSQL to boot.

  • Results are compact JSON with a matching output schema: up to max_rows rows per statement (default 20, with the full count), long values cut at 200 characters, bigints as strings.

Benchmark

Not yet measured.

Performance

Measured 2026-09-27 from Dubai, home connection against the live upstream, Node 24.19.0 (bench/perf.json, scripts/perf.mjs in the factory).

Call

First call

Repeat

Result size

check_sql: eight statements against a schema with sample rows, on PostgreSQL

827 ms

7.7 ms

1,855 chars

check_sql: the same SQL on PostgreSQL and SQLite, compared

825 ms

4.1 ms

1,918 chars

check_sql: a migration step that cannot run inside a transaction

892 ms

2.9 ms

692 chars

First call: a fresh server process, including the TLS connection and the upstream's own time. Repeat: the same call again, answered from the in-process cache, so it shows this server's own overhead.

Tool definitions the model reads on every turn (name, description, input schema): 800 characters. The full tool list, with the output schemas and annotations clients use to validate results, is 1,357 characters.

More MCP servers by Arhan Canli

  • Actions Check: Checks GitHub Actions workflows: outdated actions, old Node runtimes, retired runners, injection.

  • Config Check: Validates config files against their official schemas: tsconfig, compose, workflows, 1,400+ more.

  • Cron Check: Explains cron expressions, lists next run times in any time zone, converts between cron dialects.

  • Dockerfile Check: Checks Dockerfiles: build-breaking mistakes, base image tags that exist, digests, platforms, EOL.

  • Domain Health: Email and domain checks: SPF lookup limits, DKIM keys, DMARC, DNS records, registration expiry.

  • End of Life: Is this version still supported? EOL dates, latest patch and upgrade target for 470+ products.

  • Internet Standards: RFC sections, status, obsoleted-by chains, errata and IANA registries for coding agents.

  • Kube Check: Checks Kubernetes manifests for your version: removed APIs, unknown fields, Pod Security, risks.

  • The whole collection, 12 more

License

MIT, Copyright (c) 2026 Arhan Canli.

Available Tools

1 tool
check_sqlRun SQL on real PostgreSQL or SQLiteA
Read-onlyIdempotent

Runs SQL on real PostgreSQL 18 or SQLite 3.49 in memory, after your schema (DDL, migrations, sample INSERTs). Each statement gets the engine's verdict: error with line, column and hint, constraint violations, or the rows it returns (up to max_rows). All inside a rolled-back transaction. dialect both compares the two.

ParametersJSON Schema
NameRequiredDescriptionDefault
sqlYesthe statements to check
schemaNoCREATE TABLE statements, migrations and sample rows, run first
dialectNodefault postgres
max_rowsNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYes

TDQS

A4.2/5.0
Behavior4/5

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

Annotations already declare readOnly/idempotent/non-destructive, and the description adds real context beyond them: everything runs 'inside a rolled-back transaction' (no persistent side effects), the engines run in memory, and each statement returns a verdict with line/column/hint or constraint violations. Only the max_rows truncation behavior is mentioned without a default.

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

Conciseness5/5

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

Two dense sentences with zero filler. The primary behavior (what it runs on, on what engines) is front-loaded, followed by execution semantics and the dialect comparison.

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

Completeness4/5

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

With annotations and an output schema present, the description needn't explain return values, and it covers scope, transaction safety, and error reporting. It omits only secondary details like the max_rows default and dialect default, which the schema partially covers.

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

Parameters4/5

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

Schema coverage is 75% and the schema already documents dialect's enum and max_rows' bounds. The description nonetheless adds meaning: 'schema' is described as DDL/migrations/sample rows run first, and 'dialect both compares the two' explains the third enum value's semantics beyond the raw enum list.

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?

Specific verb plus resource with concrete engine versions: 'Runs SQL on real PostgreSQL 18 or SQLite 3.49 in memory.' It immediately conveys the validation/execution purpose and that the schema is applied first, so an agent knows exactly what the tool does.

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?

Usage is implied rather than stated: the description frames the tool as running statements after a schema (DDL/migrations/sample INSERTs) and hints that 'dialect both compares the two.' There are no siblings to disambiguate against and no explicit when-not guidance, so this sits at implied-usage level.

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

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev0.1.0
    • First observedcheck_sql

TDQS

A4.2/5.0

Scored across 1 tool

Disambiguation5/5

There is only one tool, so there is no possibility of selecting the wrong one. Its purpose (validating/running SQL statements against real engines) is unambiguous.

Naming Consistency5/5

The single name check_sql follows a clear verb_noun convention. No mixed conventions are present since there is only one tool.

Tool Count3/5

A single tool is thin against the typical 3-15 range, though the server's scope (execute and report on SQL) is narrow enough that one entry point is defensible. It packs multiple modes (dialect comparison, max_rows, rollback) into one surface.

Completeness4/5

For a stateless SQL execution/validation service the core operation is covered, including error reporting, constraints, and dialect comparison. Minor extras like EXPLAIN/plan output or schema-diff tooling are absent but not essential.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    Not graded
    maintenance
    Analyzes SQL queries for performance issues, provides optimization suggestions with automated rewriting, and recommends indexes across multiple database dialects (PostgreSQL, MySQL, Oracle, SQL Server).
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Deterministic SQL static analysis and query-equivalence checking for Postgres, MySQL, SQLite, and SQL Server. Tokenizes locally before forwarding to the API.
    6
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Gives AI coding assistants, IDEs, and CI full PostgreSQL schema intelligence from an offline snapshot, enabling linting, query validation, migration safety analysis, and foreign key graph exploration without ever exposing database credentials.
    35
    BSD 2-Clause "Simplified"
  • A
    license
    A
    quality
    D
    maintenance
    Enables AI agents to format SQL, explain queries in plain English, analyze schemas, build queries from natural language, and generate migrations, all without requiring a database connection.
    5
    29 npm
    MIT