A caller can ask what a string will occupy before placing it, and then look at the rendered page instead of asserting it came out right — which is the difference between generating a layout and confirming one. The genuine decision is upstream of that: pdf-document owns sequential content and reflows on its own, pdf-layout fixes every item to coordinates on a named page, and choosing wrong means fighting the tool all the way down the document. pdf-resume sidesteps the choice for the one format where the structure is already agreed.
A PDF generation server exposing 5 tools: a flowing document builder, an absolutely positioned layout builder, a JSON Resume renderer, a text measurement call, and a PDF-to-PNG renderer.
- Documents that paginate themselves: pdf-document flows content top to bottom and breaks pages when they fill, with pageBreak forcing a break, divider drawing a horizontal rule and spacer opening vertical space, over title, author, font, color, markdown and pageSetup.
- Placement decided by you instead: pdf-layout positions items absolutely through Yoga flexbox, each item naming the page it belongs to so pages are created as needed, with groups acting as flexbox containers.
- A resume path that skips layout entirely: pdf-resume renders JSON Resume input with sections, styling, layout, font, color and pageSize, breaking pages automatically.
- Dimensions before rendering: text-measure returns exact width and height for a given font, size and string, measuring width as a single-line natural width and folding wrapping into the height once a width constraint is supplied.
- A way to see what was produced: pdf-image renders pages to PNG — one page, a list such as [1, 3, 5], or all of them — with viewportScale setting size, where 0.25 gives a thumbnail around 150px wide.
No credentials are configured. pdf-image takes the location of an existing PDF in pdfPath, so the generated file has to be somewhere the server can open it again.
One command — npx -y @mcp-z/mcp-pdf
