Skip to main content
Midplane sits between an AI coding agent and your database. The agent speaks MCP; Midplane fronts your Postgres database. Every query the agent sends takes the same path: it’s parsed into a syntax tree, checked against your policy, audited, and only then — if allowed — run against your database. This goes a level deeper than what Midplane is: the full pipeline, the four rules the engine enforces, and why “audited before executed” is a guarantee, not a best effort.

The pipeline

For every query, Midplane runs five steps in a fixed order. The decision is binary: allow or deny.
1

Parse

The SQL is parsed with libpg_query, the actual Postgres parser — no regex on SQL, ever. If the SQL can’t be parsed, it’s denied by the parse_error rule. Midplane never enforces policy on text it can’t read — it fails closed.
2

Project to an IR

The parse tree is projected into a dialect-agnostic intermediate representation, and the policy rules evaluate that IR rather than a raw AST.
3

Policy

The IR is checked against the four rules in order; the first rule that denies wins. The guardrail (dangerous_statement) runs last, so when a more specific rule would also deny, that rule’s reason surfaces.
4

Audit (before execution)

The attempt and the decision are written to the audit trail before the query runs. Denied queries are recorded too. If the pre-execution audit write fails, the query never runs.
5

Execute (only if allowed)

Only allowed queries reach your database. If the policy masks any columns, the engine redacts those values in the result before it returns to the agent. The result and timing are appended to the same audit trail. Denied queries stop at the audit step — they never touch the database.

The four rules

The engine evaluates four rules in order. Two are built-in guards that are always on; the rest are driven by your policytable_access and the dangerous_statement guardrails default on. The built-in guards, the dangerous_statement guardrails, and the per-table rule are all covered in table access and guardrails; the guardrails are configurable per database via the guardrails block.

”Audited before executed”

Most database logging records what already happened. Midplane inverts that: the attempt and the decision are durable before a single row is read or written, and the audit write is a precondition for execution. If the pre-execution audit write fails, the query does not run. There is no path where a query reaches your database without a corresponding audit row. That’s what makes the audit trail a complete record — including every denial, with the rule that decided it, the agent that asked, and the agent’s stated intent on the call.

The two components

Midplane is one engine, exposed to agents by one server.

The engine

@midplane/engine runs the parse → policy → audit → execute pipeline and makes the allow/deny decision. It’s embeddable, with no MCP dependency.

The MCP server

@midplane/mcp-server wraps the engine and exposes it to agents over MCP (stdio and Streamable HTTP). It registers the tools your agent calls — query, list_tables, and describe_table — and routes each one through the engine.
It’s the same engine whether you self-host or use Cloud — see Cloud vs self-host.

Two layers of enforcement

When an agent tries something destructive, two independent things stop it. Here’s a delete all users prompt to Claude Code, against a Midplane-fronted database:
1

The agent asks for confirmation

Hold on — that’s destructive and irreversible. Before I run DELETE FROM public.users, please confirm.
This is the agent’s own safety behavior. It’s useful, but it’s not a guarantee: a confirmation is just a prompt, and the next reply can be “yes.” It depends entirely on the agent’s good behavior.
2

Midplane denies the query

After the user confirms, the agent runs the DELETE. Midplane returns the denial:
Midplane blocked it. The denial reason it returned: “Midplane denied this query because writes to table users are not allowed by the table-access policy (users resolves to read; mark it read_write in your MIDPLANE_POLICY_FILE to grant writes).” The DELETE was audited but not executed.
This is the layer that doesn’t depend on the agent cooperating. The table_access rule denies the write because users is read, and the audit row for the attempt lands before the query would have reached the database.
The first layer is the agent being careful. The second layer is Midplane enforcing your policy regardless of what the agent decides. Even if the agent skips its confirmation entirely, the query is still parsed, denied, and audited.

Postgres specifics

Midplane runs against Postgres — it’s the only dialect supported. The SQL is parsed by libpg_query, the real Postgres parser, so the AST the engine reasons about is the same one the server would build. Three guarantees keep that parse sound:
  • search_path is pinned to public, pg_catalog per transaction, so bare table names resolve to public as a hard guarantee — that’s what makes the public.<table> fallback in table access sound.
  • SET is denied, so agent SQL can’t rewrite search_path on a pooled connection and change how bare names resolve.
  • pg_catalog is policed like any other schema — a query into the system catalog is checked, not exempted.
The dialect: key in the policy schema defaults to postgres and rejects any other value at boot. It exists for forward compatibility, not to select a backend today.

Layered, not a replacement

Midplane enforces policy on top of your database’s own permissions — give the connection a least-privilege database role too, so it’s the second layer, not the only one. And the engine decides allow or deny declaratively; human-in-the-loop approval workflows are on the Cloud roadmap, not part of the OSS engine.

Where to go next

Write your first policy

Grant the reads and writes your agent actually needs.

Table access & guardrails

The access model, and the dangerous query shapes denied out of the box.

The audit trail

The durable record written before every query runs.