Ask an AI agent to “write the service agreement for Contoso” and it will, in its own words, with its own headings, and with a total it worked out in its head. That is the wrong division of labour. With GroupDocs.Assembly.Mcp running locally (dnx GroupDocs.Assembly.Mcp --yes), Claude Desktop, Cursor or Copilot asks your Word template which fields it needs, fills them from your notes, and the engine renders the contract, table and arithmetic included, without the model touching the layout:
Make the service agreement for this order using contract-template.docx and save it as a PDF: Contoso Pharma, Keizersgracht 210 Amsterdam, contact Dr. Lena Brandt; 12 months of managed hosting at 4,800 EUR, 12 months of support at 1,200 EUR, one onboarding workshop at 3,500 EUR; 5 percent discount.
The step-by-step version with config and troubleshooting is in the documentation: How to generate documents from templates with AI agents using MCP.
Why should the model not write the document?
Because a generated document fails in ways that are hard to see. The layout drifts from the one legal approved. A clause gets paraphrased into something that reads well and means something else. A total comes out of next-token prediction instead of multiplication. And every run is a little different, so last month’s review tells you nothing about this month’s contract.
A template inverts that. Legal owns contract-template.docx, with its headings, its signature block and its <<[customer.Name]>> tags. The model’s job shrinks to the part it is good at: reading an email or a set of notes and turning it into structured values. The engine does the rest, the same way every time.
How does the agent know what to fill?
It asks the template. get_template_fields reads the tags without running the engine and returns the names the template uses, every field it reads, and a JSON skeleton to fill. This is what the server returned for our contract template:
-> get_template_fields {"template":{"filePath":"contract-template.docx"}}
<- { "tagCount": 17,
"dataSourceNames": [ "contract", "customer", "items" ],
"dataSkeleton": {
"contract": { "Number": "", "Date": "", "Discount": "" },
"customer": { "Name": "", "Address": "", "Contact": "",
"Email": "" },
"items": [ { "Title": "", "Quantity": 0, "UnitPrice": 0 } ] } }
That skeleton is the contract between the model and the document. The agent fills it from the order text and passes it back as dataJson.
What happens when the agent forgets a field?
It gets told, precisely. In our test the agent’s first attempt left out the customer’s e-mail address. The server refused and named the gap:
-> assemble_document {"template":{...},"dataJson":"{... no Email ...}"}
<- Document assembly failed for 'contract-template.docx': ...
Can not get the value of member 'Email' on type ''. The template
reads 'Email', which the data does not have. Check the spelling
(names are case-sensitive) with get_template_fields and add it to
the data. ... pass allowMissingMembers=true to output nothing for
missing members and empty lists.
A model writing free text would have quietly left the line out. Here the agent can ask you for the address, or decide with you that the field may stay empty.
The whole exchange
user agent Assembly MCP server
| "Make the contract for this order: ..." |
|-------------->| |
| |-- get_template_fields {"template": |
| | "contract-template.docx"} ------------->|
| |<- dataSkeleton: contract, customer, items --|
| | (fills the skeleton from the order text) |
| |-- assemble_document {"dataJson":"{...}", |
| | "format":"pdf"} ----------------------->|
| |<- Saved to: .../contract-template_ |
| | assembled.pdf ----------------------------|
|<- "Contract ready: 3 items, total 75500 EUR, 5% discount." |
layout, table and arithmetic come from the template
With the e-mail added, the second call succeeded:
-> assemble_document {"template":{"filePath":"contract-template.docx"},
"dataJson":"{...}","format":"pdf"}
<- Assembled 'contract-template.docx' with inline JSON (JSON object)
as PDF
Saved to: .../contract-template_assembled.pdf
The document contains a three-row fee table built by a <<foreach>> in the table row, amounts computed by the template (<<[item.Quantity * item.UnitPrice]>>), a total of 75500 EUR from items.Sum(...), and the line “A discount of 5 percent applies.”, which exists only because an <<if>> tag found a discount in the data. The model computed none of it.
Is this better than a hosted template service?
It is a different trade. Hosted template services take your template and data over HTTPS, render in their cloud and charge per document or per credit. Here the template, the data and the output stay in a folder on your machine; the engine is GroupDocs.Assembly running in-process, and nothing listens on a port. What travels to your model provider is the order text you paste and the skeleton the agent fills, not the rendered contract. The architecture is in the family post on on-premise AI document processing.
Honest limits
- Evaluation mode: every generated document starts with the line “Evaluation Only. Created with GroupDocs.Assembly 26.7…”, saved workbooks gain an extra worksheet, and e-mail templates keep only their plain-text body. A license file or metered keys remove all three.
- The template syntax is
<<[name]>>.{{name}}placeholders are left as text;get_template_fieldssays so. - Templates are trusted input: image and document tags load whatever path or URL their expression returns. Mount one folder for the server.
- Dates: write them as
2026-11-12T00:00:00and format them in the template (<<[contract.Date]:"d MMMM yyyy">>). A date-only"2026-11-12"printed as 11 November on a UTC+2 machine in our test. - The agent is the data source. Ask it to list the values it extracted before it renders, and review them.
FAQ
Can an AI agent fill my Word template without uploading it? Yes. The server runs locally over stdio and reads the template from the folder you configure; the output is written next to it.
Which formats can the template be?
Word, Excel, PowerPoint, Markdown, HTML, text and e-mail templates; each can be saved to its own format or to PDF and others. get_supported_formats lists them per template.
What if the template is maintained by someone else?
That is the point. Whoever owns the template changes clauses and layout; the agent keeps filling the same skeleton, and get_template_fields shows it any new field the next time it asks.
Go deeper
- Documentation, canonical how-to: How to generate documents from templates with AI agents using MCP
- From an email or notes to a document: How to turn notes or an email into a finished document with an AI agent
- Documentation hub: GroupDocs.Assembly MCP Server
- 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
- Related: Your repo already has the data; let the agent make the PDF
- What is new in the engine: GroupDocs.Assembly for .NET 26.7
- 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 · NuGet package