Every repository accumulates structured data that someone eventually needs as a document: the changes in a release, the contributors of the year, an API inventory, a list of open risks. The data is already JSON; the missing step is a formatted PDF or Word file that a customer, an auditor or a manager will actually open. With GroupDocs.Assembly.Mcp running locally next to Claude Code (claude mcp add with dnx GroupDocs.Assembly.Mcp --yes), the agent fills a Word template kept in the repo from a JSON file in the repo, and writes the PDF, without anyone formatting a table:
Generate the release notes PDF for 4.2.0 from data/release-4.2.0.json with templates/release-notes.docx, and a contribution certificate for every contributor in data/contributors-2026.json.
The step-by-step version with config and troubleshooting is in the documentation: How to generate a PDF or Word report from JSON data in a repository with Claude Code.
Why not have the coding agent write the document?
Coding agents are very good at producing data and fairly bad at producing documents. Ask one for “release notes as a PDF” and it will write a Markdown file, then a script that converts it, then debug the script’s fonts. Next release, it does it again slightly differently. The output looks like whatever the conversion library defaults to, not like the documents your company sends.
A template fixes the look and puts it under version control. templates/release-notes.docx is a normal Word file with the logo, the heading styles and a table, plus a few <<…>> tags. Whoever owns the layout edits it in Word; the agent never touches it. The JSON is produced by your release tooling, or by the agent from git log, and that is the part a coding agent does well.
It also makes the work reviewable. A pull request that changes data/release-4.2.0.json shows exactly which changes will appear in the release notes, line by line, in a diff a reviewer can read in a minute. A pull request that changes a generated PDF shows nothing at all. Keeping data and layout as separate, versioned files, and treating the document as something you render from them, is the same idea as keeping source code and build output apart, and it holds for documents for the same reasons.
What the repository looks like
repo/
data/
release-4.2.0.json {"release":{...}, "changes":[...4 items]}
contributors-2026.json {"contributors":[...3 items]}
templates/
release-notes.docx <<[release.Version]>>, <<foreach [c in
changes]>> table row, counts
certificate.docx <<[contributor.Name]>> ...
out/ GROUPDOCS_MCP_OUTPUT_PATH, in .gitignore
|
| Claude Code -> GroupDocs.Assembly MCP server (local, stdio)
v
release-notes-4.2.0.pdf pnair.pdf tvarga.pdf mrossi.pdf
The calls
First the agent checks what the template expects, so it can tell you if the JSON is missing something:
-> get_template_fields {"template":{"filePath":
"repo/templates/release-notes.docx"}}
<- { "dataSourceNames": [ "release", "changes" ],
"fields": [ "release.Product", "release.Version", "release.Date",
"changes[].Type", "changes[].Title",
"changes[].PullRequest", "changes[].Author" ] }
Then one call renders it:
-> assemble_document {"template":{"filePath":
"repo/templates/release-notes.docx"},
"data":{"filePath":"repo/data/release-4.2.0.json"},
"format":"pdf","outputName":"release-notes-4.2.0.pdf"}
<- Assembled 'release-notes.docx' with 'release-4.2.0.json' (JSON
object) as PDF
Saved to: .../release-notes-4.2.0.pdf
The template’s table row repeats once per change, and its last line counts the changes and the distinct authors with changes.Count() and changes.Select(c => c.Author).Distinct().Count(). Those numbers come from the data at render time, not from the agent.
The certificates are a fan-out: one PDF per element of the contributors array, named by the contributor’s handle:
-> assemble_per_record {"template":{"filePath":
"repo/templates/certificate.docx"},
"data":{"filePath":"repo/data/contributors-2026.json"},
"recordsPath":"contributors","fileNameField":"Handle",
"format":"pdf"}
<- Assembled 3 of 3 document(s) from 'certificate.docx' with
'contributors-2026.json', one per record (each record as
'contributor'), as PDF.
Saved to:
.../pnair.pdf
.../tvarga.pdf
.../mrossi.pdf
recordsPath tells the server where the array is when the JSON file is an object, which is the usual shape of real data files.
Make it a habit, not a prompt
Agents follow project instructions about when to use a tool. A few lines in CLAUDE.md or AGENTS.md turn this into a convention:
Documents for people (release notes, reports, certificates) are generated
with the groupdocs-assembly MCP server from templates/*.docx and data/*.json.
Call get_template_fields first; never edit a template. Generated files land
in out/, which is in .gitignore. Dates in JSON as yyyy-MM-ddTHH:mm:ss.
Keep the outputs out of git. By default the server writes next to its storage folder; set GROUPDOCS_MCP_OUTPUT_PATH in the server registration to the repo’s out/ folder and add that folder to .gitignore. The JSON and the template are the source; the PDF is a build artefact, regenerated in seconds, and committing it only produces diffs nobody can review.
Honest limits
- Evaluation mode: every PDF starts with “Evaluation Only. Created with GroupDocs.Assembly 26.7…”. Fine for trying it; license the server before anything leaves the team.
- Templates are trusted input. Image and document tags load the paths and URLs their expressions return, so treat a template from a pull request like code, and give the server one storage folder.
- Dates: write them as
2026-11-03T00:00:00and format them in the template (<<[release.Date]:"d MMMM yyyy">>). A date-only string was shifted by a day on a UTC+2 machine in our test; the Docker image runs in UTC. - Per-record generation reads JSON only, and
maxDocuments(default 100) stops an unexpectedly large array before anything is written. get_template_fieldsis static: values computed insideSelectorGroupByprojections are not followed.
FAQ
Can Claude Code generate a PDF from JSON in my repository without a cloud service? Yes. The MCP server runs locally as a child process of Claude Code and reads the template and the JSON from the folder you point it at.
Can this run in CI?
The server is an MCP server, so it needs an MCP client: Claude Code in headless mode, or any agent runner. The Docker image (ghcr.io/groupdocs-assembly/assembly-net-mcp) is the convenient form for a pipeline.
Word or PDF?
Either. Leave out format and the output is a DOCX like the template; "format":"pdf" gives a PDF. get_supported_formats lists every target per template.
Go deeper
- Documentation, canonical how-to: How to generate a PDF or Word report from JSON data in a repository with Claude Code
- Documentation hub: GroupDocs.Assembly MCP Server
- Start here: Let the model fill in the data, not write the document
- Related: A hundred personalised PDFs from one prompt: mail merge with an AI agent
- Related: What does this template need? Ask it.
- Related: The chart comes from the template, the numbers come from the data
- On-premise and security model: 3 architectures for AI document processing, and the one that keeps files inside your network
- Questions: GroupDocs Assembly forum
- Source: GroupDocs.Assembly.Mcp on GitHub