Skip to main content
The fastest way to diagnose a Midplane issue is to read the denial reason or the boot log — both name what’s wrong and usually what to change. This page collects the issues people hit most, with the fix for each.

Denied queries

A denial is usually correct: Midplane is doing its job. But if a query you expected to succeed was denied, the reason string names the rule and the table. Match it to one of these.
The likely cause: the table is set to deny. A table marked deny allows neither reads nor writes. Check your policy — if the table’s access is deny (or your default is deny and the table isn’t listed), reads are blocked. Set it to read (or read_write) to allow SELECTs. See table access.
Writes deny by default. To allow a write, mark the table read_write in your policy:
Open the database’s policy grid and set a per-table override to read_write. Cloud hot-reloads the change.
Note that read_write grants ordinary DML only. Side-effecting statements — COPY, LOCK, CALL, NOTIFY, SET — deny regardless of YAML, because the policy can’t grant a per-table target for them.

Agent connection problems

The most common cause is a missing transport flag. Claude Code defaults to stdio and will treat your URL as a launcher command, failing silently. Add --transport http:
Verify it registered with claude mcp list. See Claude Code setup.
Claude Desktop’s Custom Connectors UI enforces HTTPS and rejects http://localhost (and won’t accept a self-signed cert against localhost). For a local self-host instance, use the mcp-remote shim in the config file instead:
This requires npx (Node.js) on your PATH. Restart Claude Desktop after editing the file. Hosted Midplane (an HTTPS endpoint) uses Custom Connectors normally. See Claude Desktop setup.

Connecting to your Postgres

Your database presents a self-signed or private-CA TLS certificate and its DSN uses sslmode=require (or prefer). Since engine 0.15.0 Midplane follows libpq’s TLS semantics, where require encrypts without verifying the certificate — so a self-signed cert connects. On an older engine, require was upgraded to full verification and rejected the cert with a bare self signed certificate the moment an agent ran its first query.
  • Encryption is enough (a self-signed or private cert) — sslmode=require connects on 0.15.0+; upgrade the engine if you’re behind.
  • You want the cert verified — add sslrootcert=/path/to/ca.crt to the DSN and Midplane verifies against that CA (like verify-ca). verify-ca / verify-full are unchanged.
  • Encryption isn’t needed (a private, trusted network) — sslmode=disable turns TLS off.
A DSN with no sslmode behaves exactly as before. On Cloud the managed engine already runs 0.15.0+, so this applies to whichever database you connect.

Boot failures

Midplane interpolates ${ENV_VAR} references in databases[].url so DSNs stay out of your YAML. If a referenced variable isn’t set in the environment, the URL can’t resolve and the server fails to boot.Check that every variable named in your policy file is present in your --env-file (or compose environment). For example, a policy with url: ${PROD_DATABASE_URL} needs PROD_DATABASE_URL defined.You can structurally validate a policy offline without the variables set — midplane policy validate <file> doesn’t fail on an unset ${VAR}, so it checks the YAML shape independent of the environment. The variable only has to resolve at server boot.
When in doubt, read the audit log. midplane audit tail streams every attempt and decision, including the rule that denied — so you can see exactly what your agent sent and why it was blocked. See the self-host audit log.

FAQ

Short, grounded answers to the questions that come up most.
Yes — and we don’t claim otherwise. Midplane logs every query as the audit record, and results pass through its memory on the way back to your agent (never persisted). On self-host none of this leaves your environment; on Cloud, operators with database access can read audit data until retention expires. See what we claim, and what we don’t.
No. Midplane is a second layer. It enforces policy on top of your database’s own permissions — it doesn’t replace role-based access control. If the connection string belongs to a privileged role, Midplane operates on top of those permissions. Give the agent a least-privilege database role as well. See the threat model.
No, not by default. read_write grants row writes; whole-table destruction is a separate net. The dangerous_statement guardrails — on by default since 0.9.0 — deny a DELETE/UPDATE with no WHERE clause and any DROP/TRUNCATE/ALTER, regardless of table access, so granting a write can’t be escalated into DELETE FROM orders or DROP TABLE orders. They’re configurable per flag in the guardrails block if you really need to allow one.
Postgres, since 0.1.0 — it’s the only dialect supported today. The dialect: key accepts only postgres. See the policy engine.
Self-host is free — MIT open source, no hosted dependency. Midplane Cloud has a Free tier plus Pro and Team plans, gating on projects, MCP tokens, and seats rather than query volume. The policy engine, audit logging, and credential isolation are on every tier. See plans.
Self-host sends anonymous, aggregate telemetry by default — no SQL, no table or column names, no query results, no credentials. Disable it with MIDPLANE_TELEMETRY=0 or DO_NOT_TRACK=1; inspect what’s sent with MIDPLANE_TELEMETRY=debug. See telemetry.

Get help

If the fixes above don’t cover it:
Email support@midplane.ai — also the Email support link in the dashboard sidebar. A human reads and answers.
Found a security vulnerability? Don’t open a public issue — see report a vulnerability.

Still stuck?

Denial reasons reference

Every denial message Midplane returns, and what to do about it.

How Midplane works

The parse → policy → audit → execute pipeline behind every decision.