> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blinkpdf.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Blink PDF MCP Server — Render PDFs from AI Agents

> Connect Claude Code, Codex, and other MCP clients to Blink PDF. Your agent renders PDFs, validates request bodies, stages images, and discovers themes and fonts — signed download links, no glue code.

The Blink PDF **MCP server** lets AI agents render Markdown to professional PDFs through the [Model Context Protocol](https://modelcontextprotocol.io). Instead of writing code that calls the REST API, you connect your agent once and it renders PDFs on demand — or validates a request body before spending a render — in plain language.

It is a thin layer over the same REST API (`POST /v1/render` and `POST /v1/render/validate`): billing, plan limits, rate limits, fonts, and rendering diagnostics all stay consistent with everything else you build on Blink PDF.

<Note>
  The MCP server is hosted by Blink PDF — there is nothing to install or run. Authenticate with an API key or OAuth, then point any MCP-capable client at the endpoint. See [Connect a Client](/mcp/setup) for the setup steps.
</Note>

## Endpoint and transport

| Property       | Value                                                                                                  |
| -------------- | ------------------------------------------------------------------------------------------------------ |
| Endpoint       | `https://mcp.blinkpdf.io/mcp`                                                                          |
| Transport      | Streamable HTTP (**not** stdio)                                                                        |
| Authentication | OAuth 2.1 Bearer (primary for remote connectors) or API key `Authorization: Bearer bp_*` / `x-api-key` |

The server speaks **Streamable HTTP**, so any client that supports remote HTTP MCP servers — Claude Code, Codex, and others — connects directly. stdio-only clients connect through a bridge such as [`mcp-remote`](https://github.com/geelen/mcp-remote); see [Connect a Client](/mcp/setup).

## Auth and discovery

Remote connectors that speak OAuth use **OAuth 2.1 authorization code + PKCE** against Blink PDF's authorization server. The MCP resource is `https://mcp.blinkpdf.io/mcp`; the authorization server is `https://identity.blinkpdf.io` with scopes `pdf:render` and optional `offline_access` (refresh tokens only when that scope is granted). After consent, the BFF issues a user-scoped Bearer access token; MCP verifies it and calls REST via internal delegation so the API key never leaves the platform.

IDE and CLI clients that do not support remote OAuth use the same `bp_*` API keys as the REST API, presented as `Authorization: Bearer bp_*` (preferred) or `x-api-key`.

Public, unauthenticated discovery documents (read-only):

| URL                                                                    | Purpose                                                                       |
| ---------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `https://mcp.blinkpdf.io/auth.md`                                      | Agent-oriented registration guide (Auth.md)                                   |
| `https://mcp.blinkpdf.io/.well-known/oauth-protected-resource`         | OAuth Protected Resource Metadata (RFC 9728) for `/mcp`                       |
| `https://mcp.blinkpdf.io/.well-known/oauth-authorization-server`       | Authorization Server Metadata (RFC 8414) plus `agent_auth` / `agent_commerce` |
| `https://mcp.blinkpdf.io/.well-known/blinkpdf-commerce.json`           | Plan and checkout discovery for agent commerce                                |
| `https://mcp.blinkpdf.io/.well-known/agent-skills/index.json`          | [Agent Skills](https://agentskills.io) discovery index                        |
| `https://mcp.blinkpdf.io/.well-known/agent-skills/render-pdf/SKILL.md` | Published `render-pdf` skill artifact                                         |

There is no anonymous render path — every tool call requires a credential (OAuth Bearer or API key).

## Tools

The server exposes **six tools**:

| Tool                     | Purpose                                                                                                                                |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `render_pdf`             | Render Markdown and return a signed download link                                                                                      |
| `validate_render_pdf`    | Dry-run the same request body without rendering                                                                                        |
| `upload_image`           | Stage an image's bytes (base64) — one call for a small image, or ordered parts for a larger one; returns a `blink://asset/<id>` handle |
| `list_themes`            | List the 15 curated [themes](/configuration/themes)                                                                                    |
| `list_fonts`             | List the hosted font catalog and Google Fonts limits                                                                                   |
| `describe_style_targets` | The machine-readable `rules` styling vocabulary                                                                                        |

The render tools accept the same body fields; only `markdown` is required. Blink PDF's defaults already produce publication-grade output, so in most cases only `markdown` is needed.

<Note>
  The `render_pdf` tool description is intentionally compact — it carries a single inline example and points to the [reference resources](#reference-resources) for richer payloads, keeping the agent's context small. The fields below document the full surface for reference.
</Note>

### `render_pdf`

Renders Markdown and returns a **signed download URL** plus usage and diagnostics — never the raw PDF bytes (a rendered document can be megabytes and would flood the agent's context). Every call pre-validates through `POST /v1/render/validate` before rendering; validation failures return structured issues and do not consume render quota.

```text theme={null}
Your agent → render_pdf(markdown, …) → signed download link
```

<ParamField body="markdown" type="string" required>
  The Markdown source to render. GitHub-flavored — supports tables, task lists, GFM alerts, fenced code, and images (`data:` or `http(s):` URLs). Prefer `https://` image URLs; large `data:` base64 often exceeds body limits.
</ParamField>

<ParamField body="title" type="string">
  Document title for the PDF metadata. Must not be blank. Defaults to the first heading when omitted.
</ParamField>

<ParamField body="theme" type="string">
  A curated design preset (default `report-clean`). Call `list_themes` for the catalog. See [Themes](/configuration/themes).
</ParamField>

<ParamField body="page" type="object | string">
  Page layout and appearance: `size` (named preset or `[width, height]` in points), `orientation`, `margin`, and optional full-sheet `background`. JSON object or JSON string.
</ParamField>

<ParamField body="rules" type="array | string">
  Presentation rules that `select` Markdown nodes and `set` properties on them — surgical styling over standard Markdown nodes. See [Styling Rules](/configuration/styling-rules) or `blinkpdf://reference/styles`. JSON array or JSON string.
</ParamField>

<ParamField body="typography" type="object | string">
  Font roles: assign `body`/`heading` families, declare a Google Fonts `pool`, bind custom `named` roles, and tune per-family weights. See [Fonts & Typography](/configuration/fonts-layouts). JSON object or JSON string.
</ParamField>

<ParamField body="numbering" type="object | string">
  Heading auto-numbering. Opt in with `enabled: true`, then set `depthRange`, `all`/`byDepth` formats, and `trailingSeparator`. JSON object or JSON string.
</ParamField>

<ParamField body="googleFontsUrl" type="string">
  A Google Fonts URL whose first family becomes the body/heading typeface (CSS API, specimen, or share form). Up to 4 families per URL.
</ParamField>

<ParamField body="furniture" type="object | string">
  Running headers and footers (six-slot grid), optional `watermark`, `decorations`, cover/closing overrides (`omitFirstPage`, `firstPage`, `omitLastPage`, `lastPage`), and `pageOverrides`. See [Headers & Footers](/configuration/headers-footers) or `blinkpdf://reference/furniture`. JSON object or JSON string.
</ParamField>

<ParamField body="outline" type="object | string">
  PDF outline (bookmarks). Omit for the default depth range `[1, 6]`, pass `depthRange` to constrain depths, or set `enabled: false` to suppress. Do not pass `depthRange` together with `enabled: false`. JSON object or JSON string.
</ParamField>

<ParamField body="pinRemoteImages" type="boolean" default="false">
  When `true`, each remote `http(s)://` image is fetched once and reused for up to 24 hours, so repeat renders of the same request stay byte-identical even if the origin changes. Scoped per credential. See [Document Options](/configuration/document-options#the-pinremoteimages-field).
</ParamField>

<Tip>
  Beyond the hosted families, agents can render with any [Google Fonts](https://fonts.google.com) family by passing `googleFontsUrl` — available on every plan. Arbitrary font *uploads* are still not supported.
</Tip>

#### What `render_pdf` returns

Every successful call returns three things:

* A **download URL** of the form `GET /d/:token` — a compact signed link the agent can hand straight to you.
* The PDF **byte size** and the link's **expiry timestamp** (24 hours out).
* A **usage** summary (render units this period, with the billing outcome) plus **render status** (`ok` or `degraded`), whether the PDF **rendered as requested**, and an actionable **diagnostics** list when the renderer raised warnings or errors (same semantics as the REST `X-Render-*` response headers).

### `validate_render_pdf`

Dry-run the same TypeBox schema and cross-field semantic checks as `render_pdf`, mapped to `POST /v1/render/validate`. No PDF is generated, no render quota is consumed, and no download link is returned. Use it when iterating on `rules`, `typography`, `furniture`, or other advanced options before calling `render_pdf`.

```text theme={null}
Your agent → validate_render_pdf(markdown, …) → validation result (no PDF)
```

`validate_render_pdf` accepts the same parameters as `render_pdf` (see above).

#### What `validate_render_pdf` returns

* **Valid body** — confirmation that the request would render. `render_pdf` runs this check automatically before every render.
* **Invalid body** — structured validation feedback: allowed keys per path, did-you-mean hints, and relocation suggestions. Fix the reported paths and call again, or proceed to `render_pdf` once validation passes.
* A **`limits`** block so an agent can decide between inlining, `upload_image`, and an external URL: `{ bodyBytes, bodyLimitBytes (Free 128 KiB / Pro 2 MiB / Business 5 MiB), withinLimit, perImageUploadBytes (5 MiB), maxAssetsPerRender (Free 32 / Pro 128 / Business 256) }`.
* **`typographyNotes`** advisories, which can include `google_font_axis_ignored` (an `opsz`/`wdth`/custom Google Fonts axis was accepted but ignored) and `google_font_weight_snapped` (an off-grid weight was snapped to a valid 100-step endpoint).
* **`staticWarnings`**, which can include `furniture_fragment_empty` (a `sectionTitle`/`sectionIndex` at a heading depth that doesn't exist, or a `documentTitle` when no title resolves).

For a full key map across nested paths, agents can also read `blinkpdf://reference/path-index`.

### `upload_image`

Stages an image so it can be referenced from Markdown (or `imageSequence`) as a `blink://asset/<sha256>` handle, instead of inlining a large `data:` URL. The bytes are fetched at render time, so they **do not count** toward your input-size limit.

```text theme={null}
upload_image(bytes, mime)                                → blink://asset/<sha256>   # small image, one call
upload_image(bytes, mime, part, totalParts[, session])   → awaiting … staged        # larger image, ordered parts
```

There is no presigned URL, no `PUT`, and no outbound network — the whole upload rides the tool call itself, so it works sandboxed or offline. The server hashes the bytes for you, so no client-side sha256 precompute is needed.

<Tip>
  If the image is already at an `https://` URL, **don't upload it** — reference the URL directly in Markdown as `![alt](https://…)`. `render_pdf` fetches it server-side, so no upload is needed at all.
</Tip>

A **small image** goes in one call: pass its raw bytes as a base64 string with its `mime`.

**Larger images are sent as ordered parts.** A single tool call can only carry so much base64 before it hits the model's per-message output limit, so split the image and send it part by part. Pass `part` (1-based) and `totalParts` (2–128); each non-final reply is `status: "awaiting"` and spells out the exact next call — the `session` token to echo, the next `part` index, and the max base64 length per part. The final part assembles and content-addresses the whole image, returning the **same** handle a single call would — so chunked and single-call uploads of the same image dedupe against each other. State is held server-side between parts, so nothing is lost if a part is retried.

<ParamField body="bytes" type="string" required>
  The image's raw bytes, base64-encoded. For a chunked upload this is one part's slice.
</ParamField>

<ParamField body="mime" type="string" required>
  One of `image/png`, `image/jpeg`, `image/gif`, `image/tiff`, `image/webp`, `image/avif`, `image/svg+xml`.
</ParamField>

<ParamField body="part" type="integer">
  Chunked upload only: the 1-based index of this part. Omit to send a small image in a single call.
</ParamField>

<ParamField body="totalParts" type="integer">
  Required with `part`: the total number of parts the image is split into (2–128). Send the same value on every part.
</ParamField>

<ParamField body="session" type="string">
  Required from part 2 on: the continuation token returned by the previous call. Absent on part 1 (the server mints it).
</ParamField>

The final result returns a `blink://asset/<sha256>` handle to reference as `![alt](blink://asset/<id>)` or from `imageSequence`. Assets are **content-addressed**: identical bytes dedupe and return a `dup` flag instead of re-storing. The **assembled** image is capped at a flat **5 MiB** on every tier — a single call is limited further by what the model can emit in one message, which is why anything larger is chunked (or referenced by an `https://` URL). A per-credential in-process limit of **30 uploads/min and 4 concurrent** applies — a chunked sequence counts **once**, not per part — and over-limit calls return a `rate_limited` tool error. Assets are scoped **per credential**, so a handle staged with one API key resolves in any `render_pdf` or `POST /v1/render` call made with the same key. To stage several images, call `upload_image` again for each one.

### `list_themes`

Returns the theme catalog as `{ version, default, themes[] }` — each theme's `slug`, `category`, `useWhen` tagline, and `isDefault` flag. Mirrors [`GET /v1/themes`](/configuration/themes#discovering-themes). Read-only, unmetered.

### `list_fonts` and `describe_style_targets`

`list_fonts` returns the hosted font catalog and Google Fonts limits for your key (mirrors `GET /v1/fonts`). `describe_style_targets` returns the machine-readable `rules` vocabulary — selectors and settable properties (mirrors `GET /v1/style-targets`). Both are read-only and unmetered.

## Reference resources

Beyond the tools, the server publishes read-only MCP **resources** so the tool schemas stay small while agents can still discover advanced options:

| Resource URI                          | Contents                                                                                                        |
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `blinkpdf://reference/render-options` | Curated `render_pdf` options with worked examples.                                                              |
| `blinkpdf://reference/styles`         | The full `rules` styling vocabulary — selectors and settable properties.                                        |
| `blinkpdf://reference/furniture`      | Running headers, footers, `decorations`, and `pageOverrides`.                                                   |
| `blinkpdf://reference/images`         | Image sources, sizing, masks, overrides, and `imageSequence`.                                                   |
| `blinkpdf://reference/tables`         | Table borders, stripes, cell overflow, and row splitting.                                                       |
| `blinkpdf://reference/glyphs`         | Font coverage and glyph-fallback guidance.                                                                      |
| `blinkpdf://reference/warnings`       | Diagnostic codes and how to resolve them.                                                                       |
| `blinkpdf://reference/recipes`        | Worked combinations for furniture, headings, and outline.                                                       |
| `blinkpdf://reference/path-index`     | Key map for nested `rules`, `furniture`, and related paths (use with `validate_render_pdf` for per-path hints). |

Most clients expose these under a "Resources" panel; an agent can read them on its own when it needs fine-grained control.

## Download links and retention

Because an agent can't receive raw bytes, the MCP server stores each rendered PDF briefly so it can return a link. This is the one place Blink PDF holds output bytes, so it's worth understanding precisely:

* The render itself still runs through the [zero-retention](/concepts/zero-retention) REST pipeline — your **Markdown input** is never persisted.
* The rendered **PDF output** is stored and served through a **signed download link** (`GET /d/:token`).
* Both the link and the stored object follow the flat **24-hour** retention window applied to every output on every plan, then are purged automatically.
* After the link expires, the agent simply renders again to get a fresh one.

<Note>
  Retries are safe. If a render succeeds but the download link can't be saved, the tool reports a distinct "rendered but not stored — safe to retry" error, and the retry is **not billed again** (the server sends an idempotency key the REST API dedupes on).
</Note>

## MCP vs. the REST API

Both reach the same renderer. Choose based on how you're building:

| Use the MCP server when…                                            | Use the [REST API](/api-reference/overview) when…                                               |
| ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| You work inside an AI agent (Claude Code, Codex).                   | You're writing application code or a backend service.                                           |
| You want renders triggered in natural language.                     | You need full control over the request/response cycle.                                          |
| A short-lived download link is fine.                                | You want the PDF bytes streamed back inline.                                                    |
| You want `validate_render_pdf` dry-runs with per-path schema hints. | You call [`POST /v1/render/validate`](/api-reference/overview#validate-before-render) directly. |
| You need OAuth for a remote connector, or Agent Skills discovery.   | You authenticate only with `x-api-key` on REST.                                                 |

<CardGroup cols={2}>
  <Card title="Connect a Client" icon="plug" href="/mcp/setup">
    Wire up Claude Code, Codex, or any MCP client in three steps.
  </Card>

  <Card title="AI & LLM Workflows" icon="robot" href="/guides/ai-llm-workflows">
    Pipe OpenAI and Claude output straight into Blink PDF over REST.
  </Card>
</CardGroup>
