Skip to content
Integrations · Codex

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 environment

start_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

What it does

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.

Setup

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.

  1. 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
  2. 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]
  3. 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
  4. 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.

~/.codex/config.toml
config.toml · http
[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"
config.toml · stdio
[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
http
$ codex mcp add postmq --url https://mcp.postmq.com/ --bearer-token-env-var POSTMQ_CREDENTIAL
$ codex mcp list
stdio
$ codex mcp add postmq --env POSTMQ_CREDENTIAL=pmq_…redacted… -- pmq mcp stdio
$ codex mcp list
The protocol in Codex

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.

The session protocol as AGENTS.md instructions, step by step
stepthe instructionPostMQ toolswhat 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
The tools it uses

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 tools Codex sees, by domain group, from docs/mcp/tool-surface.json
grouptoolsownercalled by the protocol
Messaging · 9 send, validate, get_pending, ack, nack, extend_lease, list_recipients, list_templates, get_template /handoffs
Webhooks · 3 list_webhooks, get_webhook, list_webhook_deliveries /handoffs
Policies and audit · 3 get_policies, acknowledge_policies, browse_audit_log /security
Projects · 7 create_project, get_project, list_projects, update_project, set_default_project, archive_project, unarchive_project /session-state
Build sessions · 12 start_build_session, end_build_session, record_build_session_prs, ping_build_session, get_build_session, get_build_session_aggregate, list_build_sessions, search_build_sessions, update_build_session_state, prepend_build_session_lead, link_build_session_continuation, reassign_build_session /session-state start_build_session, end_build_session, record_build_session_prs
Markdown import · 5 import_session_state_from_markdown, list_session_state_import_parsers, list_session_state_imports, get_session_state_import, get_session_state_import_rows /session-state
Decision log · 9 append_decision_log, correct_decision_log, amend_decision_log_entry_source, get_decision_log_entry, get_decision_log_entry_summary, list_decision_log, query_decisions, count_decisions_by_period, summarize_decisions_by_author /decision-log append_decision_log
Backlog · 13 file_backlog_item, amend_backlog_item, amend_backlog_closing_notes, triage_backlog_item, reprioritize_backlog_item, assign_backlog_item, reassign_backlog_item, acknowledge_backlog_item_staleness, list_stale_backlog_items, list_backlog_items, get_backlog_item, query_backlog, count_backlog_items_by_status /backlog file_backlog_item
Lessons and candidates · 14 file_lesson, get_lesson, query_lessons, update_lesson_status, append_lesson_observation, delete_lesson, list_lesson_candidates, get_lesson_candidate, count_lesson_candidates, observe_lesson_candidate, promote_lesson_candidate, reject_lesson_candidate, supersede_lesson_candidate, detect_lesson_candidates /lessons
Rules · 10 query_applicable_rules, create_rule, derive_rule, get_rule, list_rules, update_rule, archive_rule, delete_rule, enrich_rule, reenrich_rule /rules query_applicable_rules
Usage · 2 record_build_session_usage, get_usage /usage record_build_session_usage
A session, end to end

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.

the reviewer’s session
start_build_session
{
  "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"
    }
  }
}
get_pending
{
  "method": "tools/call",
  "params": {
    "name": "get_pending",
    "arguments": {
      "max": 10,
      "visibility_timeout": "PT2M",
      "wait": "PT20S",
      "filter": { "template": ["directive"] }
    }
  }
}
send
{
  "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…"
    }
  }
}
ack
{
  "method": "tools/call",
  "params": {
    "name": "ack",
    "arguments": {
      "message_id": "01K…",
      "lease_id": "01K…",
      "outcome_summary": "Reviewed; assessment sent back — pass."
    }
  }
}
append_decision_log
{
  "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
    }
  }
}
end_build_session
{
  "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.

914of 944 commits on main carrying a session trailer
938 pull requests merged

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.

Troubleshooting

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.

Troubleshooting the Codex integration
symptomwhywhat 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.