Skip to content

MCP reference

The MCP endpoint is the same code path as the public API: a document rendered by Claude and one rendered by curl go through one function, so quota, scope and published-version resolution cannot drift between them.

POST https://composepdf.com/mcp

Transport is Streamable HTTP, stateless — no Mcp-Session-Id is issued and none is expected. See the connection guide for client setup.

Header keyId in logs Organization
API key x-api-key, or Authorization: Bearer rsp_… the key the one the key names
OAuth Authorization: Bearer <access token> null — a person authorised it your oldest membership, or organizationId

A request with no credential is answered 401 with a WWW-Authenticate header naming /.well-known/oauth-protected-resource. That header is the whole OAuth discovery mechanism; a client that only takes a URL follows it to dynamic client registration.

tools/list shows only what the credential can actually do. A render key never sees put_dataset_rows; a data-write key never sees render_document. A model that cannot see a tool will not spend a turn discovering it is forbidden.

Tool Role What it does
list_documents render Published templates, one id⇥title per line
describe_document render The fields a template takes, one per line
open_form render Puts a form for the person to fill in in the conversation
render_document render Renders to PDF, keeps it a few days, returns a link
put_dataset_rows data-write Replaces one top-level array in a dataset
check_document OAuth Checks a 帳票DSL source against the quality gate
create_document OAuth Compiles a checked source into a published template
list_organizations OAuth Only when you belong to more than one

Templates are created by people, not by keys. check_document and create_document are offered on the OAuth connection only — the one authorised by a signed-in person. Neither API key role can reach them, so a key living in someone else’s spreadsheet can render your templates but never write one.

No arguments. Returns one line per template: the id, a tab, the title.

Argument
document required Template id or exact title
organizationId optional OAuth callers with more than one organization

Returns one line per field: path⇥type⇥required⇥example. Array members are written the way the engine reports them (items[].price), so a path in an error message can be found here without translating.

Usually unnecessary — a wrong payload comes back with the same table. Read it when you need the paths before filling something in, such as before open_form.

Argument
document required Template id or exact title
data optional Values already known, in render_document’s data shape. Partial is fine
organizationId optional OAuth callers with more than one organization

On a client that renders MCP Apps, this puts a form (ui://composepdf/document-form) in the conversation for the person to fill in; submitting it calls render_document over the same connection, so no extra key or token is involved. The fields are the ones the contract declares — the same buildForm the embed and the mobile app use, so one template produces one set of fields everywhere.

Call it at most once per template. MCP Apps attaches the UI to the tool, so every call draws another form. That is also why looking up field paths is describe_document’s job and not this one’s.

Argument
document required Template id or exact title
data optional The payload. Omit it to use the template’s own dataset
dataList optional One record per page-run, bound into a single PDF. Exclusive with data
fileName optional Defaults to the template title
organizationId optional OAuth callers with more than one organization

Always renders the published version; pinning an older one belongs to the render API and the studio.

The reply is a link, not the bytes — a PDF is megabytes and a tool result is context. Which link depends on the credential: an OAuth caller holds no API key, so it gets a browser URL; a key caller may not have a studio session, so it gets a curl line. Stored PDFs expire after the retention window.

Argument
source required The .rpt source itself, not a path
data optional Real data to lay out with; omitted, the declarations’ own examples are used
profiles optional Business profiles to lint against, e.g. ["invoice-jp"]
organizationId optional OAuth callers with more than one organization

Runs the three-layer gate in one call — content that did not fit, geometry (overlap, off-plate, uneven margins, too many type sizes), and, with a profile, what a Japanese form is legally required to carry. Answers with one diagnostic per line: line:col⇥severity⇥code⇥message, and the repair to try underneath.

This is how a template is fixed. Iterating on diagnostics converges; iterating on a picture of the page does not, because a page tells you something is wrong without telling you which line made it wrong.

The tool’s own description carries one complete working template and the four rules that trip writers up. That is deliberate: the examples below are also served as resources, but a client that cannot fetch them leaves the model guessing at a syntax — and a guessed syntax compiles into a page with nothing on it. A description always arrives.

Argument
title required What the template is called
source required The .rpt source itself
data optional Real data to check the layout with
profiles optional Business profiles to lint against
organizationId optional OAuth callers with more than one organization

Compiles the source, publishes it, and returns the id render_document takes. It refuses while any diagnostic is an error, and the refusal carries the diagnostics, so the next attempt can be right. Warnings do not block: a template is written in passes, and a gate that fails a half-finished draft is a gate people switch off.

The template lands in a new workspace of its own, and the .rpt is kept beside the published document as the original it was compiled from.

Argument
datasetId required
path required One segment — "items", never "a.b[0].c"
rows required

A whole-array replacement, not an append: send every row you want to keep. A dataset linked to an external source refuses the write rather than being silently overwritten on its next read.

Four worked .rpt sources are served as resources — rpt://examples/invoice, rpt://examples/invoice-qualified, rpt://examples/inspection-report, rpt://examples/production-schedule. They are the repository’s own examples, not copies, so they cannot go stale. Start from the closest one rather than from an empty file; they are advertised on the same connection that can create.

Errors come back as tool errors carrying the HTTP status and code, so a client can tell “we are out of renders” from “the payload is wrong”:

400 data_contract_violation The payload does not satisfy the contract. The message names the offending paths and repeats the field table, so the next attempt can be right
402 quota_exceeded The month’s allowance is spent, or the plan has no API
404 not_found No template matches, or the name is ambiguous — the message lists the candidates
429 rate_limited Over the per-minute limit for the plan

A template that declares no variables is not checked against a contract (templates published before declarations existed still work), so it renders with whatever it was given. For those, read describe_document first.

document takes a title because that is what people say. An exact match wins; a prefix that matches two templates is refused with the candidates listed rather than guessed at. Rendering the wrong document is worse than asking.