MCP server
An AI assistant that speaks the Model Context Protocol (MCP) can use Koutei with your API key: list the templates, read one, check a request, preview it and make the PDF. It uses the same templates, checks, credits and limits as the API.
Works with
The server needs a client that sends your API key in a request header. Where each one stands today:
| Client | Status | How it sends the key |
|---|---|---|
| Claude Code | Documented by the vendor; not yet tested by us | A request header, in .mcp.json or with claude mcp add (below). |
| Cursor | Documented by the vendor; not yet tested by us | A request header, in mcp.json (below). |
| VS Code (GitHub Copilot) | Documented by the vendor; not yet tested by us | A request header, in the server's headers in mcp.json. |
| OpenAI Responses API | Documented by the vendor; not yet tested by us | A request header, in the mcp tool's headers. |
| claude.ai | Only organisations with the request-headers beta | A request header, where the beta is on. |
| ChatGPT | Needs OAuth sign-in; not supported yet | — |
If your client doesn't offer you the PDF from a result, check the document with the preview image, and make the final with the API instead: a final made here uses its credit even if your client drops the file.
Before you start
- You need an API key from Settings. While you try it, give the assistant a Preview only key: it can check and preview, and can never use a credit. A Full access key can also make final documents.
- You need a client where you can set a request header, such as Claude Code or Cursor. Every message carries the key in
Authorization: Bearer kt_live_….
Connectors that sign in with OAuth can't use it yet. The server takes a key in the Authorization header and offers no OAuth sign-in, so a connector that can only sign in with OAuth, and cannot send a header of your choosing, cannot connect.
Connect a client
The server is at https://kouteisystems.com/api/mcp. It speaks Streamable HTTP and keeps no session: each message stands on its own.
Claude Code
Put your key in an environment variable named KOUTEI_API_KEY, and add the server to a project's .mcp.json. Claude Code expands ${KOUTEI_API_KEY} there from your environment when it connects, so the key itself stays out of the file:
{
"mcpServers": {
"koutei-pdf": {
"type": "http",
"url": "https://kouteisystems.com/api/mcp",
"headers": {
"Authorization": "Bearer ${KOUTEI_API_KEY}"
}
}
}
}Or add it from the command line:
claude mcp add --transport http koutei-pdf https://kouteisystems.com/api/mcp \
--header 'Authorization: Bearer ${KOUTEI_API_KEY}'Keep the single quotes, so your shell does not put the key into the command. Claude Code stores the header as written, ${KOUTEI_API_KEY} included, in your own user config (~/.claude.json). If your Claude Code doesn't expand ${KOUTEI_API_KEY} there, use the .mcp.json form above. Never put the key itself in a project's .mcp.json or add it with --scope project, and never commit it.
Cursor
In .cursor/mcp.json (one project) or ~/.cursor/mcp.json (every project):
{
"mcpServers": {
"koutei-pdf": {
"url": "https://kouteisystems.com/api/mcp",
"headers": {
"Authorization": "Bearer ${env:KOUTEI_API_KEY}"
}
}
}
}Another client
Give it the address above, the Streamable HTTP transport and the header Authorization: Bearer kt_live_…, with your whole key after Bearer.
The tools
The server offers 3 tools. Ask the assistant to start with the first.
list_templates
The document templates this server can make (invoice, quote and receipt), each with its version, its paper sizes and the names of its required and optional fields. Costs nothing.
| Input | Needed | Type | What it is |
|---|---|---|---|
category | No | string | Only templates in this category, for example "invoices". Leave it out (or null) for all of them. |
get_template
One template in full: the JSON Schema of its fields, its cross-field rules, a complete example request, and what the document is and is not. Read it before generate_document. Costs nothing.
| Input | Needed | Type | What it is |
|---|---|---|---|
template | Yes | string | A template slug from list_templates: invoice, quote or receipt. |
generate_document
Make an invoice, a quote or a receipt as a PDF from the facts in fields, and make a free preview first. mode is required and nothing is assumed: "validate" checks the request and makes nothing; "preview" makes a watermarked preview that is not a valid document, costs nothing and returns page 1 as an image; "final" makes the real document and uses 1 credit. When a person is in the conversation, show them a preview and get their approval before "final". Never invent a fact: names, tax numbers, bank details, amounts, dates and document numbers must come from the person or their data; ask when one is missing. Every answer that is not an error includes totals: the subtotal, discount, tax, total, amount paid and balance due the document prints, as decimal strings in its currency. Read the numbers from there instead of working them out. On an error, error.details lists every problem: fix those fields and call "validate" again. An identical final repeated in the following 24 hours returns the same document and is not charged again. Nothing is sent to anyone, no document number is allocated or checked, and no document is asserted to be tax-compliant, an e-invoice or legally valid.
| Input | Needed | Type | What it is |
|---|---|---|---|
template | Yes | string | A template slug from list_templates: invoice, quote or receipt. |
fields | Yes | object | The document's facts for this template. get_template returns their JSON Schema and an example. |
mode | Yes | "validate" | "preview" | "final" | Required. "validate": check only. "preview": free and watermarked. "final": the real document, 1 credit. |
template_version | No | string | Optional. Pins the template's major version, as a string of digits such as "2". |
reference | No | string | Optional. Your own label for this document, such as a job number. Never printed; stored with the document's record for 90 days. Never personal data. |
paper | No | "a4" | "letter" | Optional. "a4" (the default) or "letter". |
locale | No | "en" | Optional. Only "en". |
idempotency_key | No | string | Optional. Your own key for a safe retry (the REST Idempotency-Key), sent as it is, not in double quotes: the same key with the same request returns the same document, uncharged, for 24 hours. Without it, a final still gets one, derived from your API key and the request, so an identical final is charged once. |
include_pdf | No | boolean | Optional, for mode "preview": also return the watermarked preview PDF. A final always returns its PDF; true is refused with any other mode. |
The fields of each template are the API's: get_template returns them, and the API reference lists them.
Modes and costs
generate_document needs mode. Nothing is assumed: a request never uses a credit unless it says final.
| Mode | What it does | Credits | Key needs |
|---|---|---|---|
validate | Checks the request exactly as a document request is checked, and makes nothing. | 0 | any key |
preview | Makes a watermarked preview that is not a valid document, and returns page 1 as an image. | 0 | documents:preview |
final | Makes the real document and returns the PDF. | 1 | documents:create |
A final's credit is taken only after the PDF has been made. The template tools, a check and a preview cost nothing, and so does every error.
What a result holds
- A preview or a final returns a receipt with
document,filename,credit_used,credits_remaining,idempotent_replayandtotals, as JSON in the result and as its first text block. - A preview adds page 1 as an image (
image/png, at most 1,000,000 bytes). Withinclude_pdfset totrueit also returns the watermarked PDF. - A final adds the PDF as an embedded resource (
application/pdf, base64, at most 2,000,000 bytes before base64). Itsuri, such askoutei://documents/doc_….pdf, is a label: the server offers no resources to fetch, and Koutei keeps no copy, so save the PDF from the result. - Every result's
_metacarriescom.kouteisystems/status(the HTTP status the API would have answered with) andcom.kouteisystems/request-id.
Retries
An identical final repeated within 24 hours returns the same document and is not charged again, even if the assistant sends no idempotency_key: the server makes one from your API key and the request. Send your own idempotency_key to choose it; it works like the API's Idempotency-Key, and the same key with a different request is refused with 422 idempotency_key_reused.
Errors and limits
- A refused call is a tool result with
isError: true. Its JSON is the API's error envelope, the samecode,detailsandcredit_used. Errors explains each code. When the API would have said wait,com.kouteisystems/retry-after-secondsgives the seconds. - The API's limits apply. Each tool call counts toward the key's 120 requests a minute; previews share the account's preview limits with the API and the web app. The API reference lists them.
- Repeating an identical final is free, but limited: a key may replay 60 final documents a minute. Over that, the call is refused with
429 rate_limitedand the detailreplay_limit; nothing is charged, and the same call replays once the wait is over. - With no credits left, a final is refused with
402 insufficient_credits.
What the server tells the assistant
These are the server's instructions to the assistant, word for word:
- Koutei PDF makes invoice, quote and receipt PDFs from facts you send. Start with list_templates, read get_template for the fields, then generate_document.
- Human review: when a person is in the conversation, make a "preview", show it and get their approval before "final". A preview is watermarked and is never a valid document: never present one as final.
- No invented facts: the seller, the client, tax numbers, bank details, amounts, dates and document numbers must come from the person or their data. If a required value is unknown, ask.
- No sending: this server has no email or delivery tools and never contacts anyone. Delivering the document is the person's own action.
- No legal or tax claims: a document is not asserted to be tax-compliant, an e-invoice or legally valid.
- Credits: only "final" uses a credit (1). "validate", "preview", a repeat of an identical final in the following 24 hours, and every error cost nothing; each result states credit_used.
- Totals: every validate, preview and final result states totals, the figures the document prints (subtotal, discount, tax, total, amount paid, balance due). Quote them from there; do not recompute them.
- Errors: read error.details, fix the named fields and call "validate" again.
The same rules are in the description of generate_document, for clients that don't show instructions.