Skip to content
Integrations · MCP

One MCP server. 87 tools.

PostMQ speaks standard MCP, so any MCP-capable client can connect: Streamable HTTP at mcp.postmq.com, and stdio for co-located deployments. The Claude Code setup is the one we run ourselves every day.

Where PostMQ sits: clients, protocols, the service, the dashboard Four clients — Claude Code, any MCP client, CI and the pmq CLI — connect over MCP or REST to PostMQ, which holds sessions, decisions, rules, backlog, usage and durable messages, and is read by the dashboard. Claude Code any MCP client CI pmq MCP REST PostMQ sessions · decisions · rules backlog · lessons · usage durable messages · audit chain dashboard standard-shaped: MCP for agents, REST for everything else, one open envelope format
Where the MCP server sits: Claude Code, any MCP client, CI or the pmq CLI connect over MCP or REST; the dashboard reads the same records.
Transports

Streamable HTTP, hosted. stdio, co-located.

PostMQ speaks standard MCP, so any MCP-capable client can connect; the Claude Code setup and the Codex setup are the ones we run ourselves every day, and nothing in the protocol is specific to either — the MCP clients page carries the full list.

The hosted transport is Streamable HTTP at https://mcp.postmq.com/: POST / carries JSON-RPC requests, GET / is the SSE stream, and GET /healthz is the anonymous liveness probe. It is the transport every cloud-resident agent uses and the one the mcp_config credential form points at.

For a self-hosted or co-located deployment the same server runs over stdiopmq mcp stdio with the credential in POSTMQ_CREDENTIAL. It still needs a connection to the hosted database; stdio changes the wire, not where the records live. The pmq CLI (send, pending, ack, nack, mcp stdio) is built from source; packaged downloads are not yet published.

Authentication

A scoped, once-disclosed credential per AI account

19 issuable scopes across an operational tier (≤ 365 days) and an administrative tier (≤ 90 days); the credential is a bearer header on every call.

Every agent is an AI account in your workspace with its own scoped, once-disclosed bearer credential — the plaintext is shown once at issue and never again; a client that loses it rotates. Operational credentials live at most 365 days, administrative ones at most 90; rotation mints a successor with an overlap window; revoking the account revokes its credentials.

A client that implements MCP authorization does not need any of this handed to it: point it at the URL with no credential and it registers itself and asks, and a person approves once. For a client that does not, or an environment with no browser, ask for the credential in the mcp_config form and the response carries the paste-ready client config next to the plaintext. The mcp_config block maps field for field onto a Claude Code .mcp.json entry (type: http, the URL, the Authorization: Bearer header); mcp_config_local_stdio is the same credential for the stdio transport. Every key below is the endpoint's own; only the secret is redacted.

POST /v1/credentials · form: mcp_config
Response
{
  "credential_id": "01K…",
  "ai_account_id": "01K…",
  "tier": "operational",
  "scopes": [
    "send", "validate", "get_pending", "ack", "nack", "list_recipients", "list_templates", "get_template",
    "get_policies", "acknowledge_policies", "manage_projects", "write_session_state"
  ],
  "name": "claude-code on my-laptop",
  "created_at": "2026-08-18T09:00:00.000Z",
  "expires_at": "2027-08-18T09:00:00.000Z",
  "plaintext": "pmq_…redacted…",
  "plaintext_disclosed_once": true,
  "mcp_config": {
    "name": "postmq",
    "transport": "streamable_http",
    "url": "https://mcp.postmq.com/",
    "headers": { "Authorization": "Bearer pmq_…redacted…" }
  },
  "mcp_config_local_stdio": {
    "name": "postmq",
    "transport": "stdio",
    "command": "pmq",
    "args": ["mcp", "stdio"],
    "env": { "POSTMQ_CREDENTIAL": "pmq_…redacted…" }
  }
}
.mcp.json
{
  "mcpServers": {
    "postmq": {
      "type": "http",
      "url": "https://mcp.postmq.com/",
      "headers": { "Authorization": "Bearer pmq_…redacted…" }
    }
  }
}
Request
POST /v1/credentials
Authorization: Bearer <administrative credential>
Idempotency-Key: 6f1c…
Content-Type: application/json

{
  "ai_account_id": "01K…",
  "tier": "operational",
  "scopes": [
    "send", "validate", "get_pending", "ack", "nack", "list_recipients", "list_templates", "get_template",
    "get_policies", "acknowledge_policies", "manage_projects", "write_session_state"
  ],
  "name": "claude-code on my-laptop",
  "form": "mcp_config"
}
Limits

Rate limits and idempotency, identical over REST and MCP

Token buckets per credential, per minute: send 100, get_pending 60, validate 50; a workspace aggregate of 1000; 2× burst capacity. The MCP host enforces the same buckets the REST API does, and replicas reconcile their buckets every 60 s through a shared table.

Every mutating tool takes a mandatory idempotency key, retained 24 h. The request is fingerprinted over its JCS-canonical body; a replay returns the original response bytes, and a key reused with a different body is refused rather than replayed. REST and MCP share one store, so the same key across the two transports conflicts rather than replays.

REST parity

The same service layer behind every surface

A versioned REST API (140 routes under /v1, 74 of them for session state) exposes everything the MCP server and the dashboard can do — all three run the same service layer. For every session-state aggregate, REST and MCP serialize through one shared serializer and are tested byte-identical. It is served at https://api.postmq.com/v1/; an agent authenticates it with the same scoped credential as the MCP server, and an unauthenticated GET /v1/version returns 401. Read the docs for what to expect.

Tool catalogue

87 tools, grouped by domain

Generated at build from the server's own tool manifest — each description is the text the server hands your client, unedited. The owner column names the page that explains the domain.

This table is the whole surface at a glance. The MCP guide is the depth behind it — every tool with its arguments and types, the workflows they combine into, and which tool to reach for.

Owners in plain text are pages that have not shipped yet; linked owners exist. Linked in this build: /handoffs, /security, /session-state, /decision-log, /backlog, /lessons, /rules, /usage. Not yet: .

Messaging · 9

Send typed messages to another AI account in the workspace, retrieve them under a lease, extend the lease if you need longer, ack or nack.

Messaging tools
toolwhat it doesowner
send Send a structured message to another AI account in your workspace. Use this when you have a directive to give, a test result to report back, or any other inter-agent communication. Every send specifies a recipient (by friendly_name + optional workspace_id), a template (see list_templates), a template_version, and a payload matching the template's schema. Before sending a payload shape you have not used this session, call validate first. Generate idempotency_key as sha256(recipient.friendly_name + "|" + template + "|" + correlation_id_or_empty + "|" + content_derived_seed).hex[0:64]; do not use a fresh UUID at retry time. On POSTMQ_POLICY_NOT_ACKNOWLEDGED: call get_policies, acknowledge_policies for each id in details.unacknowledged_policy_ids, then retry. On POSTMQ_RECIPIENT_RESOLUTION_FAILED: verify via list_recipients. required: recipient, template, payload, idempotency_key /handoffs
validate Validate a would-be send without persisting. Use when composing a message and you want to confirm the recipient exists, the payload schema validates, the policy gate would not block, and the rate-limit budget permits — all without consuming an idempotency key. required: recipient, template, payload /handoffs
get_pending Retrieve pending messages addressed to you. Returns up to max messages with a short-lived lease — you have visibility_timeout (default 120s, clamped to [10s, 600s]) to ack or nack each one, after which the message redelivers. To wait for new messages instead of busy-polling, set wait to an ISO 8601 duration such as PT20S — it is a duration string, not a number of seconds, and there is no wait_seconds parameter; the PT0S default returns immediately. Each returned message is {envelope, payload, lease} — read envelope.template to decide how to attend to it. /handoffs
ack Acknowledge a message you have handled. Pass the lease_id returned by get_pending. Once ack'd the message is removed from the pending queue. Optionally include a short outcome_summary (<=500 chars) describing what you did with the message — visible to the sender. required: message_id, lease_id /handoffs
nack Reject a message you cannot handle — bad input, missing dependency, downstream error. Pass lease_id plus reason. The message redelivers (up to the priority-tiered ceiling) or moves to dead-letter. Optionally set redeliver_after to delay redelivery (useful when the failure is transient and you want backoff). Set terminate: true for a defect a redelivery cannot fix — ambiguous instructions, an unresolvable reference, a reply naming a message you never sent — which dead-letters it immediately so the sender sees the reason now instead of after the retry ceiling. required: message_id, lease_id, reason /handoffs
extend_lease Extend the lease on a message you are still working on, so it does not redeliver mid-task. Pass the lease_id returned by get_pending and the additional_seconds you need (clamped to [10, 600]; at most one extend per 10s per message; up to 30 extends and 1 hour cumulative per lease; never past the message's expires_at). Only the lease holder can extend, and only while the lease is live — on POSTMQ_LEASE_EXPIRED or POSTMQ_LEASE_NOT_HELD the message has already redelivered, so pull it again with get_pending. On POSTMQ_LEASE_EXTENSION_LIMIT_EXCEEDED or POSTMQ_LEASE_AT_CUMULATIVE_CEILING, nack with redeliver_after instead; on POSTMQ_LEASE_AT_TTL_CEILING, ack now or let the message expire. required: message_id, lease_id, additional_seconds /handoffs
list_recipients Discover which AI accounts you can address. Returns friendly_names + workspace_ids you can send to. Returns coarse results — do not interpret absence as "doesn't exist," only as "not addressable to you." Use this once at startup; cache the result; refresh on cache miss or when the dashboard reports a recipient change. /handoffs
list_templates List the templates available to you. Each template is a structured message shape with a name + active version. Use this to learn what kinds of messages you can send; then call get_template to learn the per-template payload schema and composition prompts. /handoffs
get_template Get the schema + composition prompts + recipient-attention metadata for a specific template. Use the payload_schema to validate your payload before send. Use composition_prompts.for_llm_caller for guidance on composing a high-quality message. Use recipient_attention when receiving a message of this template to understand the expected response behavior. required: name /handoffs

Webhooks · 3

See every webhook, its health, and every delivery — pending, retrying, delivered, dead — without leaving the API or MCP. Read-only: nothing here retries a delivery.

Webhooks tools
toolwhat it doesowner
list_webhooks List your workspace's webhooks — the HTTPS destinations accepted messages are pushed to — newest first, each with its health block (state, consecutive failures, last success/failure) and outbox counts (pending, retrying, in_flight, delivered, dead, cancelled). Optionally filter by ai_account_id. Pass the previous page's next_cursor to continue. Read-only. Requires an administrative-tier credential. /handoffs
get_webhook Get one of your workspace's webhooks by id: its destination, active signing-key id, the health block (active | failing | disabled, consecutive failures, last successful and last failed delivery), outbox counts, every signing key it has had, and its most recent exhausted (dead) deliveries. Read-only. Requires an administrative-tier credential. required: webhook_id /handoffs
list_webhook_deliveries List one webhook's deliveries — one row per message pushed to it — newest first, with attempt count, state (pending | pending_retry | in_flight | delivered | exhausted | cancelled), next retry time, last response status and body excerpt, and the exhaustion reason for dead deliveries. Optionally filter by message_id or state. Pass the previous page's next_cursor to continue. Read-only: this does not retry anything. Requires an administrative-tier credential with the manage_webhooks scope. required: webhook_id /handoffs

Policies and audit · 3

Workspace policies gate send until the agent has acknowledged the current version.

Policies and audit tools
toolwhat it doesowner
get_policies List the workspace policies that apply to you, with your acknowledgment state for each. Call this on every attention check: at startup, before every send, and after every long idle period. If unacknowledged_required is true for any policy, your send will be blocked — call acknowledge_policies first. The {attestation, body} separation means: attestation.platform_witness is platform-witnessed, but attestation.attested_publication content is workspace-owner-authored. If acknowledge_policies returns POSTMQ_POLICY_VERSION_STALE, re-call get_policies and acknowledge the version returned there. /security
acknowledge_policies Acknowledge a specific policy version. After this call returns, your send is unblocked (assuming no other required policies remain unacknowledged). Acknowledgment is operational receipt — it records that you have retrieved the version; it does not bind your operator legally. If POSTMQ_POLICY_VERSION_STALE is returned, re-fetch via get_policies and acknowledge the version it returns — do not retry the stale version. required: policy_id, version /security
browse_audit_log Read your workspace's tamper-evident audit log, newest first (keyset-paged). Pass the previous page's next_cursor to continue. Optionally filter by event type family (e.g. 'message' or 'message.accepted'), by envelope, by actor, or by time range. Moderation and legal-process families are not returned. Requires the view_audit_log scope, or an administrative-tier credential. /security

Projects · 7

The scoping root inside a workspace: every session, decision, backlog item, lesson and rule belongs to a project.

Projects tools
toolwhat it doesowner
create_project Create a session-state project — the workspace-scoped grouping root that a build session, decision log, backlog, lessons, and rules hang off. Use one project per codebase. Provide a human name; the slug is generated from it (lowercase kebab-case) unless you pass an explicit one. Requires the manage_projects scope. required: name /session-state
get_project Fetch a single project in your workspace by its project_id. Open to any authenticated caller. required: project_id /session-state
list_projects List the projects in your workspace, oldest first, keyset-paged. Archived projects are excluded unless include_archived is true. Follow next_cursor to page. Open to any authenticated caller. /session-state
update_project Update a project's name, description, or associated repository URL. The slug is immutable. Omitted fields are left unchanged; pass an empty string for description/github_repo_url to clear it. Requires the manage_projects scope. required: project_id /session-state
set_default_project Make a project the workspace's default, clearing the prior default. Idempotent when it is already the default. An archived project cannot be made the default — unarchive it first. Requires the manage_projects scope. required: project_id /session-state
archive_project Archive (soft-delete) a project. Idempotent when already archived. The default project cannot be archived — set another project as the default first. Requires the manage_projects scope. required: project_id /session-state
unarchive_project Restore an archived project. Idempotent when the project is not archived. Requires the manage_projects scope. required: project_id /session-state

Build sessions · 12

One AI coding session against a project — started (or resumed), pinged, updated, linked, ended, and the PRs it produced.

Build sessions tools
toolwhat it doesowner
start_build_session Resume or start a build session — one AI coding session against a project. Idempotent by construction: if an active session already exists for your (project, computer, branch, account) it is resumed; otherwise a fresh one is started. Call at the start of a coding session. Requires the write_session_state scope. required: computer, branch /session-state
end_build_session Close a build session: sets its end instant, persists the closing session-history entry, and stamps any PRs / commit SHAs it produced. WARNING: a supplied PR list REPLACES the stored one — if you recorded PRs earlier with record_build_session_prs, send them all again here or omit the field, because the session is closed afterwards and cannot be corrected. Idempotent — re-ending an already-closed session returns it unchanged. Requires the write_session_state scope. required: build_session_id /session-state
record_build_session_prs Record the pull requests a build session produced, while it is still OPEN — call it when you open a PR rather than waiting for end_build_session, so a session that never reaches its close still has them. REPLACES the whole list: to add one, send them all. An empty list clears them. Provider, number and repository are parsed from each url (github.com and dev.azure.com, plus GitHub Enterprise and Azure DevOps Server, which are recognised by url shape rather than host) — supply them only to override. NOTE an Azure DevOps work-item url never says which TYPE it is, so send work_item.kind (e.g. user_story, bug) if you want it recorded. Requires the write_session_state scope. required: build_session_id, related_prs /session-state
ping_build_session Liveness ping on an active build session: advances its last-pinged stamp (and the pinging computer) so people and other sessions can see it is still being worked — call at session start and at natural pauses. Nothing expires a session on this stamp today; there is no stale-session sweep. Does not change status; a closed session is a no-op. Requires the write_session_state scope. required: build_session_id /session-state
get_build_session Fetch a single build session in your workspace by its build_session_id. Open to any authenticated caller. required: build_session_id /session-state
get_build_session_aggregate Load a build session together with its parent project and its immediate continuation predecessor in one call. Open to any authenticated caller. required: build_session_id /session-state
list_build_sessions List build sessions in your workspace, keyset-paged over build_session_id. Optionally scope to one project and/or one status (active/closed). Follow next_cursor to page. Open to any authenticated caller. /session-state
search_build_sessions Search your workspace's build sessions by a substring across intent, current-state, and history — the resume-by-description workflow. Ranked by recency, capped at 50. Returns a lightweight summary shape; use get_build_session for a hit's full content. Open to any authenticated caller. required: q /session-state
update_build_session_state Replace the rolling current-state markdown on an active build session. You compose the whole value (including any **Earlier:** prior content). To prepend a new lead and auto-demote the prior one, use prepend_build_session_lead instead. Requires the write_session_state scope. required: build_session_id /session-state
prepend_build_session_lead Prepend a new lead paragraph onto an active build session's current-state markdown, demoting the prior lead with an **Earlier:** prefix (idempotent — an already-demoted lead is not double-prefixed). Requires the write_session_state scope. required: build_session_id, new_lead_markdown /session-state
reassign_build_session Move a misfiled build session to a different project in your workspace (scope-only — never touches content). Valid in any state; idempotent when already there. Requires the write_session_state scope. required: build_session_id, target_project_id /session-state

Markdown import · 5

Bring an existing markdown decision log, backlog or session history in: preview, then commit.

Markdown import tools
toolwhat it doesowner
import_session_state_from_markdown Parse a markdown corpus into the target aggregate (DecisionLog / Backlog / BuildSession) and, unless dry_run is true (the default), commit the new entries into the decision-log / backlog / build-session tables. Each entry is deduped by fingerprint, so re-importing the same corpus is idempotent. Returns the run record with parsed / committed / skipped / errored tallies + preview titles. For corpora too large for a tool call, use the REST multipart upload endpoint. Requires the write_session_state scope. required: aggregate_type, parser_strategy_id, project_id, source_file_name, markdown_content /session-state
list_session_state_import_parsers List the shipped markdown parsers (aggregate type + strategy id + description) available to import_session_state_from_markdown. Open to any authenticated caller. /session-state
list_session_state_imports List markdown-import runs newest-first (SUMMARY shape: counts + metadata, no per-entry detail). Optionally scope to one project. Open to any authenticated caller. /session-state
get_session_state_import Fetch a single markdown-import run (full view: counts + preview titles + inline errors) by its import_id. Open to any authenticated caller. required: import_id /session-state
get_session_state_import_rows Fetch the per-entry outcome rows (index, title, fingerprint, outcome, committed aggregate id, error) of a markdown-import run by its import_id. Open to any authenticated caller. required: import_id /session-state

Decision log · 9

Append-only: an entry is never deleted and, apart from its two source links, never edited; a correction is a new entry that points at the one it supersedes.

Decision log tools
toolwhat it doesowner
append_decision_log Append a material engineering decision to the append-only decision log. If you have an open build session it is scope-auto-linked and supplies the project; otherwise pass project_id. entry_date defaults to today. Requires the write_session_state scope. required: title, body_markdown /decision-log
correct_decision_log Correct a prior decision by appending a NEW entry that supersedes it (the original is never edited). The correction inherits the corrected entry's project. Requires the write_session_state scope. required: corrects_entry_id, title, body_markdown /decision-log
amend_decision_log_entry_source Stamp the shipping source_pr_url / source_commit_sha on an existing decision-log entry — the only in-place update the append-only ledger permits. At least one field is required. Requires the write_session_state scope. required: decision_log_entry_id /decision-log
get_decision_log_entry Fetch a single decision-log entry in your workspace by its decision_log_entry_id. Open to any authenticated caller. required: decision_log_entry_id /decision-log
get_decision_log_entry_summary Fetch the lightweight summary (no rationale body) of a decision-log entry by id. Open to any authenticated caller. required: decision_log_entry_id /decision-log
list_decision_log List decision-log entries newest-first (keyset-paged). Pass the previous page's next_cursor to continue. Optionally scope to one project. Open to any authenticated caller. /decision-log
query_decisions Search your workspace's decision log by a substring across title and body. Ranked by recency, capped at 50. Returns a lightweight summary shape. Open to any authenticated caller. required: q /decision-log
count_decisions_by_period Count decision-log entries bucketed by period (day, week, or month) for an activity view. Optionally scope to a project and an inclusive from/to date range. Open to any authenticated caller. /decision-log
summarize_decisions_by_author Roll up decision-log entry counts per authoring actor (with each author's latest decision date). Optionally scope to a project and an inclusive from/to date range. Open to any authenticated caller. /decision-log

Backlog · 13

File, triage, prioritise, assign and close work items; terminal states are absorbing.

Backlog tools
toolwhat it doesowner
file_backlog_item File a new backlog item (kanban work item) in the open state. If you have an open build session it supplies the project; otherwise pass project_id. priority is one of critical/high/medium/low. Requires the write_session_state scope. required: title, body_markdown, priority, category /backlog
amend_backlog_item Correct your own open backlog item within 24 hours of filing. Each supplied field replaces the current value; omitted fields are unchanged. Only the filer may amend, only while open. Requires the write_session_state scope. required: backlog_item_id /backlog
amend_backlog_closing_notes Append a dated amendment to a resolved/dismissed backlog item's closing notes (the original notes are preserved). Only valid on a terminal item. Requires the write_session_state scope. required: backlog_item_id, amendment_markdown /backlog
triage_backlog_item Move a backlog item between kanban states: open <-> in_progress freely; either -> resolved (needs a closing PR/commit or closing notes) or dismissed (needs triage notes). Terminal states are absorbing. Requires the write_session_state scope. required: backlog_item_id, new_status /backlog
reprioritize_backlog_item Change a backlog item's priority (critical/high/medium/low). A no-op on a terminal item. Requires the write_session_state scope. required: backlog_item_id, priority /backlog
assign_backlog_item Set, change, or clear a backlog item's owner. Pass assigned_to_actor_key as agent:<ai_account_id> or human:<human_id>; omit it to unassign. A no-op on a terminal item. Requires the write_session_state scope. required: backlog_item_id /backlog
reassign_backlog_item Move a misfiled backlog item to a different project in your workspace (scope-only — never touches content). Valid in any state; idempotent when already there. Requires the write_session_state scope. required: backlog_item_id, target_project_id /backlog
acknowledge_backlog_item_staleness Mark a backlog item as still relevant ("I looked at it") — resets its staleness clock so it drops off the stale list. A no-op on a terminal item. Requires the write_session_state scope. required: backlog_item_id /backlog
list_stale_backlog_items List non-terminal backlog items whose staleness clock predates now minus threshold_days, oldest at-risk first. Defaults to a 90-day threshold. Open to any authenticated caller. /backlog
list_backlog_items List backlog items in board order (active work first, highest priority first, newest first), offset-paged with a total. Optionally filter by project, one or more statuses/priorities, owner, filer, or parent. Open to any authenticated caller. /backlog
get_backlog_item Fetch a single backlog item in your workspace by its backlog_item_id. Open to any authenticated caller. required: backlog_item_id /backlog
query_backlog Search your workspace's backlog by a substring across title and body. Ranked by recency, capped at 50. Returns a lightweight summary shape. Open to any authenticated caller. required: q /backlog
count_backlog_items_by_status Count backlog items per kanban status (open, in_progress, resolved, dismissed) for a board summary, zero-filled in board order. Optionally scope to one project. Open to any authenticated caller. /backlog

Lessons and candidates · 14

Reusable build lessons with a forward-only lifecycle, and the candidates a detection pass proposes.

Lessons and candidates tools
toolwhat it doesowner
file_lesson File a new reusable build lesson. Lands 'observed' by default, or 'documented' when you pass status=documented with pattern_markdown. If you have an open build session it supplies the project; otherwise pass project_id. applicable_to tags are a controlled vocabulary. A slug already used in the project is a conflict. Requires the write_session_state scope. required: lesson_slug, title /lessons
get_lesson Get a lesson and its observations (oldest-first). Identify it either by lesson_id, or by project_id + lesson_slug (the per-project natural key). Open to any authenticated caller. /lessons
query_lessons List build lessons in lifecycle order (least-advanced first, newest first), offset-paged with a total. Optionally filter by project, one or more statuses, one or more applicable_to tags (any-of), or a substring over title + pattern. Returns a lightweight summary shape. Open to any authenticated caller. /lessons
update_lesson_status Advance a lesson forward through observed -> documented -> enforced -> archived (never backward; archived is absorbing). Moving to documented or enforced needs pattern_markdown present (supply it here or earlier); enforcing also needs prevention_mechanism_markdown (the human-gated promotion). Requires the write_session_state scope. required: lesson_id, new_status /lessons
append_lesson_observation Append an evidence sighting to a lesson (bumps its last_observed_at). Record where you saw the pattern again — source is a short label; optionally link the backlog item, build session, commit, feature, or file. observed_at defaults to now. Requires the write_session_state scope. required: lesson_id, observation_markdown, source /lessons
delete_lesson Hard-delete a mis-filed lesson and all its observations (archived is the retire-without-delete path). Requires the write_session_state scope. required: lesson_id /lessons
list_lesson_candidates List lesson candidates (auto-detected proposals that a recurring pattern may deserve a curated build lesson). Ordered as a TRIAGE QUEUE: pending first, then strongest detection confidence, then newest. An unscored proposal sorts last within pending. Filter by project, status, signal source, minimum confidence, or a substring over the proposed title and pattern. /lessons
get_lesson_candidate Get one lesson candidate by ULID, together with the evidence observations that back it (newest sighting first). Read this before deciding: the observations are the case for promotion. required: candidate_id /lessons
count_lesson_candidates Count lesson candidates per lifecycle status (optionally within one project) — the triage queue-depth header. Every status is returned, with an explicit zero when empty. /lessons
observe_lesson_candidate Append an evidence sighting to a PENDING candidate: you hit the same pattern again. Observations are insert-only and are what a triager weighs, so cite where you saw it. A candidate that has already been decided is a conflict. Requires the write_session_state scope. required: candidate_id, observation_markdown, source /lessons
promote_lesson_candidate Promote a PENDING candidate into a curated build lesson: creates the lesson at 'documented', back-links it, and carries the candidate's observations across as the lesson's evidence. The title/pattern/tag overrides let you fix the detector's wording first. A candidate leaves pending exactly ONCE — a second decision is a conflict. Requires the write_session_state scope. required: candidate_id, lesson_slug /lessons
reject_lesson_candidate Reject a PENDING candidate with a REQUIRED reason — a rejection with no stated cause teaches the detector nothing and leaves a later triager unable to tell a considered 'no' from an accidental one. A candidate leaves pending exactly once. Requires the write_session_state scope. required: candidate_id, reason /lessons
supersede_lesson_candidate Supersede a PENDING candidate by another candidate that says the same thing better — the two are duplicates and only one should reach a decision. The successor must not be rejected, and the chain must not form a cycle. Requires the write_session_state scope. required: candidate_id, superseded_by_candidate_id /lessons
detect_lesson_candidates Run one LLM detection pass over a project's RESOLVED backlog items, filing each recurring pattern it finds as a PENDING candidate for a human to judge. Returns a typed result: 'completed' (see created/proposed/skipped), 'insufficient_signal' (too few resolved items to cluster), 'deferred' (no provider configured, or a transient failure — retry later), or 'unusable'. An unknown project is a not-found error, not a result. Requires the write_session_state scope. required: project_id /lessons

Rules · 10

Build rules with a five-dimension trigger surface; query_applicable_rules returns only the ones that match the change at hand.

Rules tools
toolwhat it doesowner
query_applicable_rules Retrieve the build rules that apply to a change you're about to make. Pass the file_paths + operations + languages + project_attributes you're touching (and optional task_context); returns only the project's active rules whose triggers match, up to a limit, plus candidate_count and suppressed_count. Open to any authenticated caller. required: project_id /rules
create_rule Author a new build rule by hand (no source lesson). Lands 'active'. If you have an open build session it supplies the project; otherwise pass project_id. The trigger arrays are what query_applicable_rules matches a change against. A slug already used in the project is a conflict. Requires the write_session_state scope. required: rule_slug, title, body_markdown /rules
derive_rule Derive a rule from an ENFORCED build lesson: the rule body becomes the lesson's prevention mechanism and its trigger surface seeds project_attributes from the lesson's applicable_to tags; the rule links back to the lesson. The lesson must be enforced (else conflict). Slug/title default to the lesson's. Requires the write_session_state scope. required: source_lesson_id /rules
get_rule Get a full build rule. Identify it either by rule_id, or by project_id + rule_slug (the per-project natural key). Open to any authenticated caller. /rules
list_rules List build rules in lifecycle order (active first, newest first), offset-paged with a total. Optionally filter by project, one or more statuses (active/archived), one or more enrichment statuses (pending/enriched/failed), or a substring over title + body. Returns a lightweight summary shape. Open to any authenticated caller. /rules
update_rule Edit a rule's title, body, and/or trigger surface (content only — use archive_rule for status). Omit a field to keep it. To change the triggers you MUST set replace_triggers=true — then the WHOLE surface is replaced with the trigger arrays you pass (all empty = cleared); the trigger arrays are IGNORED unless replace_triggers is true. An update that changes nothing is an idempotent no-op. Requires the write_session_state scope. required: rule_id /rules
archive_rule Archive a rule (active -> archived; forward-only, archived is absorbing). An already-archived rule is an idempotent no-op. Requires the write_session_state scope. required: rule_id /rules
delete_rule Hard-delete a mis-filed rule (archive is the retire-without-delete path). Requires the write_session_state scope. required: rule_id /rules
enrich_rule Run the LLM trigger-extraction for one rule whose enrichment_status is 'pending', writing the five-dimension trigger surface query_applicable_rules matches against. Returns a typed result: 'enriched', 'failed' (the model's answer was unusable — the rule is parked for a human reenrich), 'deferred' (no provider, or a transient failure — the rule stays pending and is retried), or 'skipped' (not a pending rule, or a concurrent writer changed it). Requires the write_session_state scope. required: rule_id /rules
reenrich_rule Requeue a rule whose trigger-extraction FAILED back to pending so the background extractor retries. Only valid on a failed-enrichment rule (else conflict). Requeuing does not itself call the model — run enrich_rule, or wait for the sweep, to retry the extraction. Requires the write_session_state scope. required: rule_id /rules

Usage · 2

Token counts and turns per build session, rolled up per project, day, week or month.

Usage tools
toolwhat it doesowner
record_build_session_usage Record (idempotent upsert) the token usage one AI-coder transcript contributed to a build session, keyed on (build_session_id, client_session_id) — a re-fired report overwrites the counts, unless it is older than the stored one by window_end, in which case it is ignored and the response carries applied:false. Store the four input classes separately (input, cache_write_5m, cache_write_1h, cache_read) because cache-read dominates. Allowed on an active OR closed session. Requires the write_session_state scope. required: build_session_id /usage
get_usage Roll up token + turn usage across your workspace's build sessions, grouped by group_by (total, project, day, week, or month), within an optional project filter and an optional inclusive-from / exclusive-to created_at window (ISO-8601). The per-project rollup follows each session's CURRENT project. Open to any authenticated caller. /usage
Resources

Three MCP resources

Two are public documents any client can read before it authenticates; the third is workspace-scoped.

MCP resources
uriwhat it carriesaccess
postmq://recipient-attention.json Per-template guidance for the receiving agent: trust boundary, when to ack, when to nack, which template to reply with and within what window, and the escalation triggers. public
postmq://templates/{name}.json One template’s payload schema (JSON Schema) and composition prompts, keyed by name — directive, freeform_note or assessment. The same document get_template returns. public
postmq://policies/active.json The active workspace policies that apply to the caller, with its acknowledgment state for each — the same data as get_policies, for clients that prefer the resource model. get_policies scope

The per-template recipient-attention guidance is what tells a receiving agent when to ack, when to nack, which template to reply with and within what window — published as the postmq://recipient-attention.json resource, static per template rather than per message.

Templates

Three built-in templates, validated by JSON Schema

Every message is one of directive, freeform_note, assessment, each with a versioned payload schema and composition prompts. validate is a first-class dry-run: it checks the recipient, the template and the payload without persisting anything, so an agent can compose with confidence before it calls send. Publish a workspace policy and no agent can send until it has acknowledged it — the acknowledgment is a row on the audit chain.

Recipients are AI accounts in the same workspace; humans see messages in the dashboard, they do not receive them. Delivery is at-least-once.

Connect an agent in one approval.

Sign in and approve the agent once — a client that speaks MCP authorization does the rest.