> ## Documentation Index
> Fetch the complete documentation index at: https://midplane.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# CLI reference

> The midplane binary: server, setup wizard, query, doctor, policy authoring, and the audit log reader, with every flag and exit code.

The Docker image ships a single `midplane` binary. With no subcommand it runs the MCP server. Run the other (operator) commands inside the container with `docker exec`.

```bash theme={null}
docker exec midplane midplane policy validate /etc/midplane/policy.yaml
docker exec midplane midplane audit denies
docker exec midplane midplane doctor
```

| Command                      | What it does                                                                         |
| ---------------------------- | ------------------------------------------------------------------------------------ |
| [`server`](#midplane-server) | Run the MCP server (the default, the image entrypoint).                              |
| [`init`](#midplane-init)     | Interactive setup wizard: introspect the database and write a policy.                |
| [`query`](#midplane-query)   | Send one statement through the server as an agent would.                             |
| [`doctor`](#midplane-doctor) | Preflight and smoke checks: config, policy, DB connectivity, audit, canary.          |
| [`policy`](#midplane-policy) | Author, validate, lint, and dry-run a policy file (`init`/`validate`/`lint`/`test`). |
| [`audit`](#midplane-audit)   | Read the local audit log (`tail`/`since`/`denies`/`show`/`stats`).                   |

<Note>
  **Output convention.** Every command prints human-readable colored output on a TTY and **JSON lines when piped**. `--json` / `--pretty` force one or the other; color respects `NO_COLOR`.
</Note>

## `midplane server`

Run the MCP server. This is the **default** command, run by the image entrypoint with no arguments. Reads configuration from [environment variables](/docs/reference/environment-variables) (`DATABASE_URL`, `MIDPLANE_POLICY_FILE`, `PORT`, `MIDPLANE_TRANSPORT`, …) and serves the MCP endpoint at `/mcp`.

```bash theme={null}
midplane            # runs the server
midplane server     # explicit form
```

## `midplane init`

Interactive setup wizard. Connects with `DATABASE_URL` (or a masked prompt; the DSN is never echoed or written), introspects `information_schema`. You pick per-table `read_write` grants and `deny`s; it writes a schema-validated, linted policy file and prints the docker command, agent MCP config, and verification steps.

| Flag                 | Purpose                                                 |
| -------------------- | ------------------------------------------------------- |
| `--url <dsn>`        | The database to introspect. Defaults to `DATABASE_URL`. |
| `-o`, `--out <file>` | Where to write the policy file.                         |

```bash theme={null}
midplane init     # needs a TTY
```

**Synopsis:** `midplane init [--url <dsn>] [-o <file>]`

<Note>
  `init` needs a TTY. In a script or CI, use [`midplane policy init`](#policy-init) instead.
</Note>

## `midplane query`

Send **one** statement through the server's full policy and audit pipeline, over Streamable HTTP, as an agent would. Calls stamp `agent_name=midplane-cli` on their audit rows. One shot, no REPL.

| Flag                | Purpose                                                                       |
| ------------------- | ----------------------------------------------------------------------------- |
| `--sql "<query>"`   | The statement to send, or pass it as a bare positional.                       |
| `--intent "<why>"`  | Audit intent recorded for the call.                                           |
| `--server <url>`    | Server to call. Defaults to `http://localhost:$PORT/mcp`.                     |
| `--stdio`           | Spawn a child `midplane server` over stdio instead of calling an HTTP server. |
| `--database <name>` | Target database on a multi-database server.                                   |
| `--json`            | Print the raw result JSON. Default when piped.                                |

```bash theme={null}
docker exec midplane midplane query --sql "SELECT 1"
docker exec midplane midplane query --sql "DELETE FROM users" --intent "verify the policy blocks this"
```

**Synopsis:** `midplane query --sql "<query>" [--intent "<why>"] [--server <url> | --stdio] [--database <name>] [--json]`

**Exit codes:** `0` on ALLOW; `1` on DENY or error; `2` on a usage error.

## `midplane doctor`

Preflight and smoke checks in boot order: env config, policy file (validate plus the `policy lint` findings), per-database connectivity (`SELECT 1`), the audit store, `/health`, and an end-to-end MCP canary (a `SELECT 1` through the `query` tool).

| Flag             | Purpose                                                   |
| ---------------- | --------------------------------------------------------- |
| `--server <url>` | The running server. Defaults to `http://localhost:$PORT`. |
| `--no-canary`    | Skip the end-to-end MCP query.                            |
| `--json`         | Emit `{ ok, checks }` instead of the text report.         |

```bash theme={null}
docker exec midplane midplane doctor
```

**Synopsis:** `midplane doctor [--server <url>] [--no-canary] [--json]`

**Exit codes:** any `FAIL` exits nonzero; warnings don't.

## `midplane policy`

Author, validate, lint, and dry-run a [`MIDPLANE_POLICY_FILE`](/docs/reference/policy-schema) — without hand-editing YAML blind or connecting an agent. Four subcommands, run in order for a typical author flow:

```bash theme={null}
midplane policy init --url $DATABASE_URL -o policy.yaml   # scaffold a commented file
midplane policy validate policy.yaml                      # check against the server's schema
midplane policy lint policy.yaml                          # surface risky postures
midplane policy test policy.yaml --sql "DELETE FROM users WHERE id = 1"   # dry-run a query
```

<Tip>
  At an interactive terminal, [`midplane init`](#midplane-init) is the friendlier path — the same scaffold generator with a per-table wizard that validates and lints before writing. `midplane policy init` is the flag-driven form for scripts and CI.
</Tip>

### `policy init`

Scaffold a commented policy file. With `--url`, connect read-only, list every table in the `public` schema, and emit each under `table_access` with `default: read`. The DSN is never printed or written into the scaffold.

| Flag                 | Purpose                                                                        |
| -------------------- | ------------------------------------------------------------------------------ |
| `--url <dsn>`        | Connect and scaffold from `public` tables. Omit for a static starter template. |
| `-o`, `--out <file>` | Write to a file instead of stdout. Refuses to overwrite an existing file.      |

```bash theme={null}
# Scaffold from a live DB. DATABASE_URL must expand INSIDE the container.
docker exec midplane sh -lc 'midplane policy init --url "$DATABASE_URL"' > policy.yaml
```

**Exit codes:** `0` on success; `1` if the output file already exists or the introspection connection fails.

### `policy validate`

Validate a file against the server's schema plus the loader's semantic rules (`mappings` / `overrides` conflict, reserved or duplicate database names, the name regex). Prints `OK`, or `INVALID` followed by each error as `path: message`.

```bash theme={null}
midplane policy validate policy.yaml
```

**Synopsis:** `midplane policy validate <file>`

**Exit codes:** `0` when valid (`OK`); `1` when invalid; `2` on a usage error (no `<file>`).

### `policy lint`

Report security-posture findings the schema can't see. Findings are graded:

| Severity  | Example                                                                                                                         | Exit    |
| --------- | ------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `[ERROR]` | `default: read_write` — every unlisted table is writable, including tables added later.                                         | nonzero |
| `[WARN]`  | A guardrail explicitly disabled (`block_ddl: false` or `block_unqualified_dml: false`) widens what a `read_write` table can do. | `0`     |
| `[INFO]`  | Lists which tables were granted `read_write`, so you can confirm each is intentional.                                           | `0`     |

```bash theme={null}
midplane policy lint policy.yaml
```

Lint exits nonzero only on an error-level finding, which makes it a good CI gate — wire it in so a `default: read_write` can't merge.

**Synopsis:** `midplane policy lint <file>`

**Exit codes:** `0` when there are no error-level findings (warnings and info still exit `0`); `1` on any `[ERROR]` finding; `2` if the file isn't schema-valid (run `validate` first) or `<file>` is missing.

### `policy test`

Evaluate a query and print `ALLOW` or `DENY`, the deciding rule, and the exact agent-facing denial message. Pass exactly one of `<file>` (offline, no database connection) or `--server` (the [`/admin/dry-run`](/docs/reference/mcp-tools#http-endpoints) endpoint of a running server's currently-loaded policy, which also prints the server's `policy_hash`).

| Flag              | Purpose                                                                                                             |
| ----------------- | ------------------------------------------------------------------------------------------------------------------- |
| `--sql "<query>"` | The query to evaluate. Required.                                                                                    |
| `--server [url]`  | Test the running server's loaded policy via `/admin/dry-run`. Defaults to `http://localhost:$PORT`.                 |
| `--token <token>` | The server's `INDEXER_TOKEN`. Required by `--server`, or set `INDEXER_TOKEN` in the env.                            |
| `--db <name>`     | Which `databases[]` entry to test. Defaults to the first; warns if you don't pick when more than one is configured. |
| `--json`          | Emit the verdict as JSON.                                                                                           |

```bash theme={null}
# Against a file:
midplane policy test policy.yaml \
  --sql "DELETE FROM users"

# Against the running server:
docker exec midplane midplane policy test --server \
  --sql "DROP TABLE orders" --token "$INDEXER_TOKEN"
```

An `ALLOW` prints the statement and that it would reach the database; a `DENY` prints the deciding rule and the exact agent-facing message:

```
DENY
  database:  __default__ (postgres)
  rule:      table_access
  message:   ...
  → blocked; the query never reaches the database
```

**Synopsis:** `midplane policy test (<file> | --server [url]) --sql "<query>" [--token <token>] [--db <name>] [--json]`

**Exit codes:** `0` on `ALLOW`; `1` on `DENY` (or a load error); `2` on a usage error (missing `--sql`, both/neither of `<file>`/`--server`, or an unknown `--db`).

### `policy help`

```bash theme={null}
midplane policy help     # also: midplane policy --help, -h, or no subcommand
```

## `midplane audit`

Read the local SQLite [audit log](/docs/reference/audit-events). All five subcommands are read-only against `DB_PATH` (default `/data/audit.db`) and open SQLite directly, so they don't need `INDEXER_TOKEN`. See [reading the audit log](/docs/concepts/audit-trail).

### `audit tail`

Stream audit events as JSON lines: backfill recent rows, then poll for new ones (Ctrl-C to stop).

| Flag             | Default | Purpose                                         |
| ---------------- | ------- | ----------------------------------------------- |
| `--backfill <N>` | `10`    | Number of recent rows to emit before following. |
| `--no-follow`    | —       | Emit the backfill and exit instead of polling.  |

```bash theme={null}
docker exec midplane midplane audit tail --backfill 50 \
  | jq 'select(.event_type == "DECIDED" and .payload.decision == "DENY")'
```

**Synopsis:** `midplane audit tail [--backfill N] [--no-follow]`

### `audit since`

Dump every event whose timestamp falls within a duration window. Durations are sums of `N` + `s|m|h|d` tokens: `1h`, `30m`, `7d`, `1d12h`.

```bash theme={null}
docker exec midplane midplane audit since 1h
```

**Synopsis:** `midplane audit since <duration>`

**Exit codes:** `2` on a missing or malformed duration.

### `audit denies`

Show what got blocked and why. Joins each `DENY` decision to its `ATTEMPTED` row, printing the blocked SQL, the deciding rule, the agent-facing reason, and the agent's stated intent together. Truncation is announced, never silent.

| Flag                 | Default | Purpose                                          |
| -------------------- | ------- | ------------------------------------------------ |
| `--since <duration>` | *(all)* | Only denials within this window (`1h`, `7d`, …). |
| `--limit <N>`        | —       | Cap the number of denials shown (the latest).    |

```bash theme={null}
docker exec midplane midplane audit denies --since 24h
```

**Synopsis:** `midplane audit denies [--since DURATION] [--limit N]`

### `audit show`

Show every event for one query: agent, intent, full SQL, and the `ATTEMPTED → DECIDED → EXECUTED`/`FAILED` chain. Takes the `qid=…` that `tail` / `denies` print (an individual event id also works).

```bash theme={null}
docker exec midplane midplane audit show 01HXXX...
```

**Synopsis:** `midplane audit show <query_id>`

**Exit codes:** `2` on a missing `<query_id>`; `1` if no event matches it.

### `audit stats`

Group-by summary over a window: event types, top deny rules, top allow statement types, and top agents.

| Flag                 | Default | Purpose                                     |
| -------------------- | ------- | ------------------------------------------- |
| `--since <duration>` | `24h`   | The window to summarize.                    |
| `--json`             | —       | Emit the summary as JSON instead of tables. |

```bash theme={null}
docker exec midplane midplane audit stats --since 7d --json
```

**Synopsis:** `midplane audit stats [--since DURATION] [--json]`

**Exit codes:** `2` on a malformed `--since` duration.

### `audit help`

```bash theme={null}
midplane audit help      # also: midplane audit --help, -h, or no subcommand
```

## `midplane version` and `midplane help`

```bash theme={null}
midplane version    # print the engine version
midplane help       # top-level usage
```

## Common exit codes

| Code | Meaning                                                                                       |
| ---- | --------------------------------------------------------------------------------------------- |
| `0`  | Success (and `ALLOW` / `OK` for `policy test` / `policy validate`).                           |
| `1`  | A failure: `DENY`, `INVALID`, a lint `[ERROR]`, or an I/O / connection error.                 |
| `2`  | A usage error: an unknown subcommand, a missing required argument, or a malformed flag value. |

## Related

<Columns cols={2}>
  <Card title="Policy file schema" icon="file-code-2" href="/docs/reference/policy-schema">
    What `policy init` / `validate` / `lint` operate on.
  </Card>

  <Card title="Audit events" icon="scroll-text" href="/docs/reference/audit-events">
    The rows `audit tail` / `since` / `stats` read.
  </Card>
</Columns>
