apollo-pdf-creator
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@apollo-pdf-creatorTurn this markdown into a PDF with the report theme"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
apollo-pdf-creator
A standalone, stateless Model Context Protocol server that renders PDF documents — reports, invoices, letters, resumes, and book-style documents — from a compact JSON spec, and converts Markdown to PDF.
It is the document engine that used to ship as the pdf-creator
Agent Skill in
apollo-pack, re-homed as an MCP.
The render core (scripts/_lib.py, scripts/_engine.py, scripts/_themes.py,
scripts/templates.py) is reused as-is; reportlab is pulled in lazily only for
the actual render.
Spec in, PDF out — one JSON object (or a Markdown string), one PDF (returned inline and written to disk).
Stateless — no database, no render history, no state written; safe to run as many copies as you like.
stdio or HTTP — MCP over stdio by default, with Streamable HTTP (
/mcp) and SSE (/sse) transports built in.Modern house style — near-black type, generous spacing, one restrained accent, hairline rules, and a family of themes (
default,report,invoice,letter,resume,book,minimal,elegant).Rich blocks — headings, lists, tables, images, code, callouts, columns, TOC, cover pages, QR codes, barcodes, and more.
Discoverable — themes, demo specs, and the spec schema are read-only MCP resources, plus a
make-pdfprompt.
Requirements
Python 3.10+. The MCP layer depends on
mcp; rendering depends onreportlab(andPillowfor extra image formats). Both are installed byuv sync.Non-rendering calls (
validate_spec, pluscreate_pdf's validation step) work without reportlab; a render fails fast with adependency_missingerror if it is absent.
Related MCP server: Gen-PDF MCP Server
Install
git clone https://github.com/kakalition/apollo-pdf-creator
cd apollo-pdf-creator
./install.shinstall.sh is idempotent: it checks for uv, runs uv sync, and prints
run/transport/inspector examples. uv is the only extra tool you need; install
it from https://docs.astral.sh/uv/.
Transports
The server speaks MCP over stdio by default, and can serve the same API over HTTP:
Transport | Flag | Endpoint |
stdio (default) |
| stdin/stdout |
Streamable HTTP |
|
|
HTTP + SSE |
|
|
uv run apollo-pdf-creator # stdio
uv run apollo-pdf-creator --transport streamable-http --port 8000 # HTTP
uv run apollo-pdf-creator --transport sse --port 8000 # SSE--transport, --host (default 127.0.0.1), and --port (default 8000)
also read APOLLO_PDF_TRANSPORT, APOLLO_PDF_HOST, and APOLLO_PDF_PORT; log
verbosity reads APOLLO_PDF_LOG_LEVEL (DEBUG…CRITICAL, default INFO). The
HTTP transports bind to loopback by default; put a reverse proxy (and auth) in
front before exposing them beyond the host.
Use with an MCP client
Point a stdio client at the console script:
{
"mcpServers": {
"apollo-pdf-creator": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/apollo-pdf-creator", "apollo-pdf-creator"]
}
}
}Or run it over HTTP and point a streamable-HTTP client at
http://127.0.0.1:8000/mcp:
uv run apollo-pdf-creator --transport streamable-http --port 8000Explore it interactively with the MCP Inspector:
uv run mcp dev src/apollo_pdf_creator/server.pyTools
Tool | Arguments | Returns |
|
| JSON metadata (path, pages, bytes, sha256, width/height, page size, orientation, theme, warnings, home) and an inline |
|
| JSON mirroring |
|
| JSON metadata and an inline |
Defaults:
out—<output_dir>/<slug>.pdf, named frommeta.title(ordocument).home—$PDF_CREATOR_HOME, else~/.local/share/pdf-creator; read-only forfonts/andassets/, and the default output root.base_dir— the server process cwd, used to resolve relative image/font paths.author(from_markdown) — thePDF_CREATOR_AUTHORsetting.allow_remote— off; the renderer will not fetch remote images unless asked.force—false; an existingoutraises aconflicterror.
Example
{
"spec": {
"spec_version": 1,
"theme": "report",
"meta": { "title": "Quarterly Report", "author": "Acme Analytics" },
"header": { "text": "Quarterly Report", "align": "right", "divider": true },
"footer": { "text": "Page {page} of {pages}", "align": "center" },
"toc": { "title": "Contents", "depth": 2 },
"content": [
{ "type": "heading", "level": 1, "text": "Executive summary" },
{ "type": "paragraph", "text": "Revenue grew **12%** quarter over quarter." },
{ "type": "table", "header": ["Region", "Revenue"], "zebra": true,
"rows": [["North America", "$4.1M"], ["Europe", "$2.6M"]] }
]
}
}create_pdf returns a text block like:
{
"out": "/home/you/.local/share/pdf-creator/output/Quarterly-Report.pdf",
"pages": 2,
"bytes": 28411,
"sha256": "6f1c…",
"width_pt": 595.28,
"height_pt": 841.89,
"page_size": "595.3x841.9",
"orientation": "portrait",
"theme": "report",
"warnings": [],
"home": "/home/you/.local/share/pdf-creator"
}followed by an application/pdf content block with the document.
Resources (read-only)
URI | Content |
| Every theme's name, label, and font family. |
| One theme's merged palette, styles, header, and footer. |
| Names of the bundled example specs. |
| A full demo spec ( |
| A concise reference for every spec key and block type. |
Prompt
Name | Purpose |
| Gives the model the spec contract (top-level keys, block types, units and colors, themes, the |
The document spec
{
"spec_version": 1,
"meta": { "title": "Field Guide", "author": "Acme" },
"page": { "size": "A4", "orientation": "portrait", "margins": "20mm" },
"theme": "report",
"toc": { "title": "Contents", "depth": 2 },
"content": [
{ "type": "heading", "level": 1, "text": "Field Guide" },
{ "type": "paragraph", "text": "A short **guide**." },
{ "type": "list", "items": ["Alpha", "Beta"] },
{ "type": "callout", "kind": "info", "title": "Note", "text": "Read the schema." }
]
}All top-level keys except content are optional. Blocks include heading,
paragraph, rich, list, table, image, figure, code, callout,
blockquote, divider/hr, spacer, page_break, page_template, columns,
toc, checkbox_list, definition_list, key_values, anchor, qr, and
barcode.
Read pdfcreator://schema (or references/schema.md)
for the full reference, references/layout.md for a
worked example of each block, and
references/commands.md for the CLI verbs.
Errors
Tool failures surface as MCP errors whose message starts with the error code:
Code | Meaning |
| The spec failed validation (not an object, unknown block type, bad field type, …). |
| reportlab is absent; the message includes the install hint. |
| The output file exists and |
| reportlab raised while building the document. |
| A referenced file could not be read. |
Configuration
Settings come from the built-in defaults (scripts/_lib.py) and optional
PDF_CREATOR_* environment variables. They apply only to fields the spec leaves
unset.
Env var | Default | Env var | Default |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| (empty) |
|
|
|
|
|
|
The data root holds only generated artifacts: output/ (the default PDF
destination). fonts/ and assets/ are read-only inputs. No database is opened
and no render is recorded.
How it works
spec ──▶ _lib.validate_spec ──▶ _engine.Builder ──▶ reportlab ──▶ PDFThe engine resolves the theme, geometry, page templates, and every content
block into a reportlab story, then builds the document (multi-pass when a TOC is
present). The MCP layer reuses those modules unchanged and returns the PDF both
inline (base64 application/pdf) and on disk. The database-backed document
library, settings CRUD, asset store, render history, scheduler artifacts, and
the schedule-hint verb still exist as CLI code in scripts/, but are not
exposed over MCP.
Development
uv sync
bash tests/smoke.sh # CLI engine end-to-end
uv run python tests/mcp_smoke.py # MCP tools, resources, validation, and render roundtrips
uv run python tests/transport_smoke.py # stdio + streamable HTTP + SSE, end to endtests/smoke.sh generates its own fixtures and exercises every CLI verb against
a throwaway data root (render sections self-skip when reportlab is absent).
tests/mcp_smoke.py drives the FastMCP server in-process and checks the tool
trio, the resources, validation, error surfacing, Markdown conversion, and an
end-to-end render. tests/transport_smoke.py runs the server as a subprocess
and, over each of stdio, streamable HTTP, and SSE, initializes, lists tools,
validates a spec, and renders a PDF.
Layout
apollo-pdf-creator/
├── install.sh # one-time setup (uv sync)
├── pyproject.toml # uv project; runtime deps: mcp, reportlab, Pillow; dev: mcp[cli], pypdf
├── src/apollo_pdf_creator/
│ ├── __init__.py # __version__
│ └── server.py # FastMCP server (tools + resources + prompt + transports)
├── scripts/ # the engine and CLI verbs (reused as-is)
│ ├── _lib.py _engine.py _engine_blocks.py _engine_blocks2.py
│ ├── _markdown.py _rl.py _themes.py templates.py
│ ├── render.py documents.py settings.py assets.py
│ └── reports.py init.py
├── references/ # schema, layout, commands, scheduling
├── tests/
│ ├── smoke.sh # CLI engine end-to-end
│ ├── mcp_smoke.py # MCP-level roundtrips (in-process)
│ └── transport_smoke.py # stdio + HTTP + SSE, end to end
├── .github/workflows/ci.yml
└── LICENSELicense
MIT. See LICENSE.
Available Tools
3 toolscreate_pdfA
Render a document spec to a PDF and return metadata plus the inline file.
Args:
spec: The document spec (schema v1); see the pdfcreator://schema resource.
out: Output PDF path. Defaults to <output_dir>/<slug>.pdf.
home: Data root. Defaults to $PDF_CREATOR_HOME or ~/.local/share/pdf-creator.
theme: Override the spec theme (e.g. default, report, invoice).
base_dir: Base for relative image/font paths. Defaults to the server cwd.
allow_remote: Allow fetching remote images referenced by the spec.
force: Overwrite an existing output file.
| Name | Required | Description | Default |
|---|---|---|---|
| out | No | ||
| home | No | ||
| spec | Yes | ||
| force | No | ||
| theme | No | ||
| base_dir | No | ||
| allow_remote | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It covers key side effects: the 'force' parameter indicates overwrite behavior, 'allow_remote' reveals remote image fetching is opt-in, and it explicitly states it returns metadata plus an inline file. It does not mention error handling or permission requirements, but it provides enough behavioral context for an agent to call it safely.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose, then lists each parameter concisely in a consistent format. It is moderately long due to 7 parameters, but every sentence earns its place—no filler. The structure is easy to scan and the argument explanations are tight.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description thoroughly covers parameters but is vague about the return value ('metadata plus the inline file') without specifying what metadata contains. It also does not mention error cases, idempotency, or whether validation is required before calling. Given the lack of an output schema and the tool's complexity, more detail on return structure and potential failures would be helpful.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description is the only source of parameter meaning. It explains each of the 7 parameters with defaults, purpose, and examples (e.g., 'theme: Override the spec theme (e.g. default, report, invoice)'). It even clarifies the spec format via a resource reference. This fully compensates for the schema's lack of descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a clear, specific statement: 'Render a document spec to a PDF and return metadata plus the inline file.' It names the verb (render), resource (document spec), and output (PDF). It distinguishes from siblings like validate_spec (validation) and from_markdown (likely conversion), leaving no ambiguity about what this tool does.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies when to use this tool (to generate a PDF from a spec) but never explicitly contrasts it with validate_spec or from_markdown, nor says when not to use it. It references a spec schema resource, which hints at prerequisites, but there is no direct guidance on alternative selection. This is a clear gap for a tool with siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
from_markdownA
Convert Markdown text to a PDF and return metadata plus the inline file.
Args:
markdown: Markdown source (headings, paragraphs, lists, tables, code,
blockquotes, rules).
title: Document title; also names the default output file.
author: Document author. Defaults to the configured author setting.
theme: Theme name for the document.
toc: Insert a table of contents.
number_headings: Number section headings.
header: Running header text.
footer: Running footer text (supports {page} and {pages}).
out: Output PDF path. Defaults to <output_dir>/<title-slug>.pdf.
home: Data root. Defaults to $PDF_CREATOR_HOME or ~/.local/share/pdf-creator.
base_dir: Base for relative image/font paths. Defaults to the server cwd.
allow_remote: Allow fetching remote images referenced by the spec.
force: Overwrite an existing output file.
| Name | Required | Description | Default |
|---|---|---|---|
| out | No | ||
| toc | No | ||
| home | No | ||
| force | No | ||
| theme | No | ||
| title | No | ||
| author | No | ||
| footer | No | ||
| header | No | ||
| base_dir | No | ||
| markdown | Yes | ||
| allow_remote | No | ||
| number_headings | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does a solid job. It discloses defaults (author, home, out, base_dir), special behaviors (footer placeholders, force overwrites, allow_remote), and states that output includes metadata plus an inline file. However, it omits side effects like file creation details, error behavior, and any authorization requirements, leaving some behavioral gaps.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is efficiently structured: a one-sentence summary followed by a bulleted argument list. Each parameter gets a single line, making it scannable. It is slightly long due to the number of parameters, but every line earns its place; no fluff or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given 13 parameters and no output schema, the description covers all parameters and mentions the return type, but leaves important context missing. It doesn't specify the structure of 'metadata' or how the 'inline file' is returned, nor does it address error conditions or usage context relative to siblings. An agent might need to probe to understand full invocation behavior.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, and the description compensates exceptionally well. It explains every parameter's meaning, provides defaults, and includes specific syntax details (e.g., footer supports {page} and {pages}, out defaults to <output_dir>/<title-slug>.pdf). This goes far beyond the minimal schema titles and gives the agent actionable guidance.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a clear, specific statement: 'Convert Markdown text to a PDF and return metadata plus the inline file.' It names the action (convert), the resource (Markdown text), and the output (PDF plus metadata), making the tool's purpose unmistakable. Though it doesn't explicitly contrast with sibling tools, the name and description already disambiguate from validate_spec and create_pdf.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool versus alternatives like create_pdf or validate_spec. No prerequisites, exclusions, or decision heuristics are provided. The description focuses entirely on parameter meanings and defaults, leaving the agent to infer appropriate usage context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
validate_specA
Validate a document spec without rendering it.
With strict, also resolve the spec through the engine and report the
flowable count and engine warnings. Strict resolution needs reportlab.
| Name | Required | Description | Default |
|---|---|---|---|
| spec | Yes | ||
| strict | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it states the tool does not render, explains that strict mode resolves through the engine, reports flowable count and engine warnings, and requires reportlab. This is meaningful behavioral context beyond the bare schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded: the core purpose appears in the first line, and the strict-mode behavior is a clean, short addendum. Every sentence earns its place and there is no filler or repetition of schema data.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the output schema exists, return-value details do not need to be repeated. The description covers the tool's main behavior, the strict-mode difference, and the reportlab prerequisite. Minor gaps remain, such as what happens if reportlab is absent, but overall an agent has enough to invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It adds real semantics to the strict parameter, including what strict resolution does and its dependency. However, it gives no additional detail about the structure or expected shape of the spec object, which is undocumented in the schema as well.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb ('Validate'), a specific resource ('document spec'), and an explicit exclusion ('without rendering it'). This clearly separates it from the sibling tools create_pdf and from_markdown, so an agent can distinguish it without inspecting their schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description conveys when to use the tool: to validate a spec rather than render it. It also clarifies the optional strict mode and its dependency on reportlab. However, it does not explicitly name create_pdf or from_markdown as alternatives or state when not to use them, so it misses the top tier of routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v1.0.0- First observed
create_pdf - First observed
from_markdown - First observed
validate_spec
TDQS
Scored across 3 tools
Each tool serves a distinct purpose: validate_spec checks spec correctness, create_pdf renders a spec to PDF, and from_markdown converts Markdown to PDF. There is no overlap in functionality, making tool selection unambiguous.
Two tools follow a clear verb_noun pattern (validate_spec, create_pdf), but from_markdown deviates by using a prepositional prefix. The inconsistency is minor and does not hinder readability.
With only 3 tools, the server is tightly scoped to its core purpose of PDF creation. Each tool fills a necessary role, and the count falls within the ideal range for a focused server.
The server covers validation, spec-based PDF generation, and Markdown conversion, which are the essential workflows for a PDF creator. A minor gap is the lack of a tool for inspecting or managing generated PDFs, but this does not break core functionality.
Maintenance
Related MCP Connectors
JSON in, PDF out. Render invoices, certificates, reports and cards from a template and a payload.
Compliant PDFs (PDF/A-2A + PDF/UA-1) from markdown or a compact DSL - fast, no headless browser.
I create PDF documents from markdown, preview, then generate a downloadable file
Turn HTML or Markdown into a clean, styled PDF and get a download link.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables PDF generation from HTML, text, and Markdown content with customizable formatting options. Provides secure cross-platform PDF creation tools that automatically save to user directories like Downloads, Documents, or Desktop.425 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to generate professional PDF documents from markdown content with advanced typography, syntax highlighting, math equations, dark mode, and customizable styling options.MIT
- FlicenseNot gradedqualityDmaintenanceProvides tools for converting Markdown content and files into professional PDF documents with full support for Mermaid diagrams and LaTeX rendering. It allows for high-quality output customization, including paper size, table of contents, and syntax highlighting styles.-
- FlicenseNot gradedqualityDmaintenanceConverts Markdown files and raw content into professionally styled PDFs with full support for Mermaid diagrams and syntax highlighting. It offers customizable page formats, margins, and modern typography for high-quality document generation.11-