Skip to content
Integrations · ZCode

PostMQ in ZCode.

ZCode is Z.ai's GLM-powered coding client, and it connects to PostMQ the way every other MCP client does — one server, one scoped credential, the same catalogue of tools. The session protocol installs into ZCode's own surfaces too: the two skills as SKILL.md directories under .zcode/skills, the hooks wired in .zcode/config.json, written from the same files the Claude Code and Codex installs write. Three things about ZCode shape that install, and all three are worth knowing before you conclude a hook is broken.

Get started All MCP clients 87 MCP tools · the same catalogue for every client

one session in ZCode

~/.zcode/cli/config.jsonmcp.servers.postmq

.zcode/config.jsonhooks.enabled: true

start_build_session → resumed · project · branch

Stop fires each turn · usage upserts one row

What it does

An MCP server ZCode calls — the same one every client sees

PostMQ speaks standard MCP, so any MCP-capable client can connect; ZCode is one of them. It is an MCP server that ZCode connects to — not ZCode 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. ZCode is the client, and it sees the same 87 tools every client sees, with a scoped, once-disclosed credential as the bearer on every call.

The tool names need no translation. ZCode names MCP tools mcp__<server>__<tool> — the same convention Claude Code and Codex use — so a skill written for one client calls the same names in all three, with no per-client rewriting step between them.

That is what makes the hand-offs work in both directions: a ZCode session can pick up a review directive a Claude Code session sent, review it with its own tools, and send the verdict back as a typed message the sender's next session reads. Neither agent has to be running when the other is. The loop is multi-agent code review, and PostMQ in Codex renders the reviewer's half of the same exchange.

ZCode is one of the AI coders we run ourselves, daily, against this same surface. What differs between the clients is not the server — it is what each one gives a hook to work with, and the section on the protocol below states ZCode's differences rather than smoothing them over.

Setup

Five steps

Credential, the MCP server entry, the kit, the reporter's own credential, one restart. The hook enablement flag lives inside step three, and it is the step people lose an afternoon to.

  1. Create a workspace, then issue ZCode a credential

    Sign up, then create an AI account for your ZCode — a credential belongs to one, and POST /v1/credentials takes its ai_account_id. Issue it in the mcp_config form: the response, and the dashboard reveal, carry the URL, the headers and the once-disclosed plaintext. That block is a flat descriptor rather than any one client file format, so for ZCode you take the URL and the credential from it into the nested entry in the next step. The plaintext is shown once, so copy it before you close it.

    POST /v1/credentials · form: mcp_config
  2. Register the MCP server in ZCode

    ZCode reads MCP servers from ~/.zcode/cli/config.json, and inside its own config file the servers nest under mcp.servers rather than the top-level mcpServers object Claude Code and Antigravity use. An http server takes a type, a url, a headers object carrying the bearer, and a timeoutMs; stdio servers in the same file take command, args and env instead. Paste the entry below, with your own credential in place of the placeholder.

    mcp.servers.postmq
  3. Install the session kit for ZCode

    Copy the files. Every source the kit names is served at postmq.com/kit/, and kit/manifest.json says where each one goes: the skills to .zcode/skills/start-session/SKILL.md and .zcode/skills/end-session/SKILL.md, the hooks under .zcode/hooks/ — wire the .zcode.sh wrapper, not the script it wraps — and the settings block merged into .zcode/config.json. That block also sets hooks.enabled, and that line is not decoration: ZCode ships the hook runner off. Then chmod +x the shell hooks. pmq kit install --all --client zcode 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.

    copy from postmq.com/kit/ — see the manifest for each target
  4. Give the usage reporter the credential too

    The mcp.servers.postmq entry you pasted carries the bearer in its own headers object, which authenticates ZCode and nothing else. It does NOT authenticate the usage reporter, which is an ordinary script the Stop hook runs — it reads its credential from POSTMQ_CREDENTIAL in the environment. Put it somewhere ZCode will inherit: your shell profile, or whatever launches it, not just the terminal you happen to be in. Without it the report is built, found to have nowhere to send, and dropped. A hook must never block a session, so the reporter logs one line and exits 0 — and on ZCode you will not even see that line, because the Stop hook runs it through the detach wrapper, which sends both streams to /dev/null. The only symptom is usage that never appears.

    export POSTMQ_CREDENTIAL=pmq_…
  5. Restart, then approve the hook when ZCode asks

    ZCode reads skills and hooks at start-up, so a client that was running when you installed will not see them. Restart it once. Then approve the hook: ZCode reviews a project hook once before it will run it, so a freshly installed hook is inert until you approve, and editing the entry asks again. Check it took by running /start-session — it should name the active build session, or start one, and print the drift block.

    restart once, approve once
the MCP server entry
config.json · ZCode
{
  "mcp": {
    "servers": {
      "postmq": {
        "type": "http",
        "url": "https://mcp.postmq.com/",
        "headers": { "Authorization": "Bearer pmq_…redacted…" },
        "timeoutMs": 90000
      }
    }
  }
}

The step people lose an afternoon to is not the restart — it is hooks.enabled. A hook that is loaded and listed looks installed: there is no error, no warning, and hooks.events.SessionStart reads back exactly as you wrote it. The only symptom is that nothing happens. If you wired it by hand rather than letting an agent or the installer write it, check that flag before you check anything else.

The protocol in ZCode

How we run it: one set of files, four clients

The server does not enforce any of this. It is the recipe we run on PostMQ itself, installed into ZCode's own surfaces — and the three places ZCode differs are stated here rather than smoothed over.

On the Claude Code page this protocol is two skills and two session hooks; on the Codex page it is the same two skills under .agents/skills with hooks through .codex/hooks.json. ZCode gets the same files again, at .zcode/skills and .zcode/config.json; so does Antigravity, at .agents/skills and .agents/tools — with nothing wired: there its skills run the scripts themselves. The shared pieces are one file each in the kit manifest whatever the client — the two skills and the session-start script are installed to the path each client reads — Codex and Antigravity share one from a single source, rather than three copies that drift apart. The trees are not wholesale identical: ZCode also gets a wrapper of its own, for the reason below, and each client's wiring snippet is its own.

The hook runner ships off

hooks.enabled defaults to false, so a hook wired into hooks.events without it is loaded, listed and never run. The installer sets the flag, and tells you rather than flipping it back if you had turned it off yourself.

There is no end-of-session event

ZCode's seven hook events are SessionStart, UserPromptSubmit, PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, Stop — read from the shipped event enum rather than inferred from documentation. So the usage reporter is wired on Stop, which fires when the agent finishes answering a prompt: it reports once per turn, not once per session. That is the only mechanical option on a client with no end-of-session event, and the trade is worth stating plainly. The report is an idempotent upsert keyed on the build session and the session id, so repeated reports converge on the one row a single end-of-session report would have written, and no turn is counted twice. What it costs is a transcript read and one small request per turn — which is why the wired command is a detach wrapper, so a per-turn network call never sits between you and an answer. What it buys is that a session you never formally end still has its usage recorded, usually within a turn — though a failed detached report can leave it further behind, and nothing retries it.

A session-start hook's plain text is discarded

ZCode strips leading whitespace, then injects a hook's stdout only when it starts with { and parses as JSON. Output that is neither is dropped silently; output that parses but fails the schema raises a hook error you do see, which is the more helpful of the two failures. The shared drift-signal script prints plain text, so the kit ships a small wrapper that runs that script verbatim and re-emits its output as the additionalContext JSON ZCode does inject. Wire the wrapper, not the script — that is why the ZCode install copies both files, and why a hand-installer who wires the shared script directly gets a hook that runs perfectly and injects nothing. The wrapper is confirmed working in a real ZCode session: /start-session printed its drift block, so the project hook fires and what it prints reaches the agent.

The tools it uses

All 87 in the catalogue — the protocol calls 5

ZCode sees exactly the catalogue every client sees; the server does not vary it by client. The subset below is what the installed skills and hooks call.

Every one is in the catalogue, with the same arguments whichever client calls it. The session-state kit installs into all four clients we run from one set of files: .claude/skills and .claude/settings.json for Claude Code, .agents/skills and .codex/hooks.json for Codex, .zcode/skills and .zcode/config.json for ZCode, and .agents/skills with .agents/tools for Antigravity — which wires nothing, its skills running the scripts themselves, so usage there is recorded only when end-session runs.

A session, end to end

The builder's session, in six calls

This agent built something and handed the review to another. The panels are its calls in order — start, send the directive, take the assessment back, ack, log the verdict, end. Real argument names, placeholder values; the Codex page renders the reviewer's half of the same exchange.

  1. start_build_session
  2. send
  3. get_pending
  4. ack
  5. append_decision_log
  6. end_build_session

The hand-off in the middle is a directive, one of the built-in message templates, so the broker schema-checks it before it accepts it. The reviewer is not running when it is sent, and does not need to be: the message waits under a lease until something pulls it.

Troubleshooting

10 things that go wrong, and why

Each row names the mechanism. The client-side rows describe ZCode's own behaviour as measured against the installed product; the reporter rows are the kit's.

SymptomWhy
The hook is installed and listed, and nothing happens.hooks.enabled defaults to false, so the runner is off. A hook wired into hooks.events without it is loaded, listed, and never run — no error, no warning. Enablement is OR-ed across config sources, so setting it in .zcode/config.json turns the runner on for that project without touching your user config. This is the first thing to check, and the one that costs an afternoon.
Your project config is a zcode.json at the repo root, not a .zcode/config.json.Both are read. ZCode checks two filenames in each directory — zcode.json first, then .zcode/config.json — and it checks them in every directory from the first one containing a .git up to your working directory, so a monorepo package and its root are both sources. A git worktree has a .git FILE rather than a directory, which counts, so the walk stops at the worktree root and never reaches the main checkout. Outside a repository only the working directory is looked at. The kit writes .zcode/config.json; if you already keep a zcode.json, wire it there instead — and remember hooks.enabled is OR-ed across all of them, so it only has to be true in one.
The session-start hook runs, and injects nothing.ZCode injects a hook’s stdout only when it starts with { and parses as JSON; anything else is discarded silently. The shared drift-signal script prints plain text, so the kit ships a .zcode.sh wrapper that runs it verbatim and re-emits the output as additionalContext JSON. Wire the wrapper, not the script.
You wired the usage reporter on SessionEnd and it never fires.ZCode has no SessionEnd event. Its seven events are listed above. The kit wires the reporter on Stop instead, which fires when the agent finishes answering a prompt — so usage is reported per turn rather than once at the end.
A freshly installed hook does not run even with the flag on.ZCode reviews a project hook once before running it, against a digest of the hook declaration. A new hook is inert until you approve it, and editing the entry asks again. That is the client protecting you, and it is the same shape Codex uses.
Skills and hooks are missing after an install.ZCode reads both at start-up, so a client that was running when you installed will not see them. Restart it once.
A long session reports fewer tokens than it used.By default ZCode keeps only the three most recent rollout files and truncates a rollout past a size cap rather than rotating it; a full-retention preference exists and is off by default. After a reset the file holds only post-reset records. The reporter keeps a per-field high-water mark and refuses, best-effort, a report that would reduce the row — best-effort because the mark is local, so an unreadable one counts as no mark at all. A truncated session under-reports either way; the mark is what stops it also erasing what was already known.
sidechain_turns is always 0.ZCode’s rollout records carry no reliable sub-agent axis — model.role is absent on most records — so the reporter records an honest zero rather than a guess.
Two usage reports land out of order.The reports are detached, so one that read turn N and stalled can land after one that read turn N+1. The server settles it: the upsert is conditional on window_end, so a report older than the stored row’s is discarded and the response says applied false. A local lock could not have fixed it — if a send commits and its response is lost, no lock helps — which is why it belongs on the server. The reporter’s own high-water mark stays as the best-effort guard for a truncated rollout.
The session-start block names no session id.ZCode sets its session-id variable for hooks and for custom-command shell expansion, not for the shell the agent runs commands in — so the hook prints the id in a block for the agent to read. If the block is absent, the hook is not installed or not yet approved.

Last verified 2026-09-03 at 7a79537 — against ZCode 3.10.2 and the kit as shipped, including a real session in which the installed session-start hook fired and its output reached the agent.

Paste one entry. Restart once. Start a session.

The same server every other client sees, and the same protocol — installed into ZCode's own skills and hooks.