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

# Connect an agent

> Paste one config block into Claude Code or Cursor. Claude Desktop needs a bridge. claude.ai and ChatGPT need a public address first.

<Info>
  **Before you start**

  * **Postern, installed and started.** Open the Console at `http://localhost:8787`.
    If Postern runs on another computer, [open the Console over
    SSH](/start/install#open-console) first.
  * **An agent key, in front of you.** Postern shows a key once, then keeps only a
    scrambled copy. If you have none, [create one](/start/first-key).
  * **The agent you want to connect.** You restart it at the end of this page.
  * **claude.ai or ChatGPT only: a public https address on port 443, and a key with
    an expiry.** [Set up remote access](/start/remote-access#publish) builds the
    address.
</Info>

## Choose your path

MCP is the standard way AI assistants connect to outside tools. Postern speaks it,
so Claude, ChatGPT and Cursor connect the same way.

| Your agent                                                                            | Then                                     |
| ------------------------------------------------------------------------------------- | ---------------------------------------- |
| Claude Code or Cursor — on the same computer as Postern, or on your Tailscale network | [copy the config block](#connect-config) |
| Claude Desktop — it can only start a program on your computer                         | [add the bridge](#stdio-bridge)          |
| claude.ai or ChatGPT — they run on the vendor's servers                               | [publish an address first](#hosted)      |

<Steps>
  <Step title="Check what your key opens" titleSize="h2" id="agent-key">
    Open Console → **Agents & keys**. Your keys sit under **The register**. Read the
    **Granted sectors** column for the key you are about to use.

    A sector is one area of your life: finance, mail, calendar, contacts, health, home.
    It is the smallest thing you can grant.

    A new key can reach nothing until you tick a sector. A key with no sectors connects
    successfully, and Postern then denies every read. The form warns you under the button: `No
        sectors granted yet — this key would be denied everything until you edit its
        grants.` A connection that succeeds can still answer nothing.
    [Default-deny](/reference/grants-and-sectors#default-deny).

    A grant covers read and act across every provider in that sector, and there is
    nothing narrower. Only Home Assistant has anything to act on today.
    [What a grant covers](/reference/grants-and-sectors#what-a-grant-covers).

    To widen a key, click the agent's name and tap the sector you want under
    **Grants**. Postern saves at once and prints `Tap to toggle; the change applies at
        the agent's next session.` Postern ends that agent's live sessions when you change
    a grant, so the new sectors apply the next time it connects. The key itself does
    not change, so you paste nothing a second time. [Read a grant, and change
    it](/reference/grants-and-sectors#read-a-grant-and-change-it).

    To replace a key you have lost, rotate it:

    1. Press **Rotate** on that key's row.
    2. Tick sectors under **Grant which sectors**.
    3. Press **Rotate the key**. Postern shows you the new key once.

    The old key dies at that moment, along with every token made from it. Postern
    disconnects any agent that holds it. [What revoke and rotate
    destroy](/reference/agent-keys#revoking-rotating-and-removing).

    <Warning>
      If this key will go into claude.ai or ChatGPT, set an expiry before you create it.
      The sign-in refuses a key that never expires: `This agent key never expires. Keys
              used with a cloud client must have an expiry — mint or rotate a key with a
              30/90/365-day expiry in the Console, then try again.` The chips under **Expires**
      read **Never**, **30 days**, **60 days** and **90 days**. There is no 365-day
      chip. [Set one when you create the key](/start/first-key#expiry).
    </Warning>
  </Step>

  <Step title="Copy the config block from the Console" titleSize="h2" id="connect-config">
    Open Console → **Agents & keys** and scroll to the bottom of the page. The last
    section reads **Connect an agent**. It shows **MCP endpoint** with its own **Copy**
    button, then three columns: **Local · stdio**, **Remote · bearer** and
    **Hosted · claude.ai, ChatGPT**.

    1. Under **Remote · bearer**, press **Copy recipe · keyless**. The Console answers
       `Recipe copied — paste your key where marked`.
    2. Paste it into a text editor. It looks like this:

    ```json theme={"system"}
    {
      "mcpServers": {
        "postern": {
          "type": "http",
          "url": "http://localhost:8788/mcp",
          "headers": {
            "Authorization": "Bearer <your-agent-key>"
          }
        }
      }
    }
    ```

    3. Replace `<your-agent-key>` with your key. **Delete the angle brackets too.**
       Postern refuses `Bearer <abc123>`. It accepts `Bearer abc123`.

    Use the address the Console prints, not the one above. Your port may differ.
    [The endpoint](/reference/mcp-primitives#the-endpoint).

    **If you created the key a moment ago**, use the block on that screen instead. Press
    **Copy config · key included**. It already holds your key.

    <Note>
      Where the address should be, the Console prints a sentence instead. Which sentence
      tells you what to do.

      `This gateway isn’t serving MCP, so it has no address to point an agent at. Set
              MCP_HTTP_PORT on the box and restart it.` — or, on the screen after you create a key, `This
              gateway isn’t serving MCP yet, so it has no address to pair with this key.` Both
      mean the same thing. Open `.env` beside `docker-compose.yml`. Add the line
      `MCP_HTTP_PORT=8788`. Save it. In that same folder, run `docker compose up -d`.

      `This gateway didn’t answer with its exposure state — the request failed, which
              says nothing about how the gate is configured. Reload to try again.` — the
      Console's own request failed. Reload the page.
    </Note>
  </Step>

  <Step title="Paste the block into your agent and restart it" titleSize="h2" id="paste-and-restart">
    Open your agent's MCP config file — Claude Code's `.mcp.json`, Cursor's `mcp.json`.
    Create the file if it is not there.

    Paste the **whole block**, from the first `{` to the last `}`. Save it. Then quit the
    agent and start it again.

    Your agent now lists seven Postern tools: `describe_context`, `get_schema`, `query`,
    `get_record`, `fetch_live`, `list_actions` and `invoke_action`.

    **Claude Code takes the same thing as one command.** It writes your key into your
    shell history, which the pasted block avoids:

    ```bash theme={"system"}
    claude mcp add --transport http postern http://localhost:8788/mcp --header "Authorization: Bearer <your-agent-key>"
    ```

    That command does not touch `.mcp.json`. Where it writes is `--scope`, and the
    default is `local` — the folder you run it in, and no other. Run it in the folder you
    work in, or pass `--scope user` or `--scope project`.

    <Note>
      Your agent runs on a different computer from Postern, and the connection fails or
      times out. `http://localhost:8788/mcp` answers only on the machine Postern runs on.
      Put Postern on your Tailscale network first: [open two ports to your own
      devices](/start/remote-access#your-devices). Then use the tailnet address that step
      hands you. An SSH tunnel to the Console does not reach the agent port.
    </Note>
  </Step>

  <Step title="Bridge Claude Desktop with mcp-remote" titleSize="h2" id="stdio-bridge">
    Claude Desktop cannot open a connection to an address. It can only start a program on
    your computer and talk to it. Postern is not that program. It already runs, and it
    already holds its own input.
    [Postern offers no stdio](/reference/mcp-primitives#postern-offers-no-stdio).

    Postern supports Claude Desktop through a bridge that Claude Desktop starts for
    itself: `mcp-remote`. To Claude Desktop the bridge is a program; to Postern it is an
    ordinary agent. It needs the same two values as every block on this page — the `/mcp`
    address, and the header `Authorization: Bearer <your-agent-key>`. Take both from
    **Remote · bearer** in Console → **Agents & keys**, which is where the Console sends
    you too.

    Claude Desktop reads a file called `claude_desktop_config.json`. On macOS it sits at
    `~/Library/Application Support/Claude/claude_desktop_config.json`.

    <Note>
      **No block to paste yet.** Nobody has run `mcp-remote` against Postern and recorded
      what worked, so this page prints no `command` and no `args` rather than a guess.
      The tool's own documentation carries them:
      [mcp-remote](https://github.com/geelen/mcp-remote). Claude Code and Cursor are the
      checked paths today.
    </Note>
  </Step>

  <Step title="Publish an address, then connect claude.ai or ChatGPT" titleSize="h2" id="hosted">
    claude.ai and ChatGPT connect from the vendor's own servers. They can never reach
    your own computer or your Tailscale network, and they connect on port 443 only — the
    standard port for secure websites. Postern publishes nothing until you ask.

    **Publish first.** Open Console → **Settings** → **Remote access** → the row
    **Public · the address this gate answers on**. [Set up remote
    access](/start/remote-access#publish) walks the whole of it. Use port 443. On any
    other port a hosted agent's request never reaches you at all, and no log at either
    end shows it.

    <Warning>
      Save your agent key somewhere safe before you copy anything else. The sign-in asks
      for the key at the **end**, in another app, after the Copy button below has
      replaced it on your clipboard. Witnessed 2026-08-01: the address went into the key
      field, and the sign-in answered `That agent key was not accepted. Check the key in
              the Console and try again.`
    </Warning>

    **Copy the address.** Open Console → **Agents & keys** → **Set up the bridge →**. The
    reveal screen's **Connect a hosted client (claude.ai, ChatGPT) →** opens the same
    page. Under **Paste this into claude.ai or ChatGPT**, press **Copy**. The value is
    your published address plus `/mcp` — if Settings saved
    `https://your-mac.tailnet.ts.net`, this reads `https://your-mac.tailnet.ts.net/mcp`.
    The Console shows that **Copy** button only for an address a hosted agent can use:
    `https`, on port 443.

    **Connect claude.ai.** In claude.ai, open its settings and find its connectors. Add
    a connector of your own. Paste in the address you copied. If it asks for a client id
    or a client secret, leave both empty. Postern issues no client secret, and it
    registers the agent itself.

    <Note>
      **Nobody has captured claude.ai's dialog, so this page names nothing on it.** One run,
      on 2026-07-21, is the whole record. It offered a client id and a client secret, the
      founder left both empty, and the connection worked. What claude.ai calls the address
      field is unknown. Whether it ever asks how you want to sign in is unknown too. If
      yours asks, the Console's own advice is to choose OAuth. One screenshot of that dialog
      settles both.
    </Note>

    claude.ai then opens a page your own Postern serves, headed **Authorize agent
    access**. It names the app — `Claude`, for claude.ai — and the address your sign-in
    code goes to. Under **Paste an agent key from the Console**, paste your agent key.
    Press **Approve**. claude.ai holds a token that rotates from then on. That sign-in
    page turns on with the address; there is no separate switch. [The OAuth bridge, for
    hosted agents](/reference/oauth-bridge).

    **Connect ChatGPT.** ChatGPT's path is longer. It adds a developer-mode switch, a
    page that carries three different names, and an approval popup served by your own
    machine. [Connect ChatGPT](/connect/chatgpt) walks it end to end.
  </Step>
</Steps>

## Confirm it works

* Your agent lists seven Postern tools after the restart.
* You type `What context can you reach?` into the agent. It answers with the sectors
  you ticked for that key, and how fresh each connection is — and nothing outside them.
* Console → **The ledger** shows a new row: your agent's name under **Who**,
  `describe_context` under **Tool**. Leave the **Sector** filter off —
  `describe_context` belongs to no sector, and that filter drops exactly those rows.
* That row's **Decision** column is empty, not `deny`.

## If something went wrong

| What you see                                                                                                                                                                                   | What to do                                                                                                                                                                                                                                                                                                                                                         |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Your agent lists no Postern tools after the restart                                                                                                                                            | Check three things. You pasted the whole block, from the first `{` to the last `}`. You deleted the angle brackets around `<your-agent-key>`. You quit the agent and started it again, rather than reload a window. [Paste the block and restart it](#paste-and-restart).                                                                                          |
| `This gateway isn’t serving MCP, so it has no address to point an agent at. Set MCP_HTTP_PORT on the box and restart it.`                                                                      | Open `.env` beside `docker-compose.yml`. Add the line `MCP_HTTP_PORT=8788`. Save it. In that same folder, run `docker compose up -d`. [Copy the config block](#connect-config).                                                                                                                                                                                    |
| The agent connects, and every read comes back denied                                                                                                                                           | That key opens no sectors. Console → **Agents & keys** → the agent's name → tap a sector under **Grants**. The key does not change; the agent's session ends there, so connect it again. [Check what your key opens](#agent-key).                                                                                                                                  |
| The agent answers empty, and Console → **The ledger** shows no new row                                                                                                                         | Nothing reached Postern. An agent can describe a call it never made, so trust the record over the answer. A key that arrived and that Postern refused writes a row of its own: no name under **Who**, `initialize` under **Tool**, and nothing under **Sector** — so leave the **Sector** filter off to see it. Check the address and the key, then try once more. |
| claude.ai says `Couldn't connect to the server. Check that the URL points to a valid MCP server.`, or `Couldn't reach Postern. You can check the server URL and verify the server is running.` | claude.ai could not reach you. Both are about the address, and the usual cause is a port other than 443. Fix the address, wait five minutes, then try once. [Publish an address](#hosted).                                                                                                                                                                         |

## What you have now

An agent that holds a key, and that key opens exactly the sectors you ticked. It
reads from Postern's local cache. It can act only where the source allows, which
today means Home Assistant alone.

Every tool call that agent makes — served, refused or failed — lands in the record
under its name. Postern only ever adds to the record. [What gets a
row](/reference/audit-log#what-gets-a-row). What you do to the key is not there.
Postern writes no row when you create a key, rotate one, edit its sectors or its
deadline, revoke it or remove it. [What does not get a
row](/reference/audit-log#what-does-not-get-a-row).

Your config file now holds your agent key. Store it where your other passwords live.
[Agent keys](/reference/agent-keys#what-an-agent-key-is).

With no sources connected, your agent connects fine and answers empty. The Console
reads **Add your first source**. That is the next thing to do, not a repair.

## Next

Pick the source you can finish now.

<Columns cols={2}>
  <Card title="Connect Google and Gmail" href="/connect/google">
    your own Google Cloud app + one app password · about 20 minutes · weekly
    re-consent until you publish to production
  </Card>

  <Card title="Connect Apple Health" href="/connect/apple-health">
    Health Auto Export on your iPhone · about 10 minutes · push, not pull
  </Card>
</Columns>

The rest, by sector:

* **Finance** — [SimpleFIN](/connect/simplefin), [Plaid](/connect/plaid)
* **Health** — [WHOOP](/connect/whoop)
* **Mail, calendar and contacts** — [Microsoft](/connect/microsoft), [iCloud](/connect/icloud)
* **Home** — [Home Assistant](/connect/home-assistant)

You need not change any agent when you connect a source. A granted sector covers
every provider in it, now or later.
