GroupDocs.Assembly for Python via .NET is designed to work with AI agents, LLMs, and code generation tools. The library ships machine-readable documentation in several formats, including an AGENTS.md file inside the pip package itself, so that AI assistants can discover and use the API without manual guidance.
Built into the package
The groupdocs-assembly-net pip package includes an AGENTS.md file at groupdocs/assembly/AGENTS.md. AI coding assistants that scan installed packages (such as Claude Code, Cursor, and GitHub Copilot) can read it to learn the imports, the template-and-data workflow, the data sources, the template syntax, licensing, platform requirements, and troubleshooting tips. Its full text is reproduced below.
After installing the package, you can find it with:
pip show -f groupdocs-assembly-net | grep AGENTS
MCP server
GroupDocs provides an MCP (Model Context Protocol) server that lets LLMs query the documentation on demand instead of loading it all at once. This saves tokens and lets your AI assistant fetch only what it needs for the current task.
To connect your AI tool to the MCP server, add the GroupDocs endpoint to your MCP configuration:
// Claude Code: ~/.claude/settings.json (or project .mcp.json)
// Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json
{"mcpServers":{"groupdocs-docs":{"url":"https://docs.groupdocs.com/mcp"}}}
// .vscode/mcp.json in your project root
{"servers":{"groupdocs-docs":{"url":"https://docs.groupdocs.com/mcp"}}}
// .cursor/mcp.json in your project root
{"mcpServers":{"groupdocs-docs":{"url":"https://docs.groupdocs.com/mcp"}}}
// Any MCP-compatible client
{"mcpServers":{"groupdocs-docs":{"url":"https://docs.groupdocs.com/mcp"}}}
Point your AI assistant to the full documentation file for comprehensive context:
Fetch https://docs.groupdocs.com/assembly/python-net/llms-full.txt and use it
as a reference for GroupDocs.Assembly for Python via .NET API.
Why GroupDocs.Assembly is a good building block for AI pipelines
An agent’s answer is often structured data: a summary, a list of findings, a table of figures. People expect it as a document - a Word report, a spreadsheet, a PDF, an email. GroupDocs.Assembly turns that data into the document without asking the model to lay it out:
Keep the layout in a template: the branding, the tables and the styles are written once, in Word, Excel, PowerPoint or HTML, and never generated by the model.
Pass the model’s output as it is: a dict parsed from the model’s JSON is a data source; its keys are the template’s tags.
Any document, any format: tables, lists, charts and email messages, saved as DOCX, XLSX, PPTX, PDF, HTML or MSG.
Deterministic and local: the same data gives the same document, with no network calls.
A typical last step of an agent looks like this:
importjsonfromdatetimeimportdatetimefromgroupdocs.assemblyimportDataSourceInfo,DocumentAssembler# The structured output of a model, as JSON (here a literal)answer=json.loads("""{
"Title": "Supplier review",
"Customer": "Quayside Bistro",
"Summary": "Deliveries were on time in 9 of 10 weeks; one cold-chain delay in March.",
"Findings": [
{"Topic": "Delivery", "Text": "One cold-chain delay, 5 March", "Priority": "High"},
{"Topic": "Pricing", "Text": "Coffee price stable since January", "Priority": "Low"}
],
"NextSteps": ["Confirm the March credit note", "Review the cheese order volume"]
}""")answer["Date"]=datetime(2015,4,1)# The template keeps the layout; the agent's data fills it, and the report is a PDFDocumentAssembler().assemble_document("agent-findings.docx","agent-findings.pdf",DataSourceInfo(answer,"report"))print("Findings in the report:",len(answer["Findings"]))
Without a license, every report carries an evaluation mark and stops after about 200 paragraphs. Set the GROUPDOCS_LIC_PATH environment variable before an agent runs these steps. See Licensing and evaluation.
Pair with other GroupDocs products
For richer AI document pipelines, combine GroupDocs.Assembly with:
# GroupDocs.Assembly for Python via .NET -- AGENTS.md
> Instructions for AI agents working with this package.
Document automation and report generation -- fill Word, Excel, PowerPoint, OpenDocument, email, HTML, Markdown and text templates (documents with tags such as `<<[customer.Name]>>`) with data from Python dicts and lists, JSON, XML and CSV files, or the tables of other documents, and save the report in the template's format or another one, such as PDF. No MS Office or other software installed.
## Install
```bash
pip install groupdocs-assembly-net
```Each release's wheels are also on https://releases.groupdocs.com/assembly/python-net/ for offline installs (`pip install <wheelfile>`).
**Python**: 3.5 - 3.14 | **Platforms**: Windows x64, Linux x64 (glibc 2.27+), macOS 12+ (Intel and Apple Silicon)
## Resources
| Resource | URL |
|---|---|
| Documentation | https://docs.groupdocs.com/assembly/python-net/ |
| LLM-optimized docs | https://docs.groupdocs.com/assembly/python-net/llms-full.txt |
| API reference | https://reference.groupdocs.com/assembly/python-net/ |
| Code examples | https://github.com/groupdocs-assembly/GroupDocs.Assembly-for-Python-via-.NET |
| Release notes | https://releases.groupdocs.com/assembly/python-net/release-notes/ |
| PyPI | https://pypi.org/project/groupdocs-assembly-net/ |
| Free support forum | https://forum.groupdocs.com/c/assembly/ |
| Temporary license | https://purchase.groupdocs.com/temporary-license |
## MCP Server
If your environment has MCP configured, you can connect your AI tool to the GroupDocs documentation server for on-demand API lookups:
```json
{"mcpServers":{"groupdocs-docs":{"url":"https://docs.groupdocs.com/mcp"}}}```Works with Claude Code (`~/.claude/settings.json`), Cursor (`.cursor/mcp.json`), VS Code Copilot (`.vscode/mcp.json`), and any MCP-compatible client. If MCP is unavailable, fall back to the LLM-optimized docs URL above and this file -- both are shipped inside the wheel.
## Imports
```python
fromgroupdocs.assemblyimport(DocumentAssembler,DataSourceInfo,DocumentAssemblyOptions,LoadSaveOptions,FileFormat,OoxmlCompliance,License,Metered,LicenseError,GroupDocsAssemblyException,)fromgroupdocs.assembly.dataimport(JsonDataSource,JsonDataLoadOptions,JsonSimpleValueParseMode,XmlDataSource,XmlDataLoadOptions,CsvDataSource,CsvDataLoadOptions,DocumentTable,DocumentTableOptions,DocumentTableSet,)```## The core workflow
A template is a document whose tags name data sources. Pass each source in a `DataSourceInfo` with the name the template uses; `assemble_document` fills the template and writes the report in the format of its extension:
```python
fromgroupdocs.assemblyimportDataSourceInfo,DocumentAssembler# hello-world.docx holds: Hello, <<[customer.Name]>>! Your order of <<[customer.Product]>> is on its way.customer={"Name":"Quayside Bistro","Product":"Harbor Blend whole bean coffee"}assembler=DocumentAssembler()assembler.assemble_document("hello-world.docx","hello-world-report.docx",DataSourceInfo(customer,"customer"))assembler.assemble_document("hello-world.docx","hello-world-report.pdf",DataSourceInfo(customer,"customer"))# same template, PDF```-`assemble_document(template, report, sources)` -- `sources` is one `DataSourceInfo` or a list of them. Returns `True` (`False` only with `INLINE_ERROR_MESSAGES` and a template error).
-`assemble_document(template, report, LoadSaveOptions(...), sources)` -- the options go before the sources.
- Paths or binary streams: `open(path, "rb")` / `open(path, "wb")` / `io.BytesIO`, both sides the same kind (no path + stream mix).
## Data sources
| Data | Pass | The template sees |
|---|---|---|
| dict, list of dicts, nested | `DataSourceInfo(data, "name")` | typed objects: `customer.Name`, `o.Price`, `o.OrderDate.Month` |
| str, int, float, `Decimal`, bool, `datetime`, `date`, list of str | `DataSourceInfo(value, "name")` | the value |
| JSON file | `json.load` -> dict/list (dates: convert the strings with `datetime.strptime`), or `JsonDataSource("file.json")` | dict: Python's types; `JsonDataSource`: nullable values -- write `o.OrderDate.Value.Month` |
| XML file | `XmlDataSource("file.xml")` | nullable values, like `JsonDataSource` |
| CSV file | `CsvDataSource("file.csv", CsvDataLoadOptions(True))` (first line = column names) | rows |
| a table of a Word/Excel/PowerPoint file | `DocumentTable(path, index, DocumentTableOptions())` | rows; `table.columns["Price"].type = float` to make a column numeric |
| several related tables of one file | `DocumentTableSet(path)` + `tables.relations.add(parent_col, child_col)` | tables by name: `tables.tables["MANAGER"]` |
| database rows | turn them into dicts (`sqlite3.Row` -> `dict(row)`) | as dicts |
```python
importjsonfromdatetimeimportdatetimefromgroupdocs.assemblyimportDataSourceInfo,DocumentAssemblerdefparse_dates(item):# JSON has no dates: convert the date strings the template reads as datesforkeyin("OrderDate","ShippingDate"):ifkeyinitem:item[key]=datetime.strptime(item[key],"%Y-%m-%dT%H:%M:%S")returnitemwithopen("customers.json",encoding="utf-8")asf:customers=json.load(f,object_hook=parse_dates)DocumentAssembler().assemble_document("orders-by-customer.docx","orders-by-customer.pdf",DataSourceInfo(customers,"customers"))```## Template syntax (language-neutral; the same as in the .NET product)
| Tag | Does |
|---|---|
| `<<[customer.Name]>>` | writes a value; members by dot, dict keys as members |
| `<<[o.Price]:"0.00">>`, `<<[o.OrderDate]:"d MMMM yyyy">>`, `<<[name]:upper>>`, `:caps`, `:firstCap` | formats |
| `<<foreach [o in orders]>>...<</foreach>>` | repeats content; in a table, one row from the cell that opens it to the cell that closes it |
| `<<if [o.Price > 400]>>...<<else>>...<</if>>` | conditional content |
| `<<[orders.Sum(o => o.Price)]>>`, `Count()`, `Where(...)`, `OrderBy(...)`, `GroupBy(...)` | sequence operations |
| `<<var [total = 0]>>` | variables |
| `<<image [customer.Photo]>>` in a text box, `<<barcode [code] -qr>>` | images (path, bytes, Base64, URL), barcodes |
| `<<link [uri] [text]>>`, `<<bookmark [name]>>`, `<<doc [path]>>` | hyperlinks, bookmarks, inserted documents (`-build` fills their tags) |
| `<<x [...]>>`, `<<y [...]>>`, `<<size [...]>>` in a chart title or series | chart data |
## Operations
### A table report from a list of dicts
```python
fromgroupdocs.assemblyimportDataSourceInfo,DocumentAssemblerorders=[{"Number":10001,"Product":"Harbor Blend whole bean coffee","Quantity":30,"Price":552.0},{"Number":10004,"Product":"Aged harbour cheddar","Quantity":8,"Price":132.0},]DocumentAssembler().assemble_document("order-table.docx","order-table-report.docx",[DataSourceInfo("Quayside Bistro","customer"),DataSourceInfo(orders,"orders"),])```### A spreadsheet with typed cells
```python
importjsonfromgroupdocs.assemblyimportDataSourceInfo,DocumentAssembler,DocumentAssemblyOptionswithopen("products.json",encoding="utf-8")asf:products=json.load(f)assembler=DocumentAssembler()assembler.options=DocumentAssemblyOptions.USE_SPREADSHEET_DATA_TYPES# numbers as number cells, not textassembler.assemble_document("stock-sheet.xlsx","stock-sheet-report.xlsx",DataSourceInfo(products,"products"))```### An email message
```python
importjsonfromgroupdocs.assemblyimportDataSourceInfo,DocumentAssemblerwithopen("customers.json",encoding="utf-8")asf:customers=json.load(f)DocumentAssembler().assemble_document("in-table-list.msg","in-table-list-report.msg",[DataSourceInfo(customers,"customers"),DataSourceInfo("Harbor Lane Provisions <orders@harborlane.example>","sender"),DataSourceInfo(["Quayside Bistro <orders@quaysidebistro.example>"],"recipients"),DataSourceInfo("amara.osei@harborlane.example","cc"),DataSourceInfo("Orders by customer","subject"),])```### A document table as the data
```python
fromgroupdocs.assemblyimportDataSourceInfo,DocumentAssemblerfromgroupdocs.assembly.dataimportDocumentTable,DocumentTableOptionsoptions=DocumentTableOptions()options.first_row_contains_column_names=Truecontracts=DocumentTable("contracts.xlsx",0,options)DocumentAssembler().assemble_document("table-row.docx","table-row-report.docx",DataSourceInfo(contracts,"contracts"))```### Streams
```python
importiofromgroupdocs.assemblyimportDataSourceInfo,DocumentAssemblercustomer={"Name":"Quayside Bistro","Product":"Harbor Blend whole bean coffee"}withopen("hello-world.docx","rb")astemplate:report=io.BytesIO()DocumentAssembler().assemble_document(template,report,DataSourceInfo(customer,"customer"))print("Report bytes:",len(report.getvalue()))```### Options and target formats
```python
fromgroupdocs.assemblyimportDataSourceInfo,DocumentAssembler,DocumentAssemblyOptions,FileFormat,LoadSaveOptionscustomer={"Name":"Quayside Bistro","Product":"Harbor Blend whole bean coffee"}assembler=DocumentAssembler()# Flags combine with |: remove the paragraphs left empty, write template errors into the report instead of raisingassembler.options=DocumentAssemblyOptions.REMOVE_EMPTY_PARAGRAPHS|DocumentAssemblyOptions.INLINE_ERROR_MESSAGESok=assembler.assemble_document("hello-world.docx","hello-world-report.pdf",LoadSaveOptions(FileFormat.PDF),DataSourceInfo(customer,"customer"))print("Template parsed without errors:",ok)```Other `DocumentAssemblyOptions`: `UPDATE_FIELDS_AND_FORMULAS`, `ALLOW_MISSING_MEMBERS`, `USE_SPREADSHEET_DATA_TYPES`. `assembler.known_types.add("System.Math")` lets a template call a .NET type's static members (`<<[Math.Round(x, 1)]>>`).
## Licensing
```python
fromgroupdocs.assemblyimportLicenseLicense().set_license("path/to/license.lic")# from a filewithopen("path/to/license.lic","rb")asf:# or from a streamLicense().set_license(f)```Or auto-apply: `export GROUPDOCS_LIC_PATH="path/to/license.lic"` (a file or a URL). It is applied at the first call into the engine, and a licence that cannot be applied raises `LicenseError` from then on instead of falling back to evaluation mode (`GROUPDOCS_LIC_STRICT=0` makes it a warning). `License().is_licensed` tells whether a license is applied. `Metered().set_metered_key(public_key, private_key)` activates metered billing; `Metered.get_consumption_quantity()` and `Metered.get_consumption_credit()` report usage.
**Evaluation mode** (no licence): word-processing, spreadsheet, presentation and email reports carry an "Evaluation Only" mark (an XLSX also gets an "Evaluation Warning" sheet), and a word-processing report stops after about 200 paragraphs while `assemble_document` still returns `True`. A 30-day full license is free: https://purchase.groupdocs.com/temporary-license
**Windows:** applying a licence makes the first call take 15-30 seconds, once per process (the engine's licence check; the same in plain .NET). Reuse the process.
## API Reference
### DocumentAssembler
| Member | Description |
|---|---|
| `DocumentAssembler()` | Creates an assembler; one instance builds any number of reports. |
| `assemble_document(template, report, [load_save_options,] sources)` | Fills the template, writes the report. Paths or streams. `sources`: one `DataSourceInfo` or a list. |
| `options` | `DocumentAssemblyOptions` flags. |
| `known_types` | Types a template may call static members of: `.add("System.Math")` or `.add(float)`. |
| `barcode_settings` | `base_x_dimension`, `base_y_dimension`, `resolution`, `use_auto_correction` of barcode tags. |
### Data and options classes
| Type | Notes |
|---|---|
| `DataSourceInfo(data_source, name=None)` | A data source and the name the template uses (no name: the template reads its members directly). |
| `LoadSaveOptions(save_format)` | `save_format` (`FileFormat.PDF`, `FileFormat.DOCX`...), `resource_load_base_uri`, `resource_save_folder`, `ooxml_compliance`. |
| `JsonDataSource(path_or_stream[, JsonDataLoadOptions])` | `simple_value_parse_mode` (`JsonSimpleValueParseMode.STRICT` keeps "007" a string), `exact_date_time_parse_format`, `always_generate_root_object`. A `str` is a path: pass JSON text as `io.BytesIO`. |
| `XmlDataSource(path_or_stream[, schema][, XmlDataLoadOptions])` | XML data; `always_generate_root_object`. |
| `CsvDataSource(path_or_stream, CsvDataLoadOptions(has_headers))` | `delimiter`, `quote_char`, `comment_char`. |
| `DocumentTable(path, index, DocumentTableOptions)` | `first_row_contains_column_names`, `min/max_row_index`, `min/max_column_index`; `columns["Name"].type`. |
| `DocumentTableSet(path[, load_handler])` | All tables of a document; `tables["Name"]`, `relations.add(...)`. `load_handler`: a function `(args)` or an object with `handle(args)` setting `args.options` per table. |
## Key Patterns
- **Properties**: use `snake_case` -- auto-mapped to .NET `PascalCase`. A misspelled property raises `AttributeError` with a "Did you mean" hint instead of being stored
- **Dict keys are template members**: keep the key names the template reads (`{"CustomerName": ...}` for `<<[c.CustomerName]>>`); Python's types decide: a `str` stays a string ("007" stays "007"), a date must be a `datetime`/`date`
- **An empty list** with no non-empty list at the same key has no item type: a template reading its items fails unless `ALLOW_MISSING_MEMBERS` is set
- **One `DataSourceInfo` or a list**; `LoadSaveOptions` before the sources
- **Report format = report extension**, unless `LoadSaveOptions(FileFormat....)` says otherwise; a Word template cannot become XLSX or PPTX
- **Text and HTML templates**: save them with a UTF-8 byte order mark, or the report is written in Windows-1252
- **Enums**: UPPER_CASE members (`DocumentAssemblyOptions.REMOVE_EMPTY_PARAGRAPHS`, `FileFormat.PDF`); flags combine with `|`- **Exceptions**: a template error or a missing member raises `RuntimeError` (message: the engine's, naming the tag); a missing file `FileNotFoundError`; other engine errors `GroupDocsAssemblyException`; `LicenseError` is a licence that could not be applied. `str(e)` adds the .NET type and stack trace
- **Classes you get, not create**: `BarcodeSettings`, `KnownTypeSet`, `DocumentTableColumn`, `DocumentTableLoadArgs` and the collections come from the assembler or the table; calling their constructor raises `TypeError`## Platform Requirements
| Platform | Requirements |
|---|---|
| Windows | None |
| Linux | glibc 2.27+, `apt install libicu-dev libgdiplus` (ICU is required; libgdiplus for presentation templates) and the fonts your templates use |
| macOS | 12 or newer, `brew install mono-libgdiplus` (for presentation templates) |
## Troubleshooting
**"Evaluation Only" in the report, or a report that stops early** -- evaluation mode. Apply a license with `License().set_license(...)` or set `GROUPDOCS_LIC_PATH`; a free 30-day license is at https://purchase.groupdocs.com/temporary-license
**`An error has been encountered at the end of expression ... Can not get the value of member 'X' on type ...`** -- the data has no `X` where the tag reads it: check the key names and the data source name; for an empty list see Key Patterns
**`Can not get the value of member 'Month' on type 'System.Nullable...'`** -- a `JsonDataSource`/`XmlDataSource`/`CsvDataSource` value: write `.Value.Month`, or pass `json.load`ed dicts with `datetime` values
**`The document cannot be saved to the given format`** -- that template family cannot be saved to that format (a Word template to XLSX, for example)
**`The type initializer for 'Gdip' threw an exception`** -- a presentation template on Linux/macOS: install libgdiplus (`sudo apt install libgdiplus` / `brew install mono-libgdiplus`)
**`Value cannot be null. (Parameter 'trueTypeFont')`** -- a QR code on Linux: install the template's default font (a Word document's default, often Times New Roman; a workbook's Normal style, often Calibri) under that name; DejaVu, Liberation and Carlito do not stand in. Code 128, EAN-13 and Codabar need no particular font
**The first call takes 15-30 seconds** -- Windows, with a licence applied: the engine's licence check, once per process
**`DOTNET_SYSTEM_GLOBALIZATION_INVARIANT` errors, or the process aborts with `Couldn't find a valid ICU package`** -- do NOT set this variable. Install ICU: `sudo apt install libicu-dev`**`is not a supported wheel on this platform`** -- pip is older than 20.3 (`python -m pip install --upgrade pip`), or the OS is older than glibc 2.27 / macOS 12
**Still stuck?** Post your question at https://forum.groupdocs.com/c/assembly/ -- the development team responds directly.
Was this page helpful?
Any additional feedback you'd like to share with us?
Please tell us how we can improve this page.
Thank you for your feedback!
We value your opinion. Your feedback will help us improve our documentation.
On this page
Analyzing your prompt, please hold on...
An error occurred while retrieving the results. Please refresh the page and try again.