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.
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 answersPOST, 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
- In the left nav, click Agents & keys.
- Scroll to the Connect an agent band.
- Read the value under MCP endpoint.
- Click Copy beside it.
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:
- On the machine Postern runs on, open
.env— the settings file besidedocker-compose.yml. - Set
MCP_HTTP_PORTto the port you want. Save the file. - In that same folder, run
docker compose up -d.
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 needsmcp-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, throughmcp-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: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 aspostern and ships this text in the initialize result,
where it stays in the agent’s context for the whole session:
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: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.