Skip to main content
Glama
aniJani

asbuilt

by aniJani

asbuilt

Your code is the plan. Your backend is the as-built. asbuilt checks them against each other.

In July 2025, Replit's coding agent deleted a production database for ~1,200 executives during a code freeze. In April 2026, a Cursor agent wiped a startup's production database and its backups with a single API call. Both incidents share one root cause: coding agents act on what they assume the backend looks like, because they cannot see what it actually looks like.

asbuilt gives agents (and you) that missing sense — read-only, by construction.

What it does

asbuilt parses your repo, extracts every assumption the code makes about its backend — ORM tables, Prisma schemas, raw SQL strings, Supabase calls, storage buckets, deployed functions, auth providers, env vars — and verifies each one against the live backend, with file:line provenance:

$ asbuilt check prod
target: prod   risk: prod
  ❌ fail        table_exists(table=invoices)  [src/db.ts:10]
  ✅ pass        table_exists(table=orders)  [src/db.ts:6]
  ⏭️  skipped     dynamic argument  [src/db.ts:14]
  note: RLS intent is not derived from code; verify explicitly with rls_enabled / policy_exists assertions.
  note: 2 app-level env references were not checked — no connector exists for the application host.
summary: 1 pass, 1 fail, 0 unsupported, 1 skipped

In construction, the as-built drawings record what was actually built, as opposed to what the plans intended. Your code is the plan; your live backend is the as-built. Divergence between them is exactly where "the agent said done but production disagrees" lives.

Related MCP server: Kontrol Freek

Install

Not on PyPI yet — install straight from source. As an MCP server for your coding agent (Claude Code shown; any MCP client works):

claude mcp add asbuilt -- uvx --from git+https://github.com/aniJani/asbuilt asbuilt

Or classic:

pip install git+https://github.com/aniJani/asbuilt
claude mcp add asbuilt -- asbuilt

Once published to PyPI, these will work too:

claude mcp add asbuilt -- uvx asbuilt
# or
pip install asbuilt
claude mcp add asbuilt -- asbuilt

Try it without a backend

No database, no MCP client, no credentials required — the built-in fixture connector serves a recorded state file instead of a live backend, so you can see the whole loop in three files.

mkdir -p demo/.asbuilt demo/src

demo/.asbuilt/targets.json — a target backed by a local JSON file instead of a real connection:

{"targets": {"demo": {"connector": "fixture", "statePath": "state.json", "risk": "dev"}}}

demo/state.json — the "live" backend, recorded as a state doc (this is the same shape a real connector like postgres or supabase returns):

{"connector": "fixture", "target": "demo", "sections": {"schema": {"hash": "x", "tables": [{"name": "orders", "columns": [], "indexes": [], "foreignKeys": []}]}}}

demo/src/db.ts — a source file that assumes two tables, only one of which exists in state.json:

import { createClient } from '@supabase/supabase-js'
const supabase = createClient(process.env.URL, process.env.KEY)
export const listOrders = () => supabase.from('orders').select('*')
export const listInvoices = () => supabase.from('invoices').select('*')

Then run it:

cd demo && asbuilt check demo
target: demo   risk: dev
  ❌ fail        table_exists(table=invoices)  [src/db.ts:4]
  ✅ pass        table_exists(table=orders)  [src/db.ts:3]
  note: RLS intent is not derived from code; verify explicitly with rls_enabled / policy_exists assertions.
  note: 2 app-level env references were not checked — no connector exists for the application host.
summary: 1 pass, 1 fail, 0 unsupported, 0 skipped

asbuilt check reads .asbuilt/targets.json from the current directory (or $ASBUILT_PROJECT_DIR if set) — that's where target and state-file resolution happen. --repo <path> only changes which directory gets scanned for code assumptions; it does not relocate where targets are read from.

Configure a target

.asbuilt/targets.json in your repo (env-var names only — secrets stay in your environment):

{ "targets": {
    "dev":  { "connector": "supabase", "projectRef": "abc123",
              "tokenEnv": "SUPABASE_ACCESS_TOKEN", "risk": "dev" },
    "prod": { "connector": "postgres", "urlEnv": "PROD_PG_URL_RO", "risk": "prod" } } }

Connectors: Postgres (anything speaking pg: RDS, Neon, Supabase, Azure), Supabase (schema/RLS + auth/storage/functions/secrets), Firebase (Firestore collections & indexes, security rules, auth, storage, functions — see docs/firebase-setup.md).

The tools

Tool

What the agent gets

deployment_check

the flagship: code assumptions vs. live state, with file:line

infra_state

live backend state, normalized

infra_verify

assert specifics: table_exists, rls_enabled, bucket_exists, …

infra_impact

blast-radius before a destructive op ("dropping users breaks 3 FKs")

infra_snapshot

content-addressed, redacted state snapshots

infra_drift

structural diff between any two states (or live)

Every response carries the target's risk tier (prod/staging/dev) — the signal that was missing in both incidents above.

Honest by design

  • Read-only by construction. The connector protocol has no write method. Postgres opens default_transaction_read_only=on. The Firebase connector probes its own credential and refuses to run if it holds write permissions.

  • Unknown is never a pass. Assertions the connector can't evaluate return unsupported; dynamic code references are skipped and listed — "checked 12, skipped 3" can never be misread as all-clear.

  • Secrets never enter captured state. Values are hashed at capture time.

Benchmark

benchmark/ measures the failure mode this exists to prevent: how often agents are confidently wrong about live infra, with vs. without verification.

15 seeded cases exercise all four MCP tools (deployment_check, infra_verify, infra_impact, infra_drift) against the fixture connector, graded by deterministic accept/reject regex pairs — no LLM judge. python -m benchmark.runner runs fully offline (no ANTHROPIC_API_KEY needed) and doubles as the connector regression harness, printing each case's tool-derived answer against its known-correct truth.

The agent-alone-vs-agent-with-tools model comparison (_run_comparison in benchmark/runner.py) requires ANTHROPIC_API_KEY and hasn't been run live yet — there is no published number for how much verification actually moves agent accuracy. That comparison is implemented and offline-testable, but the "agents are less confidently wrong with asbuilt than without it" claim is still a hypothesis, not a result.

License

MIT

Related MCP Connectors

Related MCP Servers