{
  "markdown": "# Midplane\n\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)\n[![CI](https://github.com/midplaneai/midplane/actions/workflows/engine-test.yml/badge.svg)](https://github.com/midplaneai/midplane/actions/workflows/engine-test.yml)\n[![Docs](https://img.shields.io/badge/docs-midplane.ai%2Fdocs-2ea44f.svg)](https://midplane.ai/docs)\n[![MCP](https://img.shields.io/badge/MCP-stdio%20%2B%20Streamable%20HTTP-blueviolet)](https://modelcontextprotocol.io/)\n\n**Postgres MCP server for AI agents.** Connect the tables you've been keeping\noff-limits. PII masked at the source, policy enforced on the SQL AST, writes held\nfor human approval, everything audited. MIT, self-hostable.\n\nMidplane sits in the query path between an AI agent (Claude, Cursor, any MCP\nclient) and your Postgres database. Every statement is parsed into a real Postgres\nAST — not matched against a regex blocklist — checked against a declarative\nper-table policy, rewritten so masked columns never leave the database in the\nclear, and recorded in an event-sourced audit log **before** it executes.\n\n> 📖 **Full documentation lives at [midplane.ai/docs](https://midplane.ai/docs)** —\n> agent setup, the policy reference, self-hosting, deployment, and the threat model.\n> This README is just the orientation.\n\n<img width=\"960\" height=\"540\" alt=\"midplane-chat-demo\" src=\"https://github.com/user-attachments/assets/d9800b2a-dc45-4a6e-a0b2-3aa219b1009a\" />\n\n## Why this exists\n\nAI coding agents are being plugged into production Postgres without an audit trail\nor a safety layer. The deprecated Anthropic reference Postgres MCP shipped a\nstacked-statement injection vector (Datadog Security Labs, 2025); the common\nservice-role setup hands an agent a connection that can read and write every table.\nSo the tables that would make an agent genuinely useful — customers, orders,\nsubscriptions — stay off-limits, because \"a read-only role and good intentions\"\nisn't a control anyone can show a security reviewer. Midplane is that control.\n\n## What it does\n\n- **PII masked at the source.** Declare a column masked and the engine rewrites\n  the query's source relation, so the raw value never leaves Postgres — masking\n  applies inside joins, filters, and aggregates rather than being scrubbed out of\n  the rows on the way back. Transforms range from `full-redact` and `null-out` to\n  `consistent-hash` (deterministic, salted, so masked join keys still join),\n  `partial`, `generalize`, `pseudonymize`, and `noise`. It fails closed: anything\n  the engine can't prove is masked — an unvetted function that could read the\n  table behind the parser's back, a result column whose provenance doesn't map to\n  a known base column — denies the query rather than risk an unmasked value.\n- **Policy enforced on the SQL AST.** Every statement is parsed by `libpg_query`\n  — Postgres's own parser — and checked against a per-table policy of\n  `deny` / `read` / `read_write`, plus optional tenant scoping and categorical\n  write guardrails. The walk is recursive, so a write buried in a CTE, subquery,\n  or UNION arm is caught at the write, not at the outer `SELECT`.\n- **Writes held for human approval.** Opt a write class into approvals and the\n  engine pauses the statement *after* the policy has allowed it and asks a human.\n  The agent gets a `check_approval` tool and re-runs when the answer lands; a\n  denial carries the reviewer's note back, so \"use the refunds table instead\"\n  reaches the agent. Approvals sit under the policy, never over it — nothing the\n  policy denied can be approved into running.\n- **Everything audited.** An event-sourced log written *before* execution: which\n  agent, which token, the statement, the decision and its reason, and which\n  columns were masked. Local SQLite for the standalone engine; a queryable,\n  exportable audit log in the app.\n\n## What it blocks\n\nOut of the box, before you configure anything:\n\n- **Destructive writes by default** — `DELETE FROM users` is denied even with a\n  `WHERE`, until you opt the table into `read_write`.\n- **Whole-table wipes and schema destruction** — no-`WHERE` `DELETE` / `UPDATE`\n  and all `DROP` / `TRUNCATE` / `ALTER`, regardless of table policy.\n- **Stacked-statement injection** — `SELECT 1; DROP TABLE users` denied at parse time.\n- **Writes hidden inside a read** — `WITH x AS (DELETE FROM users RETURNING *)\n  SELECT * FROM x` is denied at the inner `DELETE`, not the outer `SELECT`. The\n  same recursive walk covers subqueries, UNION arms, and JOINs.\n\nThe full policy model, the parse → policy → mask → audit pipeline, and the\nadversarial corpus that pins it are documented at\n[midplane.ai/docs](https://midplane.ai/docs).\n\n## In a real chat\n\nA `delete all users` prompt to Claude Code, against a Midplane-fronted DB:\n\n> ⏺ Hold on — that's destructive and irreversible. Before I run\n> `DELETE FROM public.users`, please confirm.\n>\n> *user: confirm delete all users*\n>\n> ⏺ Midplane blocked it: *\"writes to table `users` are not allowed by the\n> table-access policy.\"* The DELETE was audited but not executed.\n\n## Quick start\n\nThree ways in — same open-core codebase behind all of them. Step-by-step guides\nare at **[midplane.ai/docs](https://midplane.ai/docs)**.\n\n### Managed cloud\n\nThe fastest way to try Midplane: **[sign up at app.midplane.ai](https://app.midplane.ai)**\nand go from zero to your first guarded query in a couple of minutes. Dashboard,\npolicy and masking editors, approval queue, hosted audit log, agent-token\nissuance. Nothing to install, multi-region, fully supported.\n\n### Guard one database yourself\n\nPut the MIT engine in front of a Postgres database and point an agent at it.\nNothing to install — `npx` ships with Node and fetches the\n[`midplane`](https://www.npmjs.com/package/midplane) package on first run\n(needs Node 22.16+; on anything older it says so and exits). Add this to your\nMCP client's config (Claude Code, Claude Desktop, Cursor — they all take this\nshape):\n\n```json\n{\n  \"mcpServers\": {\n    \"midplane\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"midplane\", \"server\", \"--stdio\"],\n      \"env\": { \"DATABASE_URL\": \"postgres://user:pass@host:5432/db\" }\n    }\n  }\n}\n```\n\nKeep the connection string in that `env` block rather than on a command line,\nwhere it would leak to `ps aux` and your shell history. The block still lands in\na plaintext config file, so give Midplane its own least-privilege Postgres role:\nit governs which SQL runs, not what the role underneath it can reach.\n\nThat config is already the safe default: reads allowed, writes and DDL denied,\nevery query audited to `~/.midplane/audit.db`. Read the log back with\n`npx midplane audit denies`. To open specific tables up, generate a policy with\n`npx midplane init` — it introspects your schema over a read-only connection,\nsuggests a tenant column, and writes a validated `midplane.policy.yaml`.\n\nMasking and approvals are sections of that same policy file: `column_masks` names\nthe columns to mask and the transform to apply (set `MIDPLANE_MASK_SALT`, and\n`mask_source_rewrite: true` for source rewriting), and `approvals` holds writes\nuntil a human rules on them. Approvals need somewhere to ask — point the engine at\nthe app's gate (`MIDPLANE_APPROVAL_URL` + `MIDPLANE_APPROVAL_TOKEN`), self-hosted\nor cloud, both below.\n\n> For a CI pipeline or a long-lived sidecar, the same engine ships as a\n> self-contained image with no Node in it — `midplane/midplane:0.20.0`, serving\n> Streamable HTTP instead of stdio.\n> [Setup](https://midplane.ai/docs) · [`engine/README.md`](./engine/README.md).\n\n### Self-host the whole app\n\nThe complete single-tenant product — dashboard, policy and masking editors,\napproval queue, audit log, agent-token issuance — keyless and uncapped, on your\nown Postgres. Docker is the only prerequisite:\n\n```bash\ngit clone https://github.com/midplaneai/midplane && cd midplane\n./bin/self-host up                               # → http://localhost:3000\n```\n\nThat generates secrets into `.env.self-host`, brings up Postgres + the web app,\napplies migrations on boot, and prints the dashboard URL — the first\nemail+password signup becomes the owner.\n\nRunning from source, the single-image deploy, the engine-spawn topology, and the\nfull walkthrough: [midplane.ai/docs](https://midplane.ai/docs) (in-repo:\n[`SELF_HOST.md`](./SELF_HOST.md)).\n\n## Open core\n\nMidplane is **open core, MIT, and self-hostable.** Everything outside\n`apps/web/src/ee/` is the Community Edition — the whole single-tenant product,\nuncapped when self-hosted. `apps/web/src/ee/` is the commercial Enterprise Edition\n(SSO/SAML today; the governance band over time); deleting it leaves a working MIT\nbuild. The managed cloud is the same codebase and the supported, paid path. See\n[`LICENSE`](./LICENSE) for the MIT terms and [`NOTICE`](./NOTICE) for the `ee/`\ncarve-out.\n\n## Architecture\n\nOne codebase, two deployables:\n\n- **Control plane** (repo root) — dashboard, policy and masking management,\n  approval queue, audit views, agent-token issuance, hosted MCP proxy. MIT except\n  `apps/web/src/ee/`.\n- **Engine** ([`engine/`](./engine)) — the MIT query-path engine, compiled to a\n  self-contained binary. It parses, enforces, masks, and audits; the control plane\n  spawns it per project and never reimplements it, so hosted and self-host run the\n  exact same engine — only the packaging differs.\n\n```\napps/web              Next.js dashboard + Better Auth + projects API\npackages/db           Drizzle schema (customers, projects, audit index)\npackages/kms          encryptDsn / decryptDsn (env-mode dev, AWS KMS prod)\npackages/router       Hosted MCP request handler — token → project → engine\nengine/               The MIT query-path engine\ninfra/telemetry-proxy Cloudflare Worker for anonymized OSS install telemetry\n```\n\nOperating the managed multi-region cloud (Fly + Neon + KMS) is in\n[`docs/deploy.md`](./docs/deploy.md).\n\n## Contributing\n\nIssues and PRs welcome — start with [`CONTRIBUTING.md`](./CONTRIBUTING.md). The\nsingle highest-leverage contribution is a new entry in the adversarial SQL corpus:\na bypass attempt and the policy fix that defeats it. Commits are DCO-signed\n(`git commit -s`). For security issues, follow [`SECURITY.md`](./SECURITY.md) —\ndon't open a public issue.\n\n## License\n\nMIT — see [`LICENSE`](./LICENSE). No copyleft, no BSL, no source-available rug-pull.\nThe one carve-out is `apps/web/src/ee/` (the commercial Enterprise Edition, governed\nby [`apps/web/src/ee/LICENSE`](./apps/web/src/ee/LICENSE) and recorded in\n[`NOTICE`](./NOTICE)); deleting it leaves a fully working MIT build.\n\n---\n\n**More:** [Docs](https://midplane.ai/docs) · [Pricing](./PRICING.md) ·\n[Support](./SUPPORT.md) · [Design system](./DESIGN.md) ·\n[Code of Conduct](./CODE_OF_CONDUCT.md)\n",
  "bytes": 10741,
  "sha": "fc199742b9eff3cb06f651474c869ce5a71f7756e257af4a040d04649a6e7270",
  "repo_slug": "midplaneai/midplane",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_ai_midplane_midplane_b1cf949a/readme"
}