Give your AI agent the meaning of your PostgreSQL database, not just its columns.
Point an MCP-capable agent (Claude Code, Claude Desktop, Cursor) at a real business database and ask it a simple question — "what was our revenue last quarter?" — and it will confidently write a plausible, wrong query. Not because the model is weak, but because the answer isn't in the column types. It's in the business rules around them: which column is authoritative, what a status really means, which rows don't count, which view is the source of truth.
Kozou reads that knowledge straight from your schema — COMMENT ON text, view
definitions, and type information — and hands it to the agent over
MCP. Same model, same question, correct
answer.
A small online-store schema where the obvious revenue query is off by 4.8×:
| Query an agent writes from… | Result |
|---|---|
the raw DDL (sum(amount_total) over paid orders) |
575.00 ❌ — a deprecated cache that includes a test order and soft-deleted rows |
the raw DDL, "carefully" (recompute from current list_price) |
580.00 ❌ — values old orders at today's price |
Kozou's context (sum(net_revenue) from vw_recognized_revenue) |
120.00 ✅ — the view encapsulates every recognition rule |
The numbers are real and reproducible. Walk through the full before/after — and
why each wrong answer is the natural one — in
examples/quickstart.
Try the demo (requires Docker):
git clone https://github.com/kozou-dev/kozou
cd kozou/examples/quickstart
cp .env.example .env
docker compose upThis brings up PostgreSQL (seeded with the demo schema) plus kozou dev — the
Admin UI on http://localhost:3333 and an MCP server on
http://localhost:3334/mcp. Point your agent at the MCP endpoint and ask it
about revenue. See examples/quickstart/README.md
for the walkthrough.
Start your own project:
# Scaffold a project (docker-compose + kozou.config.yaml + ui-hints.yaml).
# create-kozou ships as a secondary bin of the kozou package, so npx needs
# -p kozou to find it on a clean machine.
npx -p kozou create-kozou my-project
cd my-project
cp .env.example .env
docker compose upAdd your tables and COMMENT ON ... text to migrations/, and the same context
flows to your agent.
Kozou is migration-tool-agnostic. Kozou never runs DDL or owns migration state — it reads the schema your migrations produce and compiles it into faithful forms. Manage schema change (including zero-downtime rollouts) with a dedicated tool — pgroll, graphile-migrate, Atlas, Sqitch, Flyway, or plain SQL files — and Kozou compiles the result. The two are complementary, not competing.
Kozou reads a PostgreSQL schema once and produces every form a modern team
and its AI need from it. The COMMENT ON conventions (@ai, @policy,
@example, @widget) are the single place you write down what the schema
means; Kozou compiles them into:
- MCP context for AI agents (
@kozou/mcp) — the differentiator above. - A reference Admin UI (
@kozou/svelte-ui). - REST + OpenAPI (
@kozou/api, the defaultkozou devbackend). - Markdown docs (
kozou docs) — including a Mermaid entity-relationship diagram that draws the views and the@ai/@policymeaning, not just a foreign-key graph — and TypeScript types (@kozou/codegen).
One source, no duplicate definitions, no drift. The CLI, the MCP server (stdio +
HTTP, with an opt-in call tool that executes @expose: rpc actions, not just
describes them), and @kozou/api are published on npm; the runtime ships as a
multi-arch image on GHCR (ghcr.io/kozou-dev/kozou, linux/amd64 + arm64).
Opt-in: what a role may touch. Point Kozou at a role
(introspection.respectPrivileges) and the MCP describe tools and kozou docs
also tell the agent the role's effective GRANTs — that it may read a table but
not write it — so it knows its limits before it acts. A query layer that
enforces but doesn't explain (PostgREST, Hasura) can't give an agent that.
Advisory only: enforcement stays in PostgreSQL (GRANTs + your RLS). See
examples/quickstart.
Use the runtime image directly:
docker pull ghcr.io/kozou-dev/kozou:v1.12.4
docker run --rm ghcr.io/kozou-dev/kozou:v1.12.4 mcp --helpOr install the packages for library / embedded use:
npm install kozou @kozou/core @kozou/introspect @kozou/mcp @kozou/svelte-ui@kozou/api is bundled with kozou (no separate install). The experimental
@kozou/codegen ships as an optional companion (npm install kozou @kozou/codegen).
Kozou introspects COMMENT ON text, view definitions, and type information from
PostgreSQL, then hands them verbatim to AI agents through @kozou/mcp. This
relies on an important assumption: schema authors (the principals with
permission to edit DB schema) are trusted. We call this the trust boundary.
Designs where tenants in a multi-tenant SaaS can edit DB COMMENT text are discouraged (prompt-injection risk). See docs/security.md for the threat model and mitigation plans, and SECURITY.md if you need to report a vulnerability privately.
On the access-control axis, Kozou is a resource server and enforcement layer, not an identity provider: with auth enabled it verifies the JWT on each request that carries one — with an optional anonymous-role path for tokenless requests — and runs each under a PostgreSQL role so your own RLS policies decide access. Identity provision (registration, login, passwords and OAuth, token issuance) is delegated to an external provider — Supabase Auth (recommended), Auth0 / Clerk via JWKS, or a minimal self-hosted issuer. See docs/security.md.
Runtime requirements for v1.12.4:
- PostgreSQL 16 or later — the canonical source of truth
- Docker 24 or later (optional) — recommended for the
docker compose upstack, which brings up PostgreSQL and akozouservice runningkozou dev(the bundled Admin UI + MCP HTTP server, plus Kozou's in-house REST backend served in-process) fromghcr.io/kozou-dev/kozou(a multi-arch image, native on linux/amd64 and linux/arm64). The default stack needs no separate REST container; to opt out and use an external PostgREST instead, setadapter.type: postgrestand add the (commented) service in the scaffold'sdocker-compose.yml. - Node.js 20 or later — for running the npm-published packages directly (
npx kozou …)
Contributors additionally need pnpm 9 or later. See CONTRIBUTING.md for the development environment setup.
- v0.1.0 (shipped): schema introspection, MCP server (stdio), reference Admin UI,
create-kozouscaffold, PostgREST adapter - v0.1.1 (shipped): MCP HTTP transport,
kozou devhost integration, multi-arch Docker image (linux/amd64 + linux/arm64), Playwright E2E for the Admin UI, CodeQL reactivation, zod 4 / TypeScript 6 migration - v0.2.0 (shipped):
kozou docsMarkdown schema documents;@policyadvisory metadata surfaced to AI agents through the MCP server - v0.2.1 (shipped): multi-line
@ai/@policyCOMMENT blocks are now captured in full (not just the first line) in the MCP describe tools and@kozou/apiOpenAPI; the experimental@kozou/apiREST layer — with multi-hop relation embedding and opt-in JWT + Postgres RLS — and@kozou/codegenTypeScript row types are now published to npm as optional companions, exercised viakozou dev --adapter apiandkozou codegen - v1.0.0 (shipped):
@kozou/api, Kozou's in-house REST backend, is now the defaultkozou devdata layer and ships bundled with the CLI; PostgREST drops to an opt-out adapter (adapter.type: postgrest). Its REST wire format and OpenAPI are a stable contract. Composite primary keys are addressable end to end, and list filters gain PostgREST-compatible operators (eq/neq/gt/gte/lt/lte/like/ilike/in/is). See the scope table in the@kozou/apiREADME for which relational-REST features the default backend covers and which to keep PostgREST for. - v1.1.0 (shipped): composite foreign keys become first-class — they embed as relations (multi-column joins,
x-kozou-embedshints carrying the foreign-key column set, MCP /kozou docsvisibility), the relation-select endpoint serves composite-key targets (array option ids), and the Admin UI's create/edit forms gain a relation picker: single-column FKs get a searchable picker, and an eligible composite FK becomes one picker that fills every key column at once, with a non-enhanced (no-JS) form path. See the@kozou/svelte-uiREADME for the picker's eligibility rules and known limitations. The OpenAPI document is also faithful to runtime request bodies and modes. - v1.1.1 (shipped): a patch release of fixes from v1.0/v1.1 field reports. The scaffold's docker-compose now forwards the documented
KOZOU_JWT_*auth variables (they previously never reached the container, leaving auth silently off) andkozou devprints an unambiguousapi auth:state line at startup.@kozou/apimaps database outcomes to stable HTTP statuses — privilege / row-level-security denials return 403, constraint conflicts 409/400 — with raw database text kept out of response bodies (see the Errors section of the@kozou/apiREADME). The Admin UI can create rows that leave defaulted NOT NULL columns empty, two long-standing form bugs are fixed (defaulted non-text columns no longer 500 the create/edit forms; manual uuid entry submits again), and runtime warnings no longer carry stale version pins. - v1.2.0 (shipped): the auto-minted Admin UI service token (the HS256 development convenience) can now carry custom JWT claims via
auth.ui.claims(or theKOZOU_UI_CLAIMSenvironment variable), so RLS policies keyed on claims beyondrolecan be exercised from the bundled UI in local development. Theroleclaim stays reserved — a colliding entry is dropped so a custom claim can never smuggle in a role — andiatis always set by the mint; a configurediss/audwins over a custom one, while a custom temporal claim (exp/nbf) is passed through but warned about when it would make the token rejected. Schema introspection also warns when a known COMMENT tag (@ai,@policy, …) appears mid-line instead of at the start of a line — such tags are not parsed and would otherwise leak verbatim into the surrounding description. - v1.3.0 (shipped): opt-in privilege-aware introspection (
introspection.respectPrivileges, default off) makes the bundled Admin UI faithful to what the serving role may do, not just what the schema declares. A table or view the role cannotSELECT(gated by schemaUSAGE) is hidden from the nav; a column it cannot write renders read-only per form mode (noINSERT→ read-only on create, noUPDATE→ read-only on edit); and Delete is hidden where the role lacks it. The role evaluated is the Admin UI's (auth.ui.role/auth.defaultRole, or an explicitintrospection.role).@kozou/apiand the MCP server stay schema-wide. - v1.4.0 (shipped): opt-in RPC actions — a Postgres function tagged
@expose: rpcin its COMMENT is compiled into a callable action across all four surfaces at once: REST (POST /rpc/<schema>.<fn>with a named-args body), the COMMENT-native OpenAPI document, an MCPdescribe_functionstool, and an Admin UI "Actions" page. This recovers the verb-level capability the v1.0 default switch dropped, and lets the security model Kozou recommends (state changes funnelled through functions, table writes revoked) stay reachable. Exposure is opt-in and never silent: nothing is exposed by default; a function still grantingEXECUTEto PUBLIC is hard-skipped unless deliberately opened (@expose: rpc public/api.rpc.allowPublicExecute); aSECURITY DEFINERfunction needs a config double opt-in (api.rpc.allowDefiner) and an owner-safesearch_path; unsupported shapes are loudly skipped. Whether a caller may run an action is enforced by the PostgreSQLEXECUTEprivilege under the request's role — a denial is 403, so exposure is not permission. The wire shape is experimental until dogfooding settles it. See the RPC actions section of the@kozou/apiREADME. - v1.4.1 (shipped): a patch release.
@kozou/apinow pre-flights two more client inputs — item id segments (GET/PATCH/DELETE/<resource>/<id>) and write-body values — so a malformed scalar (a non-uuid id, or a value of the wrong type for its column) returns a400up front instead of surfacing as a500(issue #110). The numeric pre-flight also accepts the non-finite literals (NaN/±Infinity) PostgreSQL allows for float types. Plus documentation and scaffold hygiene — the bundled Docker image tag and the default-stack docs track the release. - v1.4.2 (shipped): a patch release of fixes.
@kozou/apinow resolves a DOMAIN column to its base type during introspection, so an invalid value for a domain-typed column returns a400up front instead of a500on list filters, item ids, and write bodies — and a domain-over-text column is now searchable (?search=) and relation-select-eligible (issue #85). Anin.(...)filter value may contain a comma when double-quoted (in.("a,b",c), with\"/\\escapes inside the quotes); unquoted lists are unchanged (issue #77).parseCommentTagsno longer absorbs an indented@ai/@policy/ … tag that follows the SQL of an@example:block — the tag is parsed normally instead of leaking into the example text and the surrounding description (issue #71). CI hardening: the npm artifact-integrity matrix now covers all seven published packages (issue #74), and the scaffolding CLI's npm publish is gated on the GHCR image it pins, so a freshly scaffolded stack never references an image tag that was not pushed (issue #105). - v1.5.0 (shipped): the MCP
callexecution tool — the MCP server can now run an exposed RPC action, not just describe it (issue #103), completing describe → act. It is opt-in and off by default: enableserver.mcp.execution(with a role) and the bundledkozou mcplists acalltool that executes a function under a single operator-configured role through the sameSET LOCAL ROLE+ RLS envelope as the REST surface — so theEXECUTEprivilege and the function's row-level security apply, the agent cannot choose the role (no self-elevation), raw database errors are never returned to the caller, and aSECURITY DEFINERaction still needs theallowDefinerdouble opt-in. It runs as one service role (no per-caller identity), so it is unsuitable for multi-tenant per-user authorization — use the REST API for that. The wire shape is experimental. Internally, the role-transaction envelope and the RPC call core moved into@kozou/coreso the REST and MCP surfaces share one enforcement path. See the RPC actions guide. - v1.5.1 (shipped): a patch release.
@kozou/api's list-filter pre-flight now validates areal(float4) value by exact comparison against the float32 rounding thresholds instead ofMath.fround, which double-rounded (decimal → binary64 → binary32) and falsely rejected (400) a decimal just inside the underflow (2^-150) or overflow (2^128 − 2^103) boundary that PostgreSQL accepts (issue #85). No value PostgreSQL rejects is now accepted, so this only turns prior false-400s into the correct 200 — never a 500. Verified against PostgreSQL 16 viapg_input_is_valid. - v1.6.0 (shipped): the opt-in RPC actions wire is now a stable contract. The REST
POST /rpc/<schema>.<fn>endpoint (named-arguments body + the return-shape mapping), the COMMENT-native OpenAPI function metadata, and the MCPcallexecution tool — all compiled from@expose: rpc— shipped experimental-first in v1.4.0 (describe) and v1.5.0 (execute, issue #103). After end-to-end validation against a realistic schema (a least-privilege execution role, RLS read/write enforcement,SECURITY DEFINER, no-leak errors, the allowlist, and the full return-shape matrix), the wire joins the table/view CRUD surface under the stability guarantee: it will not change incompatibly without a major release. Documentation/contract-declaration only — no behavior change. See the@kozou/apiREADME Stability section and the RPC actions guide. - v1.7.0 (shipped): the framework-agnostic read-path of the reference Admin UI is extracted into a new published package,
@kozou/ui-core, which@kozou/svelte-uinow depends on. The twoDataAdapterimplementations, list-parameter parsing, view-column heuristics, cell formatting, foreign-key label resolution, resource-id handling, and the schema / FK-row caches move out of the SvelteKit app unchanged, so the same logic can drive more than one UI framework. This is the structural step of the React UI exploration: a minimal React (Next.js) read spike renders list + detail by consuming@kozou/ui-coreverbatim, with no change to the shared core — evidence that the read-path abstraction is framework-agnostic. (The spike is an internal example, not a published second UI;@kozou/svelte-uistays the reference Admin UI.) Also:@kozou/apinow rejects a malformed, non-empty JSON request body with a400instead of treating it as an empty body (issue #145). The@kozou/svelte-uipublic API is unchanged. - v1.8.0 (shipped): opt-in privilege-aware AI context — point Kozou at a role (
introspection.respectPrivileges) and the MCPdescribe_table/describe_viewtools andkozou docsnow also tell the agent what that role may touch: each relation is annotated with the role's effective GRANTs (relation-levelSELECT/INSERT/UPDATE/DELETE, plus per-columninsertable/updatableon tables), andkozou docsgrows a per-table "Security" section. Unlike the Admin UI's privilege mode, the AI surfaces annotate rather than hide — a table the role cannotSELECTstill appears, markedselect: false, so the agent is told its limits rather than left guessing. It reuses the existing privilege introspection (no new queries) and is advisory only: enforcement stays in PostgreSQL (GRANTs + your RLS). When the MCPcallexecution tool is enabled, the annotated role is tied to the execution role so describe and act never disagree. Default off ⇒ every surface stays schema-wide.kozou docsalso now renders per-column@ai/@policynotes. Seeexamples/quickstart. - v1.8.1 (shipped): a security-hardening patch. The unauthenticated MCP HTTP server gains DNS-rebinding protection — it validates the request
Host/Originagainst an allowlist before handling it, so a page in the operator's browser cannot drive it by rebinding a hostname to loopback — and a request-body size limit (both the MCP server and@kozou/apireject an over-large body with413, and a non-JSON body with415, instead of buffering it unbounded). Read requests (GET) now run in aREAD ONLYtransaction, so a read cannot commit a write regardless of the role's grants. The bundled dev stack is loopback by default: the Admin UI and MCP HTTP server bind127.0.0.1(setKOZOU_UI_HOST/KOZOU_MCP_HTTP_HOSTto expose them inside a container), and the scaffold and quickstartdocker-composefiles publish every port (Admin UI, MCP, database) on127.0.0.1only. See the repository's security advisory. - v1.9.0 (shipped): field-report fixes for the introspection and REST surfaces. Native PostgreSQL
ENUMcolumns are now recognized — a column typed as a native enum gets theenum-selectwidget, a string-literal union in the generated client, and anenumin OpenAPI / the MCP context, matching how the same type already resolved as a function argument. Declarative partitioned parent tables and materialized views are now introspected (the parent is surfaced and reads like a normal table while its leaf partitions stay hidden; a materialized view appears as a read-only view) — previously both were silently absent from every surface. A list query with an explicitsorton a non-unique column now appends the primary key as a tiebreaker, soLIMIT/OFFSETpagination is stable across pages. Andkozou mcp --httphonoursserver.mcp.http.{port,host}fromkozou.config.yaml, matchingkozou dev. - v1.9.1 (shipped): field-report fixes for the reference Admin UI and the external REST adapter. A create / update / delete that the database rejects (a unique / foreign-key / check violation, or a privilege / row-level-security denial) now re-renders the form with the user's input and a readable message instead of a generic 500 that discarded it. And free-text search under the external REST (PostgREST) opt-out now quotes the term, so a value containing reserved characters (
,().) is matched literally instead of corrupting the query. Also bumps a transitive dependency for a published security advisory. - v1.10.0 (shipped): list-endpoint scaling and developer-experience improvements, all additive.
@kozou/apilist pagination gains two opt-in controls: a?count=exact|estimated|nonemode (the defaultexactcount is unchanged;estimateduses the planner's O(1) row estimate;noneskips the count and returnstotal: null), and keyset (cursor) pagination via?after=/?before=that pages by the list's effective order (sort + primary-key tiebreaker) in O(page) instead of walking a deepOFFSET. Cursors are opaque, round-trip every column type losslessly, and order correctly across composite-key, mixed-direction, and nullable sorts; offset paging is unchanged. The MCPlist_tables/list_viewstools now returnsourceSchemasandoutOfScope, so an agent can tell a schema outside the introspection scope from one that is simply empty (the no-opincludeSysteminput is retired from the tool). The reference Admin UI's list table is more accessible: the search field has a real accessible name and sortable column headers exposearia-sort. Plus minor cleanups — UI Hints keyed by schema-qualified name so same-named relations in different schemas no longer collide, array-aware codegen enum types, and an MCP tool-dispatch error that no longer echoes raw internal text. - v1.11.0 (shipped): an authorization-aware addition to the AI context, additive. MCP
describe_tableandkozou docsnow surface a row-level-security signal. A table protected by row-level security is reported with whether RLS isenabled, whether it isforced(applies to the table owner too), and whether any policy exists, plus an advisory note telling the agent the rows it sees may be filtered and a write may be rejected — a table with RLS enabled but no policy is flagged default-deny. The signal is on by default (a role-independent structural fact about the table, not the opt-in per-role privilege mode) and reported on tables only (a view carries no RLS flag of its own, and whether it masks its underlying tables depends onsecurity_invoker). Kozou reads only the booleans — never the policy expressions (USING/WITH CHECK) — so your authorization logic stays in the database and out of the agent's context; knowing a table is row-filtered never bypasses the filter, which PostgreSQL enforces regardless. - v1.12.0 (shipped):
kozou docsnow emits a Mermaid entity-relationship diagram as a schema overview, built entirely from the existing schema context (read-only, stateless, no new introspection). It is meaning-laden, not a bare foreign-key graph: tables and views are entities (views are Kozou's named business concepts, linked by a dotted "derives from" edge to the tables they are built on), columns are attributes withPK/FKmarkers, foreign keys are crow's-foot edges whose cardinality reflects NOT NULL / uniqueness, and a legend plus an entity-level@ai/@policynotes block sits under the diagram, so a source-of-truth view and the schema's meaning are co-located with the structure. The README and docs also now state that Kozou is migration-tool-agnostic — it compiles the schema your migrations produce and never runs DDL or owns migration state. - v1.12.1 (shipped): documentation and dependency hygiene. The scaffolded
migrations/0001_init.sqlnow ships a worked example of the COMMENT conventions — anorderstable and avw_recognized_revenueview exercising@ai/@widget/@policy/@exampleand the view-as-domain-concept pattern — instead of a single-line stub, so a freshly scaffolded project shows how to encode schema meaning from the start. Also pins a transitive dev/test-only dependency (undici) to a patched line for a published security advisory; no runtime dependency of any published package changes. - v1.12.2 (shipped): a patch release of input-validation hardening, all back-compatible.
@kozou/apipre-flights two more client-mistake classes to a400up front instead of letting them reach PostgreSQL as a data exception (a500): a value outside a nativeENUMcolumn's labels — on every path (list filter,in.(...), write body, item-id segment, and a forged pagination cursor) — and an out-of-range pagination offset (apageso large the computedOFFSEToverflows). CHECK-constraint columns (text / date / …) are deliberately left to PostgreSQL: their value set is not exhaustive, so a predicate outside it (status=eq.legacy,like.…, a date range) stays a valid query, not a 400. The same change also stops an empty-string text filter (?col=eq.) being wrongly 400'd, since''is a valid value. Plus dependency hygiene: a transitive build-only dependency (ts-deepmerge) is pinned for an OSV-Scanner advisory; no runtime dependency of any published package changes. - v1.12.3 (shipped): a patch release of reference Admin UI and cold-start fixes, all back-compatible. The Dashboard's Tables list now shows the table name as the title and its
COMMENTas the description — a commented table previously rendered the comment in both slots and never showed the name, while the Views list already did the right thing; the table detail / list / new / edit headings shared the same label and are fixed too (@kozou/core). Plus onramp papercuts found running the scaffold end to end: the scaffolded.env.exampleno longer carries a redundantDATABASE_URLline that silently desynced fromPOSTGRES_PASSWORD(a first-run auth failure), and a bad or missing argument to the MCPdescribe_table/describe_viewtools now returns an actionable message naming the argument instead of a generic tool-failed error. - v1.12.4 (shipped): reference Admin UI improvements from operator feedback, all additive. A new "Connect an AI agent" page turns the Admin UI into an on-ramp to the MCP layer: it shows the live MCP Streamable HTTP endpoint
kozou devalready serves and copy-paste client config for Claude Desktop / Cursor (anmcpServersHTTP entry — noDATABASE_URLor any secret) and Claude Code (claude mcp add --transport http), reachable from a dashboard card and a header link. So an operator who lives in the Admin UI finds their way to the AI context layer — kozou's actual value — instead of hunting through docs. Plus navigation papercuts: a top-of-page back link on the table detail / new / edit pages (the way back to the list no longer requires scrolling to the bottom), and a full-width desktop layout so wide list tables use the viewport instead of a centered column. - Beyond v1.12: React UI exploration — optional write-path parity (a second UI driving create / edit / delete across both adapters)
Kozou carries three meanings in three syllables:
- kozō (calf) — the young elephant walking beside PostgreSQL's mascot Slonik
- kōzō (structure) — the structural transformation a compiler performs
- kozō (apprentice) — the quiet figure who serves something larger than itself
Apache 2.0