Skip to content
Integrations · Claude Code

PostMQ in Claude Code.

One .mcp.json entry connects Claude Code to the MCP server; one restart makes the tools callable. The rest — a session that resumes where the last one stopped, decisions logged as they are made, usage reported when the session ends — is a set of skills and hooks this repository runs on itself, described here closely enough to copy.

one session in Claude Code

.mcp.json → postmq · type http

start_build_session → 200 · resumed · project · computer · branch · actor

append_decision_log → 201 · linked to the open session

end_build_session → 200 · history entry persisted

SessionEnd hook → record_build_session_usage · token counts, not dollars

the calls a session makes at its bracket ends · illustrative

What it does

An MCP server Claude Code calls, and a protocol we run on it

PostMQ speaks standard MCP, so any MCP-capable client can connect — the MCP clients page carries the full list; Claude Code is the client we run ourselves every day, alongside Codex — including the hand-offs of the multi-agent code review loop, where one agent builds and the other reviews from its own inbox. 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. The session protocol on top of it — start or resume a session, ask which rules apply, work, log each decision, file each deferral, end with a history entry, report what it consumed — is the nine steps on the session-state page, and here it is the concrete form: two Claude Code skills, a SessionStart hook and a SessionEnd hook. That part is how we run it, a recipe you copy; the server does not enforce it. The commit that ships the work carries the session id as a trailer, so a reader can go from a commit to its session and to the decisions it logged. For why a session needs any of this — what context loss is between sessions, and where the hand-rolled fixes break — read the context-loss guide; for the two skills step by step as we run them, with the numbers, read the start-session and end-session guide.

Setup

Five steps, two of them optional

Sign in, add one entry, connect, restart. The client either approves over OAuth or carries a credential you issue; the skills step is the protocol.

  1. Create a workspace

    Sign up at app.postmq.com. A workspace belongs to the person who creates it. Where the client connects over OAuth you do not create an AI account or a credential up front — approving the connection issues the credential, and creates the AI account too if the workspace has none yet. Approving is also yours to do.

    app.postmq.com/signup
  2. Add the postmq entry to .mcp.json

    Streamable HTTP at mcp.postmq.com. PostMQ refuses the first call with a 401 naming its OAuth metadata, so a client that implements MCP authorization registers itself and opens a browser for you to approve — no credential in the file. Where the client does not, add the Authorization: Bearer header from the mcp_config block instead. Project scope (.mcp.json at the repository root) is what we use, so every session in that checkout sees the server.

    type: http
  3. Or, with no browser, an issued credential

    Approval happens in a browser, so a headless box uses a credential issued in the mcp_config form instead — as an Authorization header, or over stdio with pmq mcp stdio reading POSTMQ_CREDENTIAL. stdio changes the wire, not where the records live; it still needs a connection to the hosted database.

    pmq mcp stdio
  4. Restart Claude Code once

    Claude Code fetches each server’s tool catalog when it starts. A client that was already running answers "tool not found" for a tool it has not seen; one restart refetches the catalog and every tool is callable.

    restart once
  5. Install the skills and the two hooks

    The /start-session and /end-session skills, the SessionStart hook that prints the drift signals, and the SessionEnd usage hook. They are ours and live in our repository, which is private — but their content is not: every one is served at postmq.com/kit/, and the plugin, an agent, or a copy by hand all take those same bytes. The section below describes each closely enough to reproduce.

    .claude/skills · .claude/hooks

The restart is the step people skip. Claude Code fetches each server’s tool catalog when it starts, so a client that was already running answers "tool not found" for a tool it has not seen; restart Claude Code once and the catalog is refetched. That is the client’s behaviour as we have measured it in our own sessions, not something the server controls.

The .mcp.json entry

A client that implements MCP authorization needs only type: http and the URL here — it asks for its own credential and you approve it once, so no secret goes in the file. The entry below is the other form, for a client that does not or a machine with no browser to approve in: 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 postmq entry below is that block field for field: type: http, the URL, the header. It is the same snippet the docs page and the MCP server page show, rendered from one source, so the three cannot disagree. Only the secret is redacted.

.mcp.json · project scope
.mcp.json · http
{
  "mcpServers": {
    "postmq": {
      "type": "http",
      "url": "https://mcp.postmq.com/",
      "headers": { "Authorization": "Bearer pmq_…redacted…" }
    }
  }
}
.mcp.json · stdio
{
  "mcpServers": {
    "postmq": {
      "type": "stdio",
      "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 the paste-ready block sets — 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 with none of the three the request fails closed as unauthenticated. The pmq CLI is built from source; packaged downloads are not yet published.

The skills and hooks

How we run it: two skills, two hooks

This is the part the server does not enforce. It is the recipe this repository runs at every session boundary, and each row names the PostMQ tools it calls.

/start-session has 5 steps: read the project’s law files (the conventions that override default behaviour), query the current state, surface drift, pick a worktree and a branch off origin/main, and resume or mint the session — resumed if this project, computer, branch and actor already have an active one, enforced by a unique index. /end-session has 7 steps: log every material decision, file every deferral, refresh the project’s status file and regenerate its launch plan (both ours), merge origin/main in as a pre-merge guard, write the PR body, verify CI on every PR the session touched, close the session with a history entry, and clean the worktree up. Neither skill is a request — both run at every session boundary, and skipping one is itself a decision to be flagged.

You do not have to copy these by hand. They ship as the session-state kit — both skills, both hooks and the settings to wire them — installable as a Claude Code plugin with claude plugin marketplace add No-Compromise-AI/postmq-plugins, or by copying the files from postmq.com/kit/. An agent that has just connected the MCP server will offer to install it for you, and will ask first. pmq kit install --all writes the same tree in one command, but the CLI is not a download today — it ships in no package manager and has no release workflow, so it is reachable only by building it from source.

The table below is the protocol as this repository runs it against its own session state, with the step numbers from the copies we run. The kit you install is the same protocol against PostMQ, and it carries one instruction this repository's copies do not: record each pull request at the moment you open it, rather than only at the close, because a session that crashes or runs out of context never reaches its close at all.

What the skills and the usage hook call, step by step
wherestepPostMQ toolswhat it records
/start-session 2. Query for current state list_build_sessions, list_decision_log, count_backlog_items_by_status the active session (the resume target), the last ten decisions, the open and resolved counts
/start-session 5. Resume or mint the session start_build_session, ping_build_session resume the active session for this project, computer, branch and actor and ping it; otherwise start one and record its id in a state file the usage hook reads
/end-session 1. Log decisions append_decision_log one entry per material decision; no silent decisions
/end-session 2. File deferrals file_backlog_item, triage_backlog_item every deferred item gets a row; the items you finished are resolved with closing notes or a closing PR
/end-session 6. Close the session end_build_session the closing history entry, the PR URLs and commits the session produced
SessionEnd hook SessionEnd record_build_session_usage the transcript’s token usage against the session, self-reported by the hook

The SessionStart hook is a shell script wired in .claude/settings.json. It prints the protocol reminder, origin/main’s last commit and its age, the active worktrees, and every open PR with its CI rollup and merge state — read from the checkout and gh; it fails open, so no network prints nothing rather than an error. The SessionEnd hook is postmq-usage-reporter: one Python file with no dependencies that reads the just-finished transcript, sums five token classes, turns and sidechain turns, and records them with record_build_session_usage (or POST /v1/usage; one function builds one payload for both) against the session named by the state file /start-session wrote. The numbers are self-reported by the hook — token counts, not dollars — and idempotent per transcript. Any other client can call the same tool with the same arguments.

.claude/settings.json
SessionStart
{
  "hooks": {
    "SessionStart": [
      { "hooks": [ { "type": "command",
                     "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/session-start-reminder.sh\"" } ] }
    ]
  }
}
SessionEnd
{
  "hooks": {
    "SessionEnd": [
      { "hooks": [ { "type": "command",
                     "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/postmq-usage-reporter.py\"" } ] }
    ]
  }
}
The tools it uses

All 87, by domain — the protocol calls 10

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 skills and the hook call.

The tools Claude Code sees, by domain group, from docs/mcp/tool-surface.json
grouptoolsownercalled by the skills
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, ping_build_session, list_build_sessions
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, list_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, triage_backlog_item, count_backlog_items_by_status
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
Usage · 2 record_build_session_usage, get_usage /usage record_build_session_usage
A session, end to end

What the hook prints, and the three calls at the bracket ends

The first panel is the shape of the SessionStart hook’s output — its section labels and line formats are the script’s, the reminder lines are abridged, and every value is a placeholder. The other three are the calls, with real argument names and placeholder values.

one session
SessionStart hook
== PostMQ session protocol ==
  - Run /start-session before any material work; /end-session before you stop.
  - Work in a worktree on a branch off origin/main; one concern per PR; squash + auto-merge.
  - Session state lives in the MCP server (build sessions + decision log + backlog), not markdown.
  - A push exiting 0 != shipped: verify CI green AND the merge contains your latest commit.
  - origin/main last commit: <age, e.g. 2 hours ago> (<sha>)
== active worktrees ==
  <path>/.claude/worktrees/<name>  <sha> [<branch>]
== open PRs ==
  #<n> [ok] merge=<CLEAN | BLOCKED | DIRTY> (<title>)
  #<n> [FAIL x1] merge=<state> (<title>)
start_build_session
{
  "method": "tools/call",
  "params": {
    "name": "start_build_session",
    "arguments": {
      "project_id": "01K…",
      "computer": "my-laptop",
      "branch": "feat/lease-sweep",
      "intent": "make the expiry sweep lose to a live lease",
      "client_name": "claude-code"
    }
  }
}
append_decision_log
{
  "method": "tools/call",
  "params": {
    "name": "append_decision_log",
    "arguments": {
      "title": "Ack and sweep predicates live in one UPDATE",
      "body_markdown": "Both predicates in the same statement; the loser matches zero rows.",
      "entry_date": "2026-08-18",
      "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": "Lease sweep loses to a live lease; ack refuses a re-pended row. Next: the crossing-boundary case.",
      "related_prs": [{ "url": "https://github.com/…/pull/521" }],
      "related_commit_shas": ["39b8680"]
    }
  }
}

The start returns 200 when this project, computer, branch and actor already have an active session and 201 when it mints one — a unique index makes that true, so a reconnect never duplicates a session. Each append_decision_log made while the session is open links to it without being told to; the log is append-only by a database trigger. The end persists the history entry the next session reads first, with the PR URLs and commits. Then the work commit carries the session id as a trailer, and the SessionEnd hook records the transcript’s tokens against the same session — the numbers below are this repository’s own, generated at build from its ledger and git history and 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 last one is our repository’s CI, not the product, and says so.

Troubleshooting the Claude Code integration
symptomwhywhat to do
A tool answers "tool not found" Claude Code fetched the server’s tool catalog when it started; a tool that shipped after that is not in the client’s copy. Restart Claude Code once. The catalog is refetched at startup and the tool becomes callable — the client’s behaviour, not the server’s.
Every call fails as unauthenticated There is no anonymous path: a missing header, an expired or revoked credential, or a revoked parent account all fail closed at the tool’s scope check with a coarse error, and the plaintext is once-disclosed — the server cannot show it again. If the agent holds its own OAuth token, check Connections in the dashboard — a disconnected grant revokes every token behind it, and reconnecting is one approval. If a person issued the credential, rotate or reissue it in the mcp_config form and replace the block. A tool outside the credential’s scopes fails the same way, so check the scopes it was granted.
stdio connects but calls are unauthenticated pmq mcp stdio reads the credential from POSTMQ_CREDENTIAL first, then PMQ_CREDENTIAL, then the ~/.pmq/credentials file — fresh on every request, so a rotated value is picked up mid-session; a value that already carries the Bearer prefix is accepted. Set POSTMQ_CREDENTIAL in the entry’s env block (the stdio snippet above sets exactly that key), and make sure the pmq process can reach the hosted database.
The SessionStart hook shows an old origin/main, a stray worktree or a failing PR The hook prints origin/main’s last commit and its age, the active worktrees, and every open PR with its CI rollup and merge state; it fails open, so a missing gh or no network prints nothing rather than an error. Those are the drift signals — it does not read the ledger itself. The start-session skill’s rule: if the last commit, decision or session is more than about 24 hours old and you are picking up material work, list the gap before doing new work — resume the recoverable worktree, update the blocked PR, then start.
The usage hook says "not attributed" The reporter finds the build session through the state file /start-session writes (.claude/state/active-build-session-<claude_session_id>) or POSTMQ_BUILD_SESSION_ID; with neither it never guesses. It also needs POSTMQ_CREDENTIAL with the write_session_state scope. Every failure is one stderr line and exit 0 — a hook must never block session end. Run /start-session so the pointer exists, export the credential the mcp_config block carries, and re-run: recording is idempotent per transcript, so a late report overwrites and never double-counts. On Windows use py -3 in the hook command.
The pull request’s discipline gate is red in our repo In our repository a discipline gate greps the PR body for three literal predicates: a line naming the session id with the colon, a Decisions heading, and a Follow-ups heading (or a word such as backlog or deferred). It reads the body, not the commit message, and it re-runs when the body is edited. Add the three lines to the body and save it — the check re-runs on edit. It is a required check in our repository, so a miss blocks the merge. This is our CI’s rule, not the product’s — the server records the session whether or not a PR body mentions it.

Last verified 2026-09-03 against main at 2ececc05: the two skills, the two hooks, the stdio credential source, the credential reveal and the config module this page names were read on that day.

Related: session state — the protocol these skills run · the MCP server and its tool catalogue · connect an agent · the decision log a session writes to · credentials and the audit chain.

Add one entry. Connect. Start a session.

Add the postmq entry to .mcp.json, connect it, and call start_build_session from Claude Code.