Skip to main content
Before you start
  • A running Postern of your own. Every address here belongs to one install. There is no shared address. Install Postern.
  • An agent key. The sectors granted to it decide what every tool below answers. Connect an agent.
  • An agent that speaks MCP over Streamable HTTP. That is the one transport Postern offers. Most agents speak it.
  • The address, copied from the Console. A guessed address looks right and silently does not work.
MCP is the standard way AI assistants connect to outside tools. Postern speaks it, so Claude, ChatGPT and Cursor connect the same way. Nothing on this page was captured from a run. The shapes are the declared types, and the quoted strings are Postern’s own.

The seven tools

Seven tools, three pages. Each page answers one question, so the traps that belong together stay together. A hosted agent — claude.ai or ChatGPT, running on a company’s servers — cannot send a fixed key at all, and signs in through a bridge instead. The OAuth bridge.

The endpoint

Postern listens on two ports. Agents use the agent port. The Console uses the Console port. Everything here is the agent port, except the REST read mirror. The agent port answers POST, GET and DELETE on the path /mcp. Until you give Postern a public address, that is the only path it serves. A session starts with a POST that carries an initialize request and no session header. Postern reads the agent key, resolves it once, and returns an Mcp-Session-Id. Every later request carries that header. DELETE ends the session. Past 512 sessions Postern refuses a new one and never drops a live one. Only a caller that already holds a valid key reaches that check.

Copy the address from the Console

  1. In the left nav, click Agents & keys.
  2. Scroll to the Connect an agent band.
  3. Read the value under MCP endpoint.
  4. Click Copy beside it.
Postern reports the address it is actually serving. Do not assemble one by hand. If that band prints 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., no agent port is running. The port is a setting, not a constant, and 8788 is the default. To move it:
  1. On the machine Postern runs on, open .env — the settings file beside docker-compose.yml.
  2. Set MCP_HTTP_PORT to the port you want. Save the file.
  3. In that same folder, run docker compose up -d.
Postern serves the new port after that restart.

Postern offers no stdio

Some agents can only start a program and talk to it directly. That is stdio, and Postern does not offer it. stdio works only when your agent starts Postern itself, as its own child process. The Postern that serves your Console already runs and already owns its stdin. Under the Docker setup there is no stdin to reach at all. A second Postern beside it would use its own encryption key, and it could open none of the accounts you have connected. Postern does hold one stdio session of its own, on its own stdin. That session resolves as the local operator, with every sector granted and no key involved. That is exactly why Postern never offers it to an agent. An agent that only speaks stdio needs mcp-remote in front of the same URL. The Console’s own answer is the Remote · bearer config block below.

Claude Desktop

Claude Desktop is supported, through mcp-remote. It takes the same /mcp address and the same Authorization: Bearer <your-agent-key> header as every other client. It reaches the same agent port and meets the same key check. Its config file is claude_desktop_config.json; on macOS that is ~/Library/Application Support/Claude/claude_desktop_config.json.
Nobody at Postern has run that recipe end to end. The exact command and args for the mcp-remote bridge come from that project’s own documentation, not from a run against Postern. So this page does not print them as a verified block. Copy the Remote · bearer config from the Console, put mcp-remote in front of the URL, and expect to adjust it.

How a client authenticates

Nobody types an HTTP header. You paste a config block:
type: "http" marks this as Streamable HTTP in the mcpServers shape. It is the key those config files read. To get it filled in: click Agents & keys, scroll to Connect an agent, then click Copy recipe · keyless under Remote · bearer. Paste the whole object into your agent’s MCP config file — .mcp.json for Claude Code, mcp.json for Cursor. Replace <your-agent-key> with your own key. Restart the agent. Postern matches the header name and the word Bearer without regard to case. A Bearer with no key behind it counts as no key at all. Postern compares the SHA-256 of the key you present against the hash stored with the grant. It never stores the key, never logs it, and never puts it in the record. A lost key is created again, not recovered. What an agent key is. Postern checks the key once, when the agent connects, and remembers the answer for that session. Later tool calls do not re-read the database. Three states end a key — unknown, revoked and expired — and the refusal at connect names which one it was. Why an agent was refused. A key is dead at its expiry instant, and Postern checks the deadline on every call. An open session stops answering the moment its key expires. Expiry. Four acts end every live session that agent holds, at once: an edit to its grants, an edit to its expiry, a rotation of its key, and a revoke or delete. A session that was still starting up is refused too. Postern only has to sweep the agent port. The REST read mirror re-checks the key on every single request, so it never held a stale answer to sweep. The agent must connect again, and the new grants apply from that new session. Revoking, rotating and removing. A grant decides what every tool answers. Grants are per sector and default-deny: a new key can reach nothing until you switch on a sector, and a granted sector covers reading and acting there. A key granted no sectors connects successfully and is then denied on every read, so nothing in your agent looks wrong; describe_context gives it away, coming back with sectors: []. What the grant decides, tool by tool is the per-tool effect, and why an agent was refused is the shape of each refusal.

What a tool call returns

Every tool answers with one text block holding the result as JSON, indented two spaces:
Tools are the entire surface. Postern registers no resources and no prompts. A refusal or a failure comes back as a tool error carrying Postern’s own message. It is not a transport error and not a broken session. This page quotes only Postern’s half of the string; how your agent wraps it is not recorded here. An action the source itself rejects is not an error. invoke_action returns ok: false as an ordinary result. See invoke_action. There are no rate limits in the tool path. The bridge’s own addresses are metered. Postern writes a row for every tool call inside a live session — served, refused or failed. The write happens off the response path, so it costs the call nothing, and the key is never one of the fields. A key refused at connect is a different path: the agent port records that, and the REST read mirror does not. Read it at Console → The ledger. What gets a row.

What the agent is told at connect

Postern introduces itself as postern and ships this text in the initialize result, where it stays in the agent’s context for the whole session:
Each tool carries its own description on top of that, and those descriptions are where the per-tool traps live.

The REST read mirror

Every read an agent can make over MCP is also available over plain HTTP, for tools that do not speak MCP. The Console port serves five of them, gated by the same agent key and recorded the same way:
They call the same core functions and answer the same shapes as the tools above. The mirror carries reads only. There is no invoke_action on it. To act on a source — which today means Home Assistant and nothing else — an agent must use MCP. Postern resolves the identity on every request here rather than once per session, so the mirror has no session to sweep and no session-cached grant. The mirror is on the Console port, not the agent port, and docker-compose.yml opens that port to this computer only. A public address opens the agent port to the internet. It never opens this one.

If something went wrong

Every string below is Postern’s own. Your agent may print more around it.

Next

Read data

get_schema, query and get_record — every parameter, every refusal.

Freshness and live reads

describe_context and fetch_live — how old the data is, and how to refresh it.

Act on a source

list_actions and invoke_action — the write side.

Connect an agent

Create a key and paste it into your agent.