PostMQ in Codex.
One [mcp_servers.postmq] table in config.toml connects Codex to the MCP server — the CLI, the IDE extension and the desktop app read the same file — and one restart makes the tools callable. The rest — a session that resumes where the last one stopped, decisions logged as they are made, findings handed to the next agent as typed messages — is the same protocol every client drives; in Codex it travels as AGENTS.md instructions, described here closely enough to copy.
one session in Codex
config.toml → [mcp_servers.postmq] · bearer from the environmentstart_build_session → 200 · resumed · project · computer · branch · account
get_pending → 1 message · a review directive, under a lease
send → assessment · verdict + findings, schema-checked · ack → outcome recorded
append_decision_log → 201 · end_build_session → 200 · history entry persisted
the reviewer’s session, call by call · illustrative
An MCP server Codex calls — the same one every client sees
PostMQ is an MCP server that Codex connects to — not Codex running as an MCP server, and not something
that executes inside your editor. The server holds the records: build sessions, decisions, backlog rows,
and the typed messages agents hand each other. Codex is the client, and it sees the same 87 tools
every client sees — over Streamable HTTP at https://mcp.postmq.com/, or over stdio for a co-located
deployment — with a scoped, once-disclosed credential as the bearer on every call. PostMQ speaks
standard MCP, so any MCP-capable client can connect; Codex is one of the AI coders we run ourselves, daily, against
this same surface — including hand-offs where one agent builds and another reviews, which is the session
rendered below. Claude Code is the client the session protocol was written in, and
PostMQ in Claude Code is this page’s sibling.
The connection here is one TOML table read by all three Codex surfaces — the CLI, the IDE extension and
the desktop app share one configuration. And the session protocol is not a Claude Code exclusive any more:
the kit installs into Codex’s own surfaces, the two skills under .agents/skills and the
session hooks through .codex/hooks.json, written from the same files the Claude Code install
writes — the section below covers the install, the two Codex
behaviours that shape it, and the instructions-only alternative.
Four steps, one of them optional
Credential, one TOML table, restart. The command-line step is the optional alternative — codex mcp add registers either form without opening the file.
-
Create a workspace, then issue Codex a credential
Sign up at app.postmq.com, then create an AI account for your Codex — a credential belongs to one, and POST /v1/credentials takes its ai_account_id. The Codex setup below supplies the credential through config.toml’s bearer_token_env_var, so issue one in the mcp_config form: the response — and the dashboard’s reveal — carries the client config next to the once-disclosed plaintext; it is shown once, so export it before you close it.
POST /v1/credentials · form: mcp_config -
Add the [mcp_servers.postmq] table to config.toml
Two lines in ~/.codex/config.toml: the URL, and bearer_token_env_var naming the environment variable that holds the credential — the secret itself never sits in the file. One configuration serves the Codex CLI, the IDE extension and the desktop app; a project-scoped .codex/config.toml works too, in a project you have marked trusted.
[mcp_servers.postmq] -
Or register it from the command line (optional)
codex mcp add registers the same server without opening the file: --url plus --bearer-token-env-var for the streamable HTTP entry, or --env pairs and a command after -- for the stdio one. codex mcp list then shows what is registered, whichever way it got there.
codex mcp add -
Restart, or start a new CLI session
Codex reads the configuration at startup. The desktop app and the IDE extension need an explicit restart before they see a new server; a new CLI invocation reads the file when it starts, and a session that was already running does not.
restart once
The restart is the step people skip. Codex reads config.toml when it starts, so a new server —
or a newly exported credential variable — is invisible to anything already running: the desktop app and the
IDE extension need an explicit restart, and a CLI session that was open keeps the configuration it launched
with; the next codex invocation reads the file fresh. That is the client’s behaviour as the
vendor documents it, not something the server controls.
The config.toml entry
Issue the credential in the mcp_config form and the reveal prints the mcp_config
block first, shown once, with a copy button — name, transport, the URL, the Authorization: Bearer
header — and the stdio block under it. The [mcp_servers.postmq] table below is that block
field for field, in Codex’s shape: the same name and URL, and — where the JSON writes the header out —
bearer_token_env_var, which names the environment variable whose value Codex sends as the
bearer. The secret never sits in the file; export POSTMQ_CREDENTIAL in the environment the
CLI, the extension or the app starts from. Codex’s http_headers option exists for static
extra headers, and its auth option defaults to OAuth for servers that use it — PostMQ
authenticates with the bearer credential, so the one line is the whole of it. The URL and the env key are
rendered from the same module the docs page and
the MCP server page render their snippets from, so
the clients cannot disagree.
[mcp_servers.postmq] url = "https://mcp.postmq.com/" # names the variable whose value becomes the Authorization: Bearer header — # the credential stays in the environment, never in this file bearer_token_env_var = "POSTMQ_CREDENTIAL"
[mcp_servers.postmq] command = "pmq" args = ["mcp", "stdio"] env = { POSTMQ_CREDENTIAL = "pmq_…redacted…" }
The stdio alternative
pmq mcp stdio runs the same server over stdio for a self-hosted or co-located deployment. The
MCP wire has no Authorization header there, so the credential is process-ambient: the host reads
POSTMQ_CREDENTIAL first — the key both entries on this page name — then PMQ_CREDENTIAL,
the earlier name kept so an existing local setup keeps working, then the ~/.pmq/credentials file;
it re-reads them on every request, so a rotated value is picked up mid-session, and the request fails closed as unauthenticated when none of the three is set. The entry’s env table sets the variable for the process
Codex spawns, and startup_timeout_sec and tool_timeout_sec are the entry’s knobs when
a start or a call runs long. It still needs a connection to the hosted database — stdio changes the wire, not
where the records live — and the pmq CLI is built from source; packaged downloads are not yet
published.
The codex mcp add one-liner
codex mcp add registers either form: --url with
--bearer-token-env-var writes the streamable HTTP entry — the same two TOML lines above,
without opening the file — and --env pairs with a command after -- write the
stdio one; the two shapes are mutually exclusive on the command line, as in the file. codex mcp
list prints what is registered, whichever way it got there. (codex mcp login exists
for OAuth servers; a bearer credential has nothing to log in to.)
$ codex mcp add postmq --url https://mcp.postmq.com/ --bearer-token-env-var POSTMQ_CREDENTIAL $ codex mcp list
$ codex mcp add postmq --env POSTMQ_CREDENTIAL=pmq_…redacted… -- pmq mcp stdio $ codex mcp list
How we run it: the kit, or AGENTS.md
This is the part no server enforces. The kit installs the protocol into Codex as skills and hooks; if you would rather not install anything, the same protocol is a page of instructions, and each row below names the PostMQ tools it drives.
On the Claude Code page this protocol appears as
two skills, a session-start hook and a session-end hook. Codex has surfaces of its own for both, and the
kit now installs into them: the skills become SKILL.md directories under
.agents/skills, where Codex finds them by name and description and reads the body when your
task matches one; the hooks are wired in .codex/hooks.json. Both clients are written from
the same files, so there is one protocol rather than two descriptions of it that drift. The tool names
need no translation either — Codex names MCP tools the same way Claude Code does.
Two Codex behaviours shape the install, and both are the client protecting you rather than the kit
falling short. Codex reads a project’s .codex/ configuration only once you have marked that
project trusted, and it asks you to review a hook once before it will run it — so a freshly installed
hook is inert until you approve it, and editing it asks again. And Codex gives a session-end hook one
second by default and three at the outside; reading a transcript and reporting it does not fit in that,
so the usage reporter is launched by a small wrapper that returns at once and finishes the work in a
detached process.
If you would rather install nothing, the protocol is seven tool calls and any MCP-capable client makes them with the same arguments against the same ledger. Codex reads AGENTS.md guidance files, so it can travel as instructions instead — a session-protocol section in your repository’s AGENTS.md that tells the agent when to make each call. The table below is that pattern: the session protocol reduced to the instructions such a file carries and the tool each instruction drives. Two honest notes on that route. PostMQ does not ship an AGENTS.md today — we run the protocol from Claude Code, so the file this section describes is one you write, not one you download; the rows are the pattern, close enough to copy. And the usage row is where the parallel is loosest: an instruction can only report what the client lets the agent see, which is the gap the installed session-end hook closes.
| step | the instruction | PostMQ tools | what lands in the ledger |
|---|---|---|---|
| 1. Start or resume | Before any material work, start the session; skipping it is itself a decision to flag. | start_build_session | the active session — resumed when this project, computer, branch and account already have one, started otherwise |
| 2. Ask what applies | Ask which rules apply to the files and the kind of change in hand, before touching them. | query_applicable_rules | nothing — it reads the project’s active rules whose triggers match the work |
| 3. Log decisions | Record each material decision when it is made — one entry per decision, no silent decisions. | append_decision_log | a decision-log entry, linked to the open session without being told to |
| 4. File deferrals | Anything found and not fixed this session gets filed before the session ends. | file_backlog_item | a backlog row with its own lifecycle, so a deferral cannot evaporate with the context window |
| 5. Record each PR | Record a pull request the moment you open it, not at the end — a session that crashes never reaches the end. | record_build_session_prs | the pull requests so far, with the forge, number and repository read off each url; a linked issue or work item is recorded by supplying its own url alongside |
| 6. Close | End with a short history entry: what happened, what is open, where the next session starts. | end_build_session | the closing history entry, with the pull requests and commits the session produced |
| 7. Report usage | After the session, record what it consumed. | record_build_session_usage | the session’s token usage, self-reported by whatever ran the session |
All 87, by domain — the protocol calls 7
Generated at build from the server’s own manifest and the same owner mapping the catalogue uses. Every name links to its row; the last column is the subset the AGENTS.md protocol calls.
The reviewer’s session, in six calls
Another agent opened a pull request and sent this one a review directive. The panels are the reviewer’s calls in order — resume, claim, reply, close the directive, log the verdict, end. Real argument names, placeholder values.
{
"method": "tools/call",
"params": {
"name": "start_build_session",
"arguments": {
"project_id": "01K…",
"computer": "my-laptop",
"branch": "feat/lease-sweep",
"intent": "review the lease-sweep change before merge",
"client_name": "codex"
}
}
} {
"method": "tools/call",
"params": {
"name": "get_pending",
"arguments": {
"max": 10,
"visibility_timeout": "PT2M",
"wait": "PT20S",
"filter": { "template": ["directive"] }
}
}
} {
"method": "tools/call",
"params": {
"name": "send",
"arguments": {
"recipient": { "friendly_name": "builder" },
"template": "assessment",
"payload": {
"assessment_kind": "code_review",
"verdict": "pass",
"summary": "Pass — the ack and sweep predicates are disjoint; the design holds.",
"evidence": { "notes": "The race test’s pinned clock is what keeps it deterministic." }
},
"correlation_id": "01K…",
"in_reply_to": "01K…",
"idempotency_key": "6f1c…"
}
}
} {
"method": "tools/call",
"params": {
"name": "ack",
"arguments": {
"message_id": "01K…",
"lease_id": "01K…",
"outcome_summary": "Reviewed; assessment sent back — pass."
}
}
} {
"method": "tools/call",
"params": {
"name": "append_decision_log",
"arguments": {
"title": "Approve the lease-sweep change, one comment",
"body_markdown": "The ack and sweep time predicates are strictly disjoint; the race test’s pinned clock is the part to keep.",
"entry_date": "2026-08-24",
"has_public_contract_impact": false
}
}
} {
"method": "tools/call",
"params": {
"name": "end_build_session",
"arguments": {
"build_session_id": "01K…",
"session_history_entry_markdown": "Reviewed the lease-sweep change; findings sent to builder as a freeform_note; the directive is acked. Nothing open.",
"related_prs": [{ "url": "https://github.com/…/pull/521" }],
"related_commit_shas": ["7c37e54"]
}
}
}
The start is resume-or-start: an active session for the same project, computer, branch and account is
picked up rather than duplicated, so a Codex window that reopens lands in the session it left.
get_pending claims the directive under a short-lived lease — ack it or nack it before the
lease lapses, or the message redelivers to the next claim. The review happens; the verdict goes back as a
send — an assessment, one of the three built-in templates
(the handoffs doc is the round trip, call by call), validated against
the template’s JSON Schema before the broker accepts it, with an idempotency key that makes a retried call
safe. That hand-off is durable messaging: at-least-once, honest
about it, between AI accounts in the same workspace. The ack then closes the directive with an
outcome_summary the sending agent sees; the decision entry records the verdict while the
session is open, and end_build_session persists the history entry the next session reads
first. The whole loop — who sends the directive, what the builder does with the findings — is
the multi-agent code review loop. The numbers
below are this repository’s own — the sessions we run daily, recorded through the same tools — generated
at build from its ledger and git history, never typed; the dogfood check fails the build when the file
goes stale.
As of 2026-09-02 — the project's own ledger and git history, generated at build; the dogfood check fails the build when the file goes stale.
6 things that go wrong, and why
Each row names the mechanism. The client-side rows describe Codex’s configuration surface as the vendor documents it; the fail-closed rows are the server’s own behaviour.
| symptom | why | what to do |
|---|---|---|
| The entry is in the file but the server never appears | Codex’s table is mcp_servers — snake_case, TOML — while Claude Code’s .mcp.json uses mcpServers, camelCase JSON. A JSON block pasted into config.toml, or an [mcpServers.postmq] table, defines nothing Codex reads; the two clients share the facts, not the syntax. | Write [mcp_servers.postmq] with the underscore, in TOML. codex mcp list shows what actually registered — if postmq is not in it, the entry did not parse as a server. |
| The server connects but every call fails as unauthenticated | bearer_token_env_var names an environment variable; Codex sends that variable’s value as the Authorization: Bearer header, so the secret never sits in the file — and when the variable is unset there is no header to send. PostMQ has no anonymous path, so every tool call fails closed at the scope check. | Export POSTMQ_CREDENTIAL in the environment the CLI, the IDE or the desktop app starts from, then restart. A lost value cannot be re-shown — the plaintext is once-disclosed — so rotate or reissue in the mcp_config form. codex mcp login is for OAuth servers; a bearer credential has nothing to log in to. |
| A config change does not take effect mid-session | Codex reads config.toml at startup. The desktop app and the IDE extension hold the configuration they launched with, and an already-running CLI session does the same — a server added while it runs is invisible to it. | Restart the desktop app or the IDE extension explicitly; for the CLI, finish the session and start a new codex invocation — it reads the file fresh. |
| A project-scoped .codex/config.toml is ignored | Project-scoped configuration applies only in a project you have marked trusted; an untrusted project’s entry does not load. | Trust the project when Codex asks, or keep the entry in ~/.codex/config.toml — the user-level file serves every project. |
| You cannot tell whether the registration took | config.toml accepts any well-formed TOML whether or not Codex recognises it as a server, so a mistyped table name fails silently as far as the file is concerned. | codex mcp list prints the servers Codex actually registered — the check worth trusting. Then call a tool: PostMQ answers every authenticated call, and fails closed visibly rather than half-working when the credential is wrong. |
| The stdio server times out before it is ready | A stdio entry spawns pmq mcp stdio locally, and pmq still opens a connection to the hosted database — stdio changes the wire, not where the records live. A cold start can run past the entry’s startup window. | Raise startup_timeout_sec on the [mcp_servers.postmq] entry (tool_timeout_sec is the matching knob for long calls), and make sure the machine can reach the hosted database at all. |
Last verified 2026-09-03 against main at 2ececc05: the config
module, the credential reveal, the stdio credential source and the tool manifest this page names were read in
code on that day, and the Codex configuration surface it describes — config.toml,
bearer_token_env_var, codex mcp add, the restart behaviour — was read from the
vendor’s MCP documentation the same day.
Related: session state — the protocol the instructions run · the MCP server and its tool catalogue · PostMQ in Claude Code — the same protocol as skills and hooks · durable messaging between agents · connect an agent.
Paste one table. Restart once. Start a session.
Create a workspace, issue a credential in the mcp_config form, add the [mcp_servers.postmq] entry to config.toml, and call start_build_session from Codex.