> ## 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.

# Connect your agent

> Point any MCP client at Midplane's single endpoint, sign in once, and grant the agent least-privilege access.

Midplane exposes a single [MCP](https://modelcontextprotocol.io/) endpoint. Point your agent's MCP config at it, and from then on the agent reaches your database only through Midplane — every query is parsed, checked against your [policy](/docs/policies/overview), and written to the [audit trail](/docs/concepts/audit-trail) before it runs.

There's no per-agent integration to build and no allow-list to get onto. Any client that speaks MCP connects the same way — Cursor, Claude Code, Claude Desktop, ChatGPT, VS Code, Windsurf, Zed, and others. The client registers itself, you sign in once, and you choose exactly which databases that agent can reach. The only thing that differs between clients is where you paste the URL — see [connect your client](#connect-your-client).

## Connect your client

Every client reaches the same [endpoint](#the-endpoint) and signs in once over OAuth — only *how* you point it there differs. Pick your client below; each uses the Cloud endpoint URL, so swap in `http://localhost:3000/mcp` for self-host. If the tools don't appear, see [troubleshooting](/docs/resources/troubleshooting).

<Tabs>
  <Tab title="Cursor">
    **One-click** — install from a link (pick your region): [Add to Cursor · EU](cursor://anysphere.cursor-deeplink/mcp/install?name=midplane\&config=eyJ1cmwiOiJodHRwczovL2V1Lm1pZHBsYW5lLmFpL21jcCJ9) · [Add to Cursor · US](cursor://anysphere.cursor-deeplink/mcp/install?name=midplane\&config=eyJ1cmwiOiJodHRwczovL3VzLm1pZHBsYW5lLmFpL21jcCJ9)

    Or add it by hand — Cursor reads MCP servers from `~/.cursor/mcp.json` and auto-detects Streamable HTTP from the URL, so no `transport` or `type` key is needed:

    ```json ~/.cursor/mcp.json theme={null}
    {
      "mcpServers": {
        "midplane": {
          "url": "https://{eu|us}.midplane.ai/mcp"
        }
      }
    }
    ```

    Either way, Cursor walks you through the OAuth sign-in, then its MCP panel lists the Midplane tools.
  </Tab>

  <Tab title="Claude Code">
    Register from the command line with one `claude mcp add` command. The `--transport http` flag is required:

    ```bash theme={null}
    claude mcp add --transport http midplane https://{eu|us}.midplane.ai/mcp
    ```

    <Warning>
      `--transport http` is **required**. Without it, the CLI defaults to stdio and treats the URL as a launcher command to execute — which fails silently. If Midplane never connects, this flag is almost always why.
    </Warning>

    The first call triggers the OAuth sign-in. Confirm it registered with `claude mcp list`.
  </Tab>

  <Tab title="VS Code">
    **One-click** — install from a link (pick your region): [Install in VS Code · EU](vscode:mcp/install?%7B%22name%22%3A%22midplane%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A//eu.midplane.ai/mcp%22%7D) · [Install in VS Code · US](vscode:mcp/install?%7B%22name%22%3A%22midplane%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A//us.midplane.ai/mcp%22%7D)

    Or add it by hand — run **MCP: Add Server** from the Command Palette and give it the endpoint URL over HTTP, or drop it into `.vscode/mcp.json`:

    ```json .vscode/mcp.json theme={null}
    {
      "servers": {
        "midplane": {
          "type": "http",
          "url": "https://{eu|us}.midplane.ai/mcp"
        }
      }
    }
    ```

    VS Code then walks you through the OAuth sign-in and lists the Midplane tools.
  </Tab>

  <Tab title="Claude Desktop">
    Claude Desktop attaches a remote MCP server two ways, depending on your deployment:

    * **Cloud** — the HTTPS endpoint drops straight into **Settings → Connectors → Add custom connector**. Name it `midplane`, paste the endpoint, save, then sign in once when prompted.
    * **Self-host** — the Custom Connectors UI rejects `http://localhost`, so use the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) stdio shim. Open your `claude_desktop_config.json` from **Settings → Developer → Edit Config** (the path varies by OS) and add:

    ```json claude_desktop_config.json theme={null}
    {
      "mcpServers": {
        "midplane": {
          "command": "npx",
          "args": ["-y", "mcp-remote", "http://localhost:3000/mcp"]
        }
      }
    }
    ```

    `mcp-remote` is fetched on demand by `npx`, so you need Node.js on your `PATH`.

    <Warning>
      **Restart Claude Desktop after editing the file.** New MCP servers don't hot-reload.
    </Warning>
  </Tab>

  <Tab title="Any other client">
    Any client that supports remote MCP servers over OAuth connects the same way — ChatGPT, Windsurf, Zed, and others. There's nothing Midplane-specific to install:

    1. Open your client's **add MCP server** (or **add connector**) setting.
    2. Give it the endpoint URL — `https://{eu|us}.midplane.ai/mcp` (Cloud) or `http://localhost:3000/mcp` (self-host). If the client asks for a transport, choose **Streamable HTTP**.
    3. The client registers itself and walks you through the OAuth sign-in. Approve the scope, and the Midplane tools appear.

    If your client can't reach a remote MCP server directly, bridge it with the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) stdio shim shown in the Claude Desktop tab.
  </Tab>
</Tabs>

### Or let your agent connect itself

If your client is an agentic CLI or IDE that can run its own commands — Claude Code, Cursor, Windsurf, Codex, and the like — let it wire itself up instead. Pick your deployment and paste the prompt into that client; it adds Midplane, then hands back to you for the one-time browser sign-in. A UI-only client like Claude Desktop or ChatGPT has no shell for the agent to drive, so use the by-hand tabs above.

<Tabs>
  <Tab title="Cloud · EU">
    ```text Setup prompt theme={null}
    Add Midplane as an MCP server in this client, over Streamable HTTP (MCP `type: "http"`).
    My endpoint is https://eu.midplane.ai/mcp.

    Add it using whatever this client uses to register a remote MCP server — its own
    CLI, config file, or add-server command. Setup details vary by client and version,
    so use the client's current method rather than a fixed command. One known gotcha:
    in Claude Code, `claude mcp add` needs the `--transport http` flag, or it treats
    the URL as a stdio command and fails silently.

    Don't invent or ask me for an auth token — Midplane authorizes over OAuth. After
    you add the server, trigger a connection and hand back to me to sign in in the
    browser. Once it's connected, confirm the Midplane tools (`query`, `list_tables`,
    `describe_table`) appear.
    ```
  </Tab>

  <Tab title="Cloud · US">
    ```text Setup prompt theme={null}
    Add Midplane as an MCP server in this client, over Streamable HTTP (MCP `type: "http"`).
    My endpoint is https://us.midplane.ai/mcp.

    Add it using whatever this client uses to register a remote MCP server — its own
    CLI, config file, or add-server command. Setup details vary by client and version,
    so use the client's current method rather than a fixed command. One known gotcha:
    in Claude Code, `claude mcp add` needs the `--transport http` flag, or it treats
    the URL as a stdio command and fails silently.

    Don't invent or ask me for an auth token — Midplane authorizes over OAuth. After
    you add the server, trigger a connection and hand back to me to sign in in the
    browser. Once it's connected, confirm the Midplane tools (`query`, `list_tables`,
    `describe_table`) appear.
    ```
  </Tab>

  <Tab title="Self-host">
    ```text Setup prompt theme={null}
    Add Midplane as an MCP server in this client, over Streamable HTTP (MCP `type: "http"`).
    My endpoint is http://localhost:3000/mcp.

    Add it using whatever this client uses to register a remote MCP server — its own
    CLI, config file, or add-server command. Setup details vary by client and version,
    so use the client's current method rather than a fixed command. One known gotcha:
    in Claude Code, `claude mcp add` needs the `--transport http` flag, or it treats
    the URL as a stdio command and fails silently.

    Don't invent or ask me for an auth token — Midplane authorizes over OAuth. After
    you add the server, trigger a connection and hand back to me to sign in in the
    browser. Once it's connected, confirm the Midplane tools (`query`, `list_tables`,
    `describe_table`) appear.
    ```
  </Tab>
</Tabs>

Prefer to understand what's happening under the hood? Read on for [the endpoint](#the-endpoint), [what you grant at consent](#what-you-grant-at-consent), and the rest of the flow.

## How connecting works

It's the same three steps for every client:

<Steps>
  <Step title="Point your client at the endpoint">
    Add Midplane as an MCP server, using the [endpoint URL](#the-endpoint) below. No token to paste.
  </Step>

  <Step title="Sign in once">
    The first time your client connects, it registers itself and hands you off to Midplane to sign in over **OAuth**. This is standard MCP authorization — the client does it automatically; you never copy a client ID or secret.
  </Step>

  <Step title="Grant least-privilege scope">
    At sign-in you approve the agent and choose its scope: which [project](/docs/cloud/projects) it connects to, which of that project's databases it can reach, and whether each is `read` or `read_write`. See [what you grant at consent](#what-you-grant-at-consent).
  </Step>
</Steps>

Once connected, the agent's [tools](#the-tools-your-agent-gets) appear in your client, and every call it makes runs through the engine.

## The endpoint

The endpoint URL depends on how you run Midplane:

<Tabs>
  <Tab title="Cloud URL">
    ```
    https://{eu|us}.midplane.ai/mcp
    ```

    Your account's MCP endpoint, with the region in the host. There's no token or project id in the path — the URL is an address, not a secret, so it's safe to copy and share. Your client registers itself and you sign in once over OAuth; the project it reaches is the one you pick at consent. Copy the URL from the [dashboard](https://app.midplane.ai). See [projects](/docs/cloud/projects).

    To bind a client straight to one project (handy when a client reaches several), use the explicit form `https://{eu|us}.midplane.ai/mcp/<projectId>`.
  </Tab>

  <Tab title="Self-host URL">
    ```
    http://localhost:3000/mcp
    ```

    The local control-plane URL your deployment exposes. See [deploy](/docs/self-host/deploy) to boot it.
  </Tab>
</Tabs>

### Transport

Midplane speaks MCP over **Streamable HTTP**, the current MCP transport — one endpoint that handles every call. Most clients (Cursor, Claude Code, ChatGPT, VS Code) connect to that URL directly. A few — notably Claude Desktop against a `http://localhost` self-host endpoint — need a small **stdio** shim ([`mcp-remote`](https://www.npmjs.com/package/mcp-remote)) that bridges stdio to the same HTTP endpoint.

## What you grant at consent

Sign-in is also where you set the agent's scope — least privilege, chosen the moment it connects. The consent screen names the agent asking to connect (by the name it registered under) and asks you to grant:

* **One project.** Each agent credential is bound to a single [project](/docs/cloud/projects). If your account has one project it's selected for you; with several, you pick — Midplane never silently defaults the choice, so an agent can't bind to the wrong project.
* **Which databases.** Check only the databases on that project the agent should reach. Nothing is checked by default — granting is opt-in, per database.
* **`read` or `read_write` for each.** The least-privilege default is `read`.

The grant is enforced on every request: the agent reaches only the databases you checked, at the access you set. In Cloud, an agent with no grant is **denied** — the scope fails closed. (Self-host is single-tenant, so an empty grant instead leaves the owner's agent *unscoped* — full reach across the project, still bounded by [policy](/docs/policies/overview) — rather than locking the owner out of its own data.) Midplane also shows the identity details the client reads (your name, your email), so there are no hidden scopes.

<Note>
  Per-agent scope sits on top of the project [policy](/docs/cloud/security) — it doesn't replace it. The policy is the ceiling for every agent on the project; the consent grant narrows one credential below that ceiling. See [per-agent database scope](/docs/cloud/projects#per-agent-database-scope).
</Note>

To change an agent's scope later, reconnect it — the consent screen pre-selects its current grant — or [revoke](#manage-connected-agents) it.

## Machine and CI agents

OAuth covers interactive agents. For headless callers — CI jobs, scheduled tasks, anything without a browser — mint a [machine token](/docs/cloud/projects#machine-tokens) instead and present it as a bearer header:

```
Authorization: Bearer mp_live_…
```

A machine token carries the same kind of scope as the consent screen — `read` or `read_write` per database — except you set it when you mint the token. The token is a secret, shown once at creation; store it like any other credential. Clients that can't set a header can use the legacy `https://{eu|us}.midplane.ai/mcp/<token>` URL form.

## Manage connected agents

In Cloud, every agent connected to a project — interactive OAuth clients and headless tokens alike — appears in the project's **Agents** pane. **Revoke** any agent and its access stops on the next request — there's no waiting for a credential to expire. Revoke the moment a laptop or runner goes missing. See [projects & access](/docs/cloud/projects) for the Agents pane and the machine-token lifecycle.

## The tools your agent gets

Once connected, the agent sees a small, fixed set of tools:

| Tool             | What it does                                                                                                                                                |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `query`          | Run a single SQL statement through the policy engine.                                                                                                       |
| `list_tables`    | List the tables the agent can see.                                                                                                                          |
| `describe_table` | Return a table's columns and types.                                                                                                                         |
| `list_databases` | List each database and its policy summary. Registered only when you configure [multiple databases](/docs/policies/overview#serve-multiple-databases) (`0.2.0+`). |

Every `query` call also carries an `intent` — a short, plain-language "why," filled in by the agent automatically, that lands on the [audit row](/docs/concepts/audit-trail) next to the SQL and the decision. It's recorded for the audit trail only; it never relaxes or tightens a decision, which is made on the parsed SQL. For the full argument and response shapes, see the [MCP tools reference](/docs/reference/mcp-tools).

## Try it

With the tools connected, run these prompts to confirm the engine is enforcing policy. The first is an allowed read; the rest each hit a different rule.

<Steps>
  <Step title="List the tables — allowed">
    *"What tables are in the database?"*

    The agent calls `list_tables` and returns your tables. This is a read, so the default read-only policy allows it.
  </Step>

  <Step title="Delete all users — denied by table access">
    *"Delete all users."*

    The agent calls `query` with `DELETE FROM users`. Midplane denies it: writes are read-only by default, so the `table_access` rule blocks the statement, naming the table and the policy key you'd flip to grant the write. See [table access](/docs/concepts/table-access).
  </Step>

  <Step title="Stacked statements — denied by multi-statement">
    *"Run this SQL: `SELECT 1; DROP TABLE users`"*

    Two statements in one call. The `multi_statement` rule denies it at parse time — the stacked `DROP` never reaches the database.
  </Step>
</Steps>

Each call lands in the [audit trail](/docs/concepts/audit-trail). An allowed query records `ATTEMPTED → DECIDED → EXECUTED`; a denied query stops at `ATTEMPTED → DECIDED` and never executes.

## Next steps

<Columns cols={2}>
  <Card title="Write a policy" icon="shield-check" href="/docs/policies/overview" horizontal>
    Grant the reads and writes your agent actually needs.
  </Card>

  <Card title="Read the audit trail" icon="scroll-text" href="/docs/concepts/audit-trail" horizontal>
    See every attempt, decision, and result your agent produced.
  </Card>
</Columns>
