Skip to main content
Glama
idk-arsh
by idk-arsh

schema-guard

Your AI agent stops inventing column names.

Coding agents write SQL against the schema they think you have, based on your README, an old query, or a naming convention. Then it fails in CI, in a dashboard, or at 2am. Snowflake's own developer blog ran a whole post on this in September 2026 (My coding agent won't stop hallucinating table columns).

schema-guard keeps a snapshot of your real tables and columns in the repo (names and types only, no data, no credentials) and checks the agent's SQL against it before it runs or lands in a file:

schema-guard: this SQL names things that are not in the schema snapshot (.schema-guard/schema.json, taken 2026-09-29T01:35:07Z):
- `analytics.customers` has no column `country`. Did you mean: `country_iso2`?
- `analytics.orders` has no column `customer_id`. Did you mean: `cust_id`, `order_id`?
- `analytics.customers` has no column `id`. Did you mean: `cust_id`?
Fix the names and try again. ...

That's a real denial from the eval below: Claude Haiku 4.5 writing models/revenue_by_country.sql from a README that describes last year's schema. The agent reads the denial, fixes its SQL and moves on. You never see the broken version.

Does it help? (measured)

Setup. An analytics repo whose README describes an older schema (customer_id, created_at, country), plus two up-to-date models that use a few of the real names. The agent can read and write files but can't reach the warehouse. That's the situation in Snowflake's post: the agent has the repo, not the account. Each request asks for a new SQL model, and afterwards the grader runs every file the agent wrote against the real DuckDB warehouse. 4 requests × 3 arms × 3 runs, on Claude Code.

Arm

Haiku 4.5: fails on a missing name / runs / correct

Sonnet 5: fails / runs / correct

baseline (repo only)

12 / 0 / 0 of 12

12 / 0 / 0 of 12

rule (snapshot + one line in CLAUDE.md)

0 / 12 / 11 of 12

0 / 12 / 12 of 12

hook (snapshot + hook, no instruction)

0 / 12 / 12 of 12

0 / 12 / 11 of 12

What that means:

  • Without a snapshot, neither model wrote one working file (0 of 24). Both trusted the README, and even when they copied real names from the existing models they mixed them with stale ones.

  • With the snapshot, every file ran (48 of 48). The 2 wrong answers are logic errors, not names: Haiku started weeks on Sunday, and Sonnet counted the last days of 2025 in the first week.

  • The hook is the safety net for agents that don't go looking. Haiku was denied in 10 of its 12 hook runs and fixed the names on the first retry every time. Sonnet found .schema-guard/schema.json by itself and was never denied. A one-line rule gets the same result if the agent follows it; the hook doesn't depend on that, and it also covers ad-hoc queries and MCP tools.

  • Cost: the hook arm cost about the same as baseline ($0.65 vs $0.58 for 12 Haiku runs; $1.61 vs $1.61 for Sonnet).

Be skeptical of this: the world is small and synthetic, and the stale README is designed in (docs drift is normal, but I chose how far). There are 3 runs per cell. Two grader references were added after I read runs: "net revenue" net of refunds (it changed 2 Haiku grades, one rule run and one hook run), and listing all 52 weeks with zeros (5 Sonnet grades). Both are disclosed in evals/scenarios.py, and every run's SQL is in evals/results/. Rerun it: cd evals && python run_eval.py --model <model> --runs 3.

Related MCP server: db-tools-mcp

Install

pip install "schema-guard[duckdb] @ git+https://github.com/idk-arsh/schema-guard"
schema-guard snapshot --dbt target        # or --duckdb, --bigquery, --snowflake, --databricks, --url, --ddl, --csv
git add .schema-guard/schema.json

Then use it however your team works. All of these read the same snapshot:

Where

How

Claude Code (hook)

/plugin marketplace add idk-arsh/schema-guard then /plugin install schema-guard, or schema-guard install to add it to .claude/settings.json

Cursor, Claude Desktop, VS Code, Windsurf (MCP)

{"command": "uvx", "args": ["--from", "git+https://github.com/idk-arsh/schema-guard", "schema-guard-mcp"]}. Tools: list_tables, describe_table, search_columns, check_sql

pre-commit

- repo: https://github.com/idk-arsh/schema-guard / rev: v0.1.0 / hooks: [{id: schema-guard}]

CI

schema-guard check models/ queries/ exits 1 on a missing table or column. schema-guard snapshot --dbt target --check exits 1 if the committed snapshot is out of date

Any agent (AGENTS.md, .cursorrules)

paste rules/schema-guard.md

Where the snapshot comes from

Source

Command

Needs

dbt

--dbt target

dbt docs generate (catalog.json). With only manifest.json, tables are checked but columns aren't

DuckDB / SQLite

--duckdb wh.duckdb / --sqlite app.db

nothing

Postgres, MySQL, Redshift ...

--url postgresql://...

sqlalchemy + driver

BigQuery

--bigquery my-project.my_dataset (or region-us)

the bq CLI; INFORMATION_SCHEMA queries are free

Snowflake

--snowflake MY_DB [--connection name]

snowflake-connector-python, ~/.snowflake/connections.toml

Databricks

--databricks my_catalog

databricks-sql-connector, DATABRICKS_HOST / _HTTP_PATH / _TOKEN

Migrations or a schema dump

--ddl migrations/

nothing; CREATE / ALTER / DROP applied in file order

Anything else

--csv columns.csv

an export of information_schema.columns

Several files in .schema-guard/ are merged, so one repo can cover more than one warehouse. The person taking the snapshot needs warehouse access once; the agent never does.

Live on Databricks (Free Edition, 2026-10-04)

I ran the Databricks reader against a fresh Databricks Free Edition workspace to prove the Unity Catalog path works end to end.

$env:DATABRICKS_HOST = "<workspace>.cloud.databricks.com"
$env:DATABRICKS_HTTP_PATH = "/sql/1.0/warehouses/<id>"
$env:DATABRICKS_TOKEN = "<token>"

python -m schema_guard.cli snapshot --databricks samples -o .schema-guard/databricks-samples.json
# wrote .schema-guard\databricks-samples.json: 9 tables, 277 columns, dialect databricks

python -m schema_guard.cli check "SELECT customerid, first_name FROM samples.bakehouse.sales_customers LIMIT 10"
# (silent: passes)

python -m schema_guard.cli check "SELECT customer_id, first_name FROM samples.bakehouse.sales_customers LIMIT 10"
# <sql>: `bakehouse.sales_customers` has no column `customer_id`. Did you mean: `customerid`?

The snapshot came back in under 30 seconds on a cold warehouse. The reader pulls from information_schema.columns, so any Unity Catalog you can read works the same way.

What it checks

  • Shell commands: bq query, snowsql, snow sql, psql, duckdb, sqlite3, databricks, spark-sql, mysql, trino, SQL passed to scripts (python run_sql.py "...", python -c "...sql..."), heredocs and -f file.sql.

  • Files: every .sql the agent writes or edits. dbt {{ ref() }} and {{ source() }} are resolved to real tables. On an edit, only problems the edit adds are reported, so old debt in a file doesn't block new work.

  • MCP tools: any tool with a sql / query / statement argument (Snowflake, Databricks, BigQuery, Postgres MCP servers).

  • Resolution: CTEs, subqueries, correlated subqueries, aliases, USING, set operations, CTAS and temp tables created earlier in the same script, INSERT column lists, UPDATE SET, DELETE WHERE. Parsing is by sqlglot, so 20+ dialects.

When it stays quiet

A false block costs more trust than a missed one, so it says nothing when it can't be sure:

  • SQL it can't parse, Jinja beyond ref/source/config, sources it can't see into (UNNEST, LATERAL, table functions, PIVOT), SELECT * from a table it doesn't know, struct and JSON field access.

  • Tables from a database the snapshot doesn't cover (unless the name is a near miss of one it does).

  • Stale snapshot: if the agent sends the exact same SQL again after a denial, it goes through. A new column can slow the agent down once but never lock it out. Refresh with schema-guard snapshot, and put --check in CI.

False blocks (measured, no LLM)

A guard that blocks valid SQL gets uninstalled, so this matters more than the catch rate.

Corpus

Valid queries

False blocks

Planted wrong names caught

Spider dev, 20 databases (held out: never looked at while building)

1,034

0

1,032 / 1,034

defog sql-eval, 7 databases × Postgres, BigQuery, Snowflake, MySQL, SQLite

960

0

959 / 960

Every valid query is human-written gold SQL that runs on its database, so any finding would be a false block. The planted mistakes swap one real name for a wrong one the way agents get it wrong (a column from another table, _id / plural / _name variants, singular vs plural table names). I fixed 3 checker bugs that defog exposed, so treat its numbers as training numbers; Spider is the honest one. Its 2 misses are inside correlated subqueries, where the checker deliberately gives the benefit of the doubt. Run them: python evals/benchmark_spider.py, python evals/benchmark_defog.py (needs pip install defog-data).

Limits

  • It checks names, not meaning. SUM(gross_amount) when you wanted net_amount passes.

  • Dynamic SQL built from string pieces in application code isn't seen.

  • The snapshot is only as fresh as the last schema-guard snapshot.

  • The Snowflake and BigQuery snapshot readers are unit-tested on their output format, not yet run against live accounts. The Databricks reader was run live on Free Edition on 2026-10-04 (see below). If you run one, tell me how it went.

Part of a set of small, measured tools for AI agents working on data: data-agent-rules (rules + safety hooks, cost checks, masked previews), show-your-sql (every number in the answer traced to a query result), data-test-guard (agents can't delete or loosen tests to go green).

MIT licensed. M8ven Score Using it? Open a "We use this" issue or add a line to ADOPTERS.md. False blocks are the bug I most want to hear about.

Available Tools

4 tools
check_sqlA

Tables and columns in sql that don't exist in the snapshot, with close matches. Does not run the query.

dialect: bigquery, snowflake, databricks, postgres, duckdb ... (default: the snapshot's).

ParametersJSON Schema
NameRequiredDescriptionDefault
sqlYes
dialectNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.7/5.0
Behavior3/5

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

With no annotations, the description carries the full burden. It usefully discloses that the query is not executed and that output includes close matches for unknown identifiers, but it says nothing about snapshot requirements, whether it is read-only by construction, cost/latency, or handling of otherwise-valid-but-unresolvable references.

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?

Front-loaded with the core behavior and result shape, then the modifier about non-execution, then parameter guidance. Two short blocks with no filler, though the trailing ellipsis in the dialect list is slightly sloppy.

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

Completeness5/5

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

An output schema exists, so return values need not be described, and the description covers the essentials for a 2-parameter validation tool: what it reports, that it does not execute, and how dialect resolution works. Nothing needed to invoke it correctly is missing.

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 description coverage is 0%, so the description must compensate. It does so well for 'dialect', listing accepted values (bigquery, snowflake, databricks, postgres, duckdb) and the default of inheriting the snapshot's dialect, which is entirely absent from the schema. The 'sql' parameter needs no further explanation beyond its name.

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

Purpose4/5

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

States the resource it inspects (tables and columns in sql not present in the snapshot) and the form of the result (close matches), and explicitly rules out execution. The verb 'check' is in the name but not restated, and it distinguishes itself from siblings like describe_table/search_columns by operating on a whole query rather than enumerating schema.

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 line 'Does not run the query' implies this is for static validation before executing, which is useful context, but the description never states when to use this versus list_tables, describe_table, or search_columns, nor any prerequisites for having a snapshot loaded.

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

describe_tableB

Columns and types of one table. table: name, schema.name or catalog.schema.name.

ParametersJSON Schema
NameRequiredDescriptionDefault
tableYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

B3.2/5.0
Behavior2/5

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

No annotations are provided, so the description carries the full disclosure burden. It implies a read (returns columns/types) but says nothing about read-only safety, permission needs, or behavior when the table does not exist.

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 compact sentences, front-loaded with the purpose and followed by the one parameter's syntax. Zero filler.

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

Completeness3/5

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

An output schema exists, so return values need not be explained, and the single parameter's format is covered. However, for a tool with no annotations, the missing usage/routing guidance and error behavior leave clear gaps.

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 0%, so the description must compensate, and it does: it spells out the accepted qualified-name forms (name, schema.name, catalog.schema.name). This is genuinely useful format guidance the schema does not provide.

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

Purpose4/5

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

States a specific resource and what is returned ('Columns and types of one table'), which cleanly distinguishes it from siblings list_tables (enumerates tables) and search_columns (searches across columns). It lacks an explicit verb but the operation is unambiguous.

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

Usage Guidelines2/5

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

No when-to-use guidance, no mention of alternatives such as search_columns for finding where a column lives, and no prerequisites. The agent must infer the routing from the sibling names alone.

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

list_tablesC

Tables in the warehouse snapshot. filter: part of a table or schema name.

ParametersJSON Schema
NameRequiredDescriptionDefault
filterNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

C2.6/5.0
Behavior2/5

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

With no annotations, the description carries the full behavioral burden, yet says nothing about read-only semantics, ordering, pagination, or whether unfiltered calls return everything. The read-only nature is only implied by the word 'list'/'Tables'.

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

Conciseness3/5

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

Two short front-loaded fragments are efficient but tip into under-specification rather than crisp conciseness. Nothing is wasted, but too little is said for the space available.

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

Completeness3/5

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

An output schema exists, so return values need not be explained, and the single optional parameter is described. However, for a tool with no annotations, the absence of any read-only or scoping/behavioral context leaves the definition only minimally complete.

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

Parameters3/5

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

Schema description coverage is 0%, so the description must compensate, and it partially does: 'filter: part of a table or schema name' explains what the single optional parameter matches, including that it spans both table and schema names. That is genuinely more than the bare schema provides, though it lacks format or matching-behavior specifics.

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

Purpose3/5

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

The fragment 'Tables in the warehouse snapshot' identifies the resource but states no verb, relying on the tool name list_tables to convey the action. It does not distinguish this listing tool from siblings describe_table or search_columns, so an agent gets the gist but no explicit differentiation.

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

Usage Guidelines2/5

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

There is no when-to-use guidance, no mention of alternatives (e.g., describe_table for a single table's detail, search_columns for column lookups), and no exclusions. The agent must infer placement among the siblings entirely on its own.

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

search_columnsB

Columns whose name (or table name) contains term, e.g. "revenue", "email", "created".

ParametersJSON Schema
NameRequiredDescriptionDefault
termYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

B3.1/5.0
Behavior2/5

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

No annotations exist, so the description carries the full behavioral burden. It discloses substring matching over column and table names, but omits case sensitivity, result limits/pagination, and any read-only/no-side-effect confirmation beyond what the tool name implies.

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?

Single, tightly worded sentence that front-loads the matching scope and examples. It is a fragment rather than a full sentence, but no words are wasted.

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

Completeness3/5

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

For a simple one-parameter search tool with an output schema, the description identifies what is searched but offers no guidance on choosing it over list_tables/describe_table and no behavioral limits. It is minimally adequate but incomplete for an agent deciding among siblings.

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 description coverage is 0% and the only parameter, term, is barely named. The description compensates by defining term as a substring over column name or table name and providing examples, which gives usable semantic meaning beyond the schema type.

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

Purpose4/5

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

States the resource (columns) and the matching criterion (name or table name contains term) with examples, which is more specific than a bare name. It does not explicitly route against siblings like list_tables or describe_table, but the resource is unambiguous.

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

Usage Guidelines2/5

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

No explicit when-to-use instructions, no alternatives named, no exclusions. Usage is only implied by the description of what is searched; an agent is left to infer when this beats list_tables or describe_table.

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. 4 tool updatesv0.1.0
    • First observedcheck_sql
    • First observeddescribe_table
    • First observedlist_tables
    • First observedsearch_columns

TDQS

A3.5/5.0

Scored across 4 tools

Disambiguation4/5

list_tables and search_columns can both surface table-name matches, creating slight overlap, but describe_table and check_sql are clearly distinct. Descriptions help clarify intent, so misselection is unlikely.

Naming Consistency5/5

All four tools follow a consistent snake_case verb_noun pattern: list_tables, describe_table, search_columns, and check_sql. There are no mixed conventions or vague verb styles.

Tool Count5/5

Four tools is well-scoped for a schema exploration and SQL validation server. Each tool has a clear role, and the set avoids both bloat and thinness.

Completeness5/5

The set covers the core schema-guard workflow: discovering tables, inspecting columns, searching columns, and validating SQL against the snapshot. No obvious lifecycle gaps exist for the stated purpose.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Exposes SQL Server and Snowflake schema metadata to AI coding agents, enabling schema search, join path discovery, and stored procedure metadata retrieval without live queries.
    18
    GPL 3.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.
    36
    BSD 2-Clause "Simplified"