Codync
A free, open-source 1:1 alternative to Grok Bot and Muse. Message Claude Code, Codex, Cursor and 40+ coding agents as persistent bots from your iPhone, Mac or Linux desktop. Group chats, threads, appr
MCP for word layouts for Microsoft Dynamics 365 Business Central
Translation pending
Developer toolsGitHub @TKapitanSelf-installOwner unconfirmed
Paste into Claude or ChatGPT for installation instructions suited to your environment.
Have you tried it? One tap is enough. No sign-in needed.
Repository information has not been collected yet.
No updates detected yet. GitHub releases will appear here.
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗An MCP (Model Context Protocol) server that lets AI agents create, edit, validate, and preview Microsoft Word report layouts for Microsoft Dynamics 365 Business Central through deterministic, typed tools — instead of free-hand OOXML editing, which is unreliable in a file format where a single misplaced element silently corrupts the document.
28 typed MCP tools over stdio: inspect any layout, validate it (structurally, and via a real dry-run merge), render an offline mock PDF preview the agent can look at, and author edits — fields, labels, repeater tables, nested detail rows, block repeaters, table structure, text and cell formatting, full lifecycle refresh. One uniform response envelope; nothing throws across the MCP boundary; every mutating edit is validated before it is saved and rejected outright if it would corrupt the file.
skills/al-word-layout/SKILL.md — the companion skill: the
intended agent workflow (inspect → edit → validate → preview → sandbox verify).skills/al-word-layout-design/SKILL.md — the design
skill: what a new layout should look like — archetype skeletons and observed BC document
conventions, each tagged with the stock corpus layout or BC-verified build it comes from.skills/al-word-layout-from-design/SKILL.md — from a
finished Word design plus the report's empty layout to a bound layout: what to ask a consultant for,
mapping sample text to dataset fields, binding it, checking it.skills/al-word-layout-composite/SKILL.md — composable
layouts: what the body, header/footer and theme files contain, and the style contract between them.docs/SOLUTION-DESIGN.md — architecture and design rationale.docs/adr/ — the deliberate design decisions, as ADRs.The server reads and writes layout files with your own permissions, at paths the calling agent supplies — point it only at repositories and layouts you trust, as you would any local build tool.
dnx (needs the .NET 10 SDK)The .NET 10 SDK ships dnx, which fetches and
runs the packaged server on demand — no install step, version-pinnable.
VS Code (.vscode/mcp.json, or user-level mcp.json):
{
"servers": {
"bc-word-layout": {
"type": "stdio",
"command": "dnx",
"args": ["BcWordLayout.Mcp", "--yes"]
}
}
}
Claude Code (.mcp.json in a project, or user scope via the one-liner):
{
"mcpServers": {
"bc-word-layout": {
"command": "dnx",
"args": ["BcWordLayout.Mcp", "--yes"]
}
}
}
claude mcp add bc-word-layout -- dnx BcWordLayout.Mcp --yes
Unpinned, dnx resolves the latest stable release at each launch, so updates arrive without config
edits. Append an exact version — [email protected] — to pin for reproducibility; a pinned
config stays on that version until you edit it. Pinning is also the only way to run a pre-release,
which latest-stable resolution skips.
Prefer a pinned global install over on-demand fetch? dotnet tool install -g BcWordLayout.Mcp
installs the same server as the bc-word-layout-mcp command; use that as the command in the
blocks above (no args).
Each GitHub Release carries win-x64 and
win-arm64 zips (the whole publish folder — the exe does not run alone) plus an install script that
unpacks to a stable location, verifies the checksum, and prints the ready-to-paste config block:
irm https://github.com/TKapitan/bc-word-layout-mcp/releases/latest/download/install.ps1 | iex
For a specific version, pre-releases included, run that release's own script with -Version:
& ([scriptblock]::Create((irm https://github.com/TKapitan/bc-word-layout-mcp/releases/download/v1.2.3/install.ps1))) -Version 1.2.3
The documented config then points at the fixed path
%LOCALAPPDATA%\bc-word-layout-mcp\current\BcWordLayout.McpHost.exe.
The repo-root .mcp.json launches the server from your working tree with
dotnet run --project src/BcWordLayout.McpHost --artifacts-path .mcp-run, so there's no publish step
while iterating. The separate .mcp-run/ build folder matters on Windows: a running server locks
the DLLs it loaded, and building into the normal bin/ folders would stop dotnet build and
dotnet test while any session is open.
Claude Code starts every .mcp.json server when a session opens, whether or not it's used. So the
committed .claude/settings.json lists this one in disabledMcpjsonServers,
and sessions in the repo don't start it. To use your build as a server, add it under your own local
scope, which that setting doesn't cover:
claude mcp add --scope local bc-word-layout-dev -- dotnet run --project src/BcWordLayout.McpHost --artifacts-path .mcp-run
Remove it again with claude mcp remove --scope local bc-word-layout-dev. Either way, the server
runs the code as built when the session started; restart the session to pick up later edits. See
Building from source.
Four skills ride along with the server: al-word-layout teaches an agent the intended workflow and
the supported matrix, al-word-layout-design teaches what a from-scratch layout should look
like (archetype skeletons and observed BC document conventions), al-word-layout-from-design
turns a finished Word design plus the report's empty layout into a bound layout — so a functional
consultant can get a working layout without touching OOXML or AL — and al-word-layout-composite
covers composable layouts: the report body, the reusable header/footer layout and the theme. The server is fully usable without
them, but agents drive the tools noticeably better with them. The plugin installs the skills and
the server entry together, and this repository is a plugin marketplace for both ecosystems:
/plugin marketplace add TKapitan/bc-word-layout-mcp, then install the
bc-word-layout plugin it offers."chat.plugins.marketplaces": ["TKapitan/bc-word-layout-mcp"] to your user settings.json,
then install the bc-word-layout plugin from the marketplace. The same mechanism serves
Copilot CLI.skills/ folders
(al-word-layout/, al-word-layout-design/, al-word-layout-from-design/, al-word-layout-composite/) into your project's .claude/skills/ or .github/skills/ (or
user-level ~/.claude/skills/). SKILL.md agent skills are an open standard — Claude Code,
VS Code, and Copilot CLI all scan these locations natively; the server is then registered
separately per Option A/B above.Label classification works out of the box for both shapes found in real BC layouts: columns whose
name ends in Lbl/_Lbl (BC's documented convention), plus every direct column of a data item
named Labels — a dedicated labels data item, common in older/converted reports whose columns are
suffixed Caption/Label or not at all. The second rule is self-scoping: it only affects layouts
that actually contain a Labels data item, so no per-report configuration is needed. Two
environment variables override this default convention, read once at server startup; an invalid
value is ignored (logged to stderr) and the default kept:
BCWL_LABEL_SUFFIXES — comma-separated label suffixes, e.g. Lbl,Caption (default: Lbl).BCWL_LABELS_DATA_ITEM — retargets the labels data-item rule to a different data-item name, or
disables it entirely with the special value - (default: Labels).{
"mcpServers": {
"bc-word-layout": {
"command": "dnx",
"args": ["BcWordLayout.Mcp", "--yes"],
"env": {
"BCWL_LABEL_SUFFIXES": "Lbl,Caption",
"BCWL_LABELS_DATA_ITEM": "ReportLabels"
}
}
}
}
Some layout features need a newer Business Central. The target is the minimum BC version a layout must work on, and every call resolves it again (ADR-0007); the first match wins:
app.json above the layout (above outputPath for create_layout): its
application, e.g. 29.0.0.0. The walk stops at the first app.json, at a folder containing .git,
or at the drive root. An app.json that cannot be read, or names something that is not a version,
makes the target unknown; one with no application falls through to the variable.BCWL_BC_VERSION (e.g. 29 or 29.1) in the server's env block, for layouts outside an AL
project. A project-scoped .mcp.json can set it per repository. An invalid value makes the target
unknown (logged to stderr at startup).get_layout_info and validate_layout report it as target: { bcVersion, source, detail }, e.g.
29.0 from app.json (…pp.json: application 29.0.0.0). What it controls:
bc_version_unsupported below
the feature's version and when the target is unknown. No shipped tool writes one yet.validate_layout's bc-version check reports such a feature a layout already has: a header/footer
layout bound to Tenant Report Defaults is an error below 29.0 and a warning for an unknown target.
Theme colours and paragraph styles already in a layout are not flagged: BC 28 prints them.| Feature | Kind | Since | Covers | Evidence |
|---|---|---|---|---|
report-themes | capability | 29.0 | report theme .dotx files, header/footer layouts bound to Tenant_Report_Defaults | 2026 release wave 2; a composite body + header/footer + theme set printed on 2026-10-17 |
themable-formatting | capability | 29.0 | theme-slot colours for text, cell fills and borders, and paragraph styles | decided with report themes, 2026-10-27 (#152) |
Windows (x64 / arm64) is the supported platform. PDF preview conversion uses Microsoft Word via
COM automation when Word is installed. A LibreOffice converter (soffice CLI) exists in the code as
the fallback and is what non-Windows use would rely on — but it is untested and unverified end to
end, so Linux and macOS are currently unsupported, not "supported via LibreOffice" (the
non-Windows code paths compile and their unit tests run in CI as a rot-guard; that is a build claim,
not a support claim).
Neither Word nor LibreOffice is required to use the server. Without a converter,
preview_layout still merges the sample data and returns the merged .docx path — it reports
conversionOk: false with a conversionError explaining why, instead of failing the call (and
removes the PDF an earlier preview left, so no stale render remains next to the new merge). Every
other tool is conversion-free and works everywhere the server runs.
| Tool | Purpose |
|---|---|
get_layout_info | Inspect a layout: report name/id, dataset namespace, storeItemID, the full control inventory (kind, alias, tag, xPath, part, parent repeater), per-part details (kind, header/footer role default/first/even, and which part a partName-less header/footer edit targets) plus a hasTitlePage flag, every table's rows (one per w:tr, so each row of a multi-row repeater item is addressable, tagged with the repeater it repeats with) and cells — each cell with both its physical colIndex (what the cell tools take) and its gridColumn/gridSpan (what the column tools take; they differ on any spanned row), each row with its gridBefore/gridAfter — a quick validation summary, and the target BC version the call resolved. |
list_dataset_fields | List the dataset hierarchy (data items + columns) from a .docx layout or a standalone schema .xml; flags labels and, for a layout, bound/unbound columns. |
validate_layout | Validate a layout: level=quick (structural + binding checks, including a repeater-downgraded error for a repeater a Word save turned into a plain content control, a top-level-repeater-group error for a block-level repeater over the top-level data item without the w:group wrapper Business Central needs to print it, an attached-template error for a layout that links an external Word template (Business Central refuses to upload one), a warning when a layout containing repeaters declares a Word compatibility mode below 15, a duplicate-control-id warning when two content controls share a w:id, a font-installed warning for a font Business Central online does not have, and a bc-version finding for a feature the target BC version lacks) or level=full (adds a dry-run merge that surfaces every unresolved binding as a finding). Reports the target it used. |
Every tool returns a uniform envelope:
{ "ok": true, "data": { "...": "tool-specific payload" } }
Members that would be null are omitted rather than sent as null: a success has no error key, a
failure has no data key, and an optional field inside data is simply absent.
One tool extends (never replaces) this shape: render_preview_pages returns the same JSON envelope as
its result's first text content block, followed by one MCP image content block per rendered page
— the only way a tool result can carry images over MCP. Its envelope JSON is serialized with the same
options the SDK applies to every other tool, so parsers see an identical shape either way.
On failure, error is populated with { code, message, hint } — hint is always
a non-empty, agent-actionable next step (which argument to fix and its valid values, or which
inspection tool to call first). Nothing throws across the MCP boundary. The 22 mutating tools
(insert_field, insert_label, insert_text, insert_page_number, insert_picture, insert_table,
insert_repeater_table, insert_repeater_row, insert_subtotal_row, insert_table_row,
wrap_in_repeater, remove_control, set_cell_text, set_text_format, clear_cell_text, set_column_widths, insert_column,
remove_column, merge_cells, split_cells, set_cell_format, refresh_xml_part) additionally run
OpenXmlValidator before saving — a write that would introduce a new structural error is
rejected (edit_would_corrupt) and the file on disk is left untouched. Every one of them is also
checked by a grid-consistency guard that rejects (edit_would_break_table, file untouched) any edit
leaving a row's cells inconsistent with its w:tblGrid — a corruption class OpenXmlValidator
accepts silently. Every save also removes a link to an external attached template, which Business
Central refuses to upload, and the summary says so. Every successful mutating response also carries a
fresh post-edit quickValidation summary. (create_layout writes a new file instead: it checks the layout it built
and returns quickValidation too, but a failure of that check is a server bug, reported as
internal_error, never edit_would_corrupt.)
Envelope boundary: malformed calls get the envelope too. The SDK looks the tool up and binds the
JSON arguments onto its typed parameters before any tool body runs. The server's call-tool filter wraps
that step, so a call that never reaches a body still returns isError: true with the same
{ok:false,error:{code,message,hint}} shape:
invalid_argument naming the tool; the hint points at tools/list.invalid_argument naming it; the hint lists the tool's required
parameters."abc" or 1.5 for an integer, "true" for a boolean, null for a number or
boolean): invalid_argument naming the parameter, the type it needs, and what the call sent. Inside
an object or array argument it names the member or item by its path (columns[1].width), or the
required member an object lacks.invalid_argument rather than ignored, naming the closest
real parameter (layoutpart → layoutPart; names are case-sensitive) and listing them all. An
ignored misspelling would silently fall back to the parameter's default. The same holds for the
members of an object argument and of the objects in an array (format.size, columns[1].Width)."[1500, 6206]" for [1500, 6206]): read as the value
it holds, since the string is unambiguous. A comma-separated list is not a JSON array and is refused,
with a hint that asks for one.C:Report.docx) is refused with invalid_argument naming the parameter,
instead of resolving against the server's working directory.null for a string: an optional enum-like string (layoutPart, borders, borderStyle,
converter, level) takes its documented default, exactly as if it were omitted; a required one
(locationType, mode) is refused with invalid_argument.The input schemas say what an argument may hold before any call is made: every enumerated argument
lists its values (enum) and every bounded number its range (minimum/maximum), including the
members of format and of the table tools' column and cell objects. The server still reads an
enumerated value case-insensitively.
Only a request too malformed for the SDK to parse as a tool call at all (no tool name, or arguments
that is not a JSON object) fails earlier, as a JSON-RPC error with no tool result; MCP client libraries
do not produce that shape.
preview_layout merges deterministic sample data (or a real dataset via dataOverridesPath —
both the layout's own data-store part shape and the report UI's Send to → XML export are accepted,
and the export's decimalformatter columns are formatted the way BC itself renders them) into
a working copy of the layout and converts it to PDF entirely offline — never through the real
Business Central report engine. That's good enough to catch structural and binding mistakes on every
edit, but captions, fonts, and exact pagination can differ from a genuine BC render. Every
preview_layout response repeats this as a disclaimer string. A dataset printed for several
documents previews only the first, and a multiple-top-level-records warning says so: BC renders the
report body once per top-level record, the mock does not. Generated sample data fills every field and
every repeating item, so the mock cannot show a field that is empty in real data (a purchase order's
VendAddr block previews full and prints empty), and it ignores the BC add-in's hide-if-empty controls,
which do hide content in BC.
Final sign-off on any layout is always a real Business Central sandbox render (AL build → publish to dev sandbox → run the report), never this tool's PDF alone.
What has been validated against a real Business Central sandbox: packs of layouts made with
these tools were uploaded to real BC sandboxes and rendered by the real report engine, in three rounds
on BC 28 (Cronus AU). Rounds 1 and 2 (2026-08, BC 28.0 and 28.3) had five items, then seven, three
of them from-scratch builds, and every item passed. Round 3 (2026-10) printed seventeen items covering
what 2.0 writes, plus seven follow-up probes, and found four things the tools got wrong: a link to an
external template that BC 28 newly refuses on upload, a template conversion that crashed BC's print, a
detail row's formatting, and a work-description repeater shape BC did not print. Each is fixed in
2.0.0 and printed correctly again. Round 3 also measured three behaviours the project had no evidence
for: last-page-only field codes, a storeItemID naming no part, and the add-in's hide-if-empty controls.
docs/FIDELITY-CHECKLIST.md records every round and what it measured.
BC accepts what these tools author, bindings resolve, repeaters expand, and the mock preview's
structure matched the real render on the dimensions checked. That is what it proves — and no more:
one BC version family, one localization, the layouts in the pack. The mock preview remains a mock; the
per-release fidelity procedure and its open manual dimensions live in the same checklist.
This project follows semver. From v1.0.0 on, the public API is: tool names,
tool parameters (names, types, required-ness), the {ok, data, error} envelope shape, and the error
codes. Removing or changing any of those is a breaking change and lands only in a major version;
new tools, new optional parameters, new data fields, and new error codes are additive (minor).
Error message/hint texts are not part of the contract — never parse them.
ModelContextProtocol C#
SDK (the host's project file names it) and upgrades it in minor releases, with any behavioral consequence called out in the
changelog. The envelope boundary above is pinned by a host-level test that runs the real server
pipeline over the SDK's own transport, so an upgrade that changes it fails the suite.set_hide_if_empty tool yet. What BC 28 does with the two captured
ones is measured (#123): inside a repeater, Hide Table Row If Empty hides the whole
repeated item when its cell is empty, and outside a repeater it does nothing; Hide Table If Empty
around a cell does nothing. A tool is planned after 2.0; until then a hand edit in the captured shape
works. Hide Field if Zero and layout comments wait on capturing the BC Word add-in's OOXML
(#8, ADR-0005).IF PAGE = NUMPAGES, e.g. text on the last page only) — no
tool yet. Business Central evaluates the field per page, as Word does (sandbox-verified on one- and
two-page documents), and a tool is planned after 2.0 (#11); until then a
templatePath shell or a hand edit carrying it works.insert_repeater_table only creates one in the document body and wrap_in_repeater only wraps body
content; a pre-existing/hand-authored one is still detected by validate_layout with a warning
rather than silently accepted. (NESTED repeaters in the body are supported: insert_repeater_row
adds the standard detail row under each parent line, or insert the inner table into a cell of the
outer table's dataRowIndex row.)preview_layout fills
labels from the dataset's own label columns, falling back to humanized element names, so captions read
differently from a real BC render. Pass a real exported dataset via dataOverridesPath when caption
text matters: the dataset carries BC's own captions, and they then match the real render.insert_column, remove_column,
set_column_widths, horizontal / — all -aware,
grid-column-addressed, guarded against desyncing a row from its ; rows skipping grid
columns via / are handled). Still deferred and refused with a clear
message, pending real-add-in OOXML capture: any table using () —
except , which is cosmetic and accepts them. See
.Prerequisite: the .NET 10 SDK (and the nuget.org package source configured).
dotnet build # warnings fail the build (TreatWarningsAsErrors + .NET analyzers)
dotnet test
The suite includes Word COM PDF-conversion tests and OOXML snapshot tests against the corpus of 10
real BC layouts (tests/corpus/ — see PROVENANCE.md). The
PDF-converter tests are dependency-agnostic: they pass whether or not Word/LibreOffice is installed
(an unavailable converter is itself a tested, expected outcome, not a skip).
dotnet publish src/BcWordLayout.McpHost -c Release -r win-x64 --self-contained
Produces BcWordLayout.McpHost.exe (~160 KB) in
src/BcWordLayout.McpHost/bin/Release/net10.0/win-x64/publish/ alongside the rest of a
self-contained deployment (240 files, ~109 MB — the .NET 10 runtime plus every dependency, so the
target machine needs no .NET installed; native debug symbols are stripped at publish). Ship the
whole publish/ folder together, not just the .exe — it depends on the sibling .dlls.
scripts/build-release-zips.ps1 builds the exact per-RID zips (+ checksums) a GitHub Release
carries, and python tools/release/verify_server.py -- <command> smoke-tests any install channel
(handshake + tools/list).
dotnet pack src/BcWordLayout.McpHost -c Release
Produces the top-level BcWordLayout.Mcp package (with the embedded .mcp/server.json manifest
NuGet.org renders as install config) plus one framework-dependent package per Windows RID — all of
them must be pushed together on release. PackagingMetadataTests pins the manifests' versions to
Directory.Build.props, so a version bump that misses one file fails the suite.
The server speaks JSON-RPC over stdio (newline-delimited). Logs go to stderr; the protocol uses stdout.
dotnet run --project src/BcWordLayout.McpHost
Then send, one JSON object per line:
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke","version":"1.0"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
Or write the requests to a file of your own and pipe it (Get-Content requests.jsonl | dotnet run --project src/BcWordLayout.McpHost), and try a real call:
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_layout_info","arguments":{"layoutPath":"C:\\path\\to\\bc-word-layout-mcp\\tests\\corpus\\SalesInvoiceForSubscriptionBilling.docx"}}}
| Project | Role |
|---|---|
src/BcWordLayout.Domain | Core: dataset models, SchemaProvider (schema parsing), LayoutReader (control inventory), LayoutValidator (quick validation), LayoutEditor/SdtFactory/Location+LocationResolver (edits), TableStructureEditor/TableShell (table structure and new-table shells), CellTextEditor/TextFormatEditor/RunFormatter (cell text and formatting), LayoutBuilder (create_layout), LayoutRefresher (refresh_xml_part). |
src/BcWordLayout.Merge | SampleDataGenerator (seeded, type-aware fakes), MergeEngine (binding fill, repeater expansion incl. nested/re-anchored XPaths, picture placeholders), FullValidator (the dry-run merge behind validate_layout level=full). |
src/BcWordLayout.Render | IPdfConverter + WordComConverter (primary) / LibreOfficeConverter (fallback) + PdfConverterFactory (auto-selection) + PDF output sanity checks. |
src/BcWordLayout.McpHost | The MCP stdio server (console app): the 28 tool definitions (Tools/ReadTools.cs, Tools/EditTools.cs, Tools/TableTools.cs, Tools/LifecycleTools.cs, sharing Tools/ToolGuards.cs), their structured arguments (Tools/TableArguments.cs, Tools/TextFormatArgument.cs, the enum lists in Tools/AllowedValueAttributes.cs), the {ok,data,error} envelope (ToolContracts.cs), the call filter that keeps malformed calls inside it (ToolCallBoundary.cs), and the schema correction that puts a list parameter's allowed values and range on its items (ToolInputSchemas.cs). |
tests/BcWordLayout.Tests | xUnit tests: the 13 real corpus layouts in tests/corpus/ plus synthetic fixtures — reader/validator/merge/editor/refresher/converter coverage, snapshot tests, MCP host tool tests, packaging-manifest guards, and the fidelity harness. |
CONTRIBUTING.md — dev setup, the design constraints PRs need to fit
(docs/adr/), corpus rules, PR checklist.SECURITY.md — private vulnerability reporting and the threat model.CODE_OF_CONDUCT.md.skills/al-word-layout/SKILL.md — the companion skill: intended
agent workflow, full tool reference, supported matrix, anti-patterns, and error handling.skills/al-word-layout-design/SKILL.md — the design
skill: the two archetype skeletons (trading document, list report), per-element BC conventions
(address blocks, caption placement, line-table alignment, totals, chrome, typography), and the
stock idioms the tools cannot build.skills/al-word-layout-from-design/SKILL.md — the
design-to-layout skill: inputs to request, the sample-text-to-field map and its rules, the binding
recipe for a styled design, mock-render traps, and the consultant handover.skills/al-word-layout-composite/SKILL.md — the
composable-layout skill: body / header-footer / theme rules, the theme–body style contract, splitting
one design into the three parts, and what made a theme fail to apply.docs/SOLUTION-DESIGN.md — architecture, tool surface, design risks.docs/BRANCHING.md.docs/FIDELITY-CHECKLIST.md — per-release mock-preview fidelity
regression procedure, and the record of the BC-sandbox validation.MIT © Tom Kapitan. Third-party components and the corpus layouts' provenance:
THIRD-PARTY-NOTICES.md.
preview_layout |
| Mock preview: merges deterministic (or supplied) sample data into a working copy and converts it to PDF via Word COM or LibreOffice. |
render_preview_pages | Render pages of a preview PDF (the pdfPath from preview_layout) as PNG images returned inline as MCP image content blocks, so the calling agent can visually inspect the preview (default 3 pages, cap 10 per call; page onward via firstPage; each page capped at 5000 px a side and 20 MP, a larger one rendered smaller and flagged downscaled). Pass outputDir to also write each page as a .png a human can open — optionally with inlineImages=false to skip the token cost of the inline copies. |
create_layout | Create a new blank layout .docx from a schema, optionally starting from a branded/styled (unbound) template. A blank build ships deterministic default typography (Calibri 11 pt via a scaffolded styles part) so it renders with the same font in Word and in BC, plus a settings part declaring compatibilityMode 15 so its repeaters survive being opened and saved in Word. |
insert_field | Insert a plain-text FIELD content control bound to a dataset path, in the body, a header, or a footer. |
insert_label | Insert a plain-text LABEL content control bound to a label-suffixed (Lbl/_Lbl) dataset path. |
insert_text | Insert plain STATIC text — a literal run, not a content control: the separator space, colon or " / " that glues two inline controls together (without it, chained controls render as Document NoDOCU-0150). Creates no control, so it has no controlId and cannot be targeted by remove_control. |
insert_page_number | Insert Word PAGE/NUMPAGES field codes — the "Page X / Y" chrome every stock BC document header carries (by default the full X / Y shape; includeTotal=false for the current page alone). Field codes are plain runs, so no controlId. |
insert_repeater_table | Insert a complete repeater table (label/static header row + one bound data row) for a repeating data item, in the body — the flagship editing tool. Draws the BC-native look by default (no grid, one rule under the header row; borders also offers none or a full grid) and can reference a table style the layout defines; optional per-column widths and alignments, pinned with a fixed table layout so Word renders the grid as specified. |
insert_repeater_row | Add a nested DETAIL ROW repeater inside an existing repeater's item — the standard BC shape for per-line detail (assembly components, serial/lot nos): a row under each line, aligned to the parent grid via spans, repeating once per parent row. Its cells take the run formatting and paragraph style of the row above them, column by column; format overrides. |
insert_picture | Insert a PICTURE content control bound to a picture dataset path (e.g. /Header/CompanyPicture) — the logo placeholder a from-scratch layout needs; BC fills it at render time. |
insert_table | Insert a plain (unbound) table, in the body, a header or a footer — the building block for non-repeating layout sections (address columns, label/value header grids, right-anchored totals blocks, the stock header and footer tables). Borderless by default, with the same borders and tableStyle options as insert_repeater_table. Column widths are pinned with a fixed table layout, so Word keeps the grid you asked for. Returns the new table's index for set_cell_text/insert_field to fill. |
wrap_in_repeater | Wrap a run of existing body blocks in a block-level repeater over a data item, so they print once per record: a page per customer in a statement, a paragraph per work-description line. Over a top-level item it adds the w:group Business Central needs; over a nested item, a paragraph holding only a field becomes a block-level field control, the shape Business Central prints. |
remove_control | Remove a content control (field/label/repeater/picture/unbound) by its w:id, searching the body and every header/footer; keepText=true on a block-level repeater unwraps it and keeps its content. |
set_cell_text | Set (replace) the PLAIN TEXT of a table cell, addressed by table/row/column index — e.g. re-label a line-items column header. Rejects cells that hold a bound control. |
set_text_format | Restyle text that is ALREADY in the layout without changing it: a control by id, a table cell, a whole row, or one exact piece of text, with the same format object the insert tools take (bold, italic, underline, size, font, colour, character style) and an optional alignment. |
clear_cell_text | Remove all PLAIN TEXT from a table cell, leaving a valid empty cell (the cell/column is preserved) — e.g. blank an unwanted column-header label. |
insert_subtotal_row | Append a STATIC per-group row at the end of an existing repeater's item — the stock BC shape for group subtotal (and spacer) rows: it renders once per PARENT row, after the nested detail rows, never per detail row. Its cells take the formatting of the row above them, as insert_repeater_row's do. |
insert_table_row | Insert ONE STATIC (non-repeating) row into an existing table — the stock BC shape for the totals block INSIDE the line-items table (spacer cells, a spanned bound caption cell, a right-aligned bound amount cell). Cells bind FULL dataset paths; the row renders exactly once, never per data row. Its cells take the formatting of the row above them (below, at the top of a table), as insert_repeater_row's do. |
set_column_widths | Set a table's column widths (one twip value per grid column); gridSpan-aware — each cell is resized to the sum of the columns it spans. |
insert_column | Add a new column at any grid position (append by default) — a bound field/label cell (as insert_field/insert_label) or a plain-text column. Adds the w:gridCol and one cell per row; a spanned cell in a row with no content there is widened instead. The table keeps its total width: the new column's width comes out of the existing columns. |
remove_column | Remove a whole grid column — addressed by the gridColumn get_layout_info reports for its cells, not their colIndex: drops each row's covering cell (a bound cell too) or decrements its gridSpan, and removes the w:gridCol. The summary says per row which of the two happened. |
merge_cells | Horizontally merge a run of adjacent cells in one row into a single gridSpan cell (keeps the first cell's content). |
split_cells | Horizontally split one spanned cell back into single-column cells. |
set_cell_format | Set the appearance of a whole row's cells, or one cell: the per-cell rules a BC document's look is actually made of (a line above a totals row, an underline on one cell) and/or a fill colour (a shaded header row). |
refresh_xml_part | Update a layout's BC dataset XML part to a new schema in place; remaps/orphans bindings, re-points any binding left naming a foreign report's namespace (which BC refuses to upload) or a storeItemID that names no part, and reports newly-unbound fields. Also the repair for a dataset part that no longer parses (validate_layout names the part and the parser error). |
merge_cellssplit_cellsgridSpanw:tblGridw:gridBeforew:gridAfterw:vMergeset_cell_formatformat object (bold, italic, underline, size, font, colour, character style) on
every tool that writes it, plus set_text_format to restyle text already in the layout; cells have
the alignment arguments and set_cell_format's rules and fills; and a blank create_layout ships
deterministic default typography (Calibri 11 pt via its scaffolded styles part). For the rest,
restyle via create_layout's templatePath or hand-edit the OOXML directly, then run
validate_layout to confirm the edit didn't break structure or bindings. Restyling in Word is
safe for a layout this tool created — it declares compatibilityMode 15, so Word preserves the
repeating sections; a layout that declares a lower mode (an older build, or a templatePath shell
that brought one) loses them on save, which is what validate_layout's compatibility-mode warning
is for.create_layout and
refresh_xml_part take their schema from a BC-produced artifact (a compiler-generated layout, an
exported stock layout, or an exported schema .xml) and transplant its part content byte-for-byte,
so "the layout's XML part == the compiler's XML part" holds by construction. Reimplementing the AL
compiler's dataset-to-schema mapping (element-name derivation, IncludeCaption → _Lbl columns,
namespace construction) would reintroduce exactly the drift class the transplant rule eliminates.
Practical consequence: a brand-new layout starts with one AL build (the compiler creates the
referenced layout .docx, dataset part included) or one export from BC — the skill's §1 documents
the workflow.CHANGELOG.md