Skip to main content
Glama
er-guard
by D365Wale

ER Guard

Static analysis for Dynamics 365 Finance & Operations Electronic Reporting (ER) configurations. Lint, diff and rebase-check ER exports before the bank rejects the payment file or the tax authority rejects the e-invoice.

CLI · MCP server · Microsoft Copilot harness skill · SARIF for GitHub/Azure DevOps code scanning


Why

ER runs every payment file (ISO 20022), e-invoice (PEPPOL, XRechnung, PINT-AE, ZATCA, FatturaPA) and statutory report in D365 F&O. Yet ER changes today are reviewed by clicking through the designer, and the problems show up in production:

  • a customised <Ustrd> binding without an empty-value guard → <Ustrd/> in the file → pain.002 RJCT

  • a remittance text that is not truncated to 140 characters → schema rejection

  • FIRST() on an empty invoice list → the whole payment export fails at runtime

  • a rebase onto Microsoft's new version silently overwrites (or is overwritten by) your customisation

  • a production BIC or endpoint URL hard-coded in a formula

There are many MCP servers for D365 F&O (OData, metadata, X++). None of them understands ER. ER Guard fills that gap.

Related MCP server: gx3-mcp-server

What it does

Command / MCP tool

Purpose

lint / er_lint

10 rules with business impact and a ready-to-paste fix

diff / er_diff

Structural + binding diff. Breaking changes first, including removed safety nets (dropped ISEMPTY guard, dropped LEFT(…,140), removed enabled condition)

rebase / er_rebase_check

Three-way check: old Microsoft base × new base × your derived version → conflicts before you click Rebase

summary / er_summary

What is in this export, which standard it targets

inspect / er_inspect

Tag/attribute census to tune recognition for unusual exports

Knowledge packs (auto-detected):

  • ISO 20022 pain.001 (credit transfer): Max35Text/Max140Text limits, ISODate/ISODateTime, IBAN/BIC patterns, mandatory GrpHdr/PmtInf/CdtTrfTxInf children

  • ISO 20022 pain.008 (direct debit, SEPA SDD): mandate fields (MndtId, DtOfSgntr), SeqTp, ReqdColltnDt

  • UBL 2.1 / EN 16931: PEPPOL BIS 3, XRechnung (UBL), PINT-AE: mandatory invoice structure, BT references

  • ZATCA Fatoora (KSA): UUID, IssueTime, TaxCurrencyCode = SAR, AdditionalDocumentReference (on top of UBL)

Generic rules (ER001, ER003, ER004, ER006, ER007, ER010) apply to every ER configuration: JSON e-invoices, text formats, Excel/Word reports and model mappings.

Rules

Rule

Catches

ER001

Leaf element with no binding and no value → emitted empty

ER002

Value can be blank, standard needs ≥1 char, no ISEMPTY/IF guard or enabled condition (the <Ustrd/> RJCT)

ER003

Dangling binding (target removed/renamed, typical after rebase)

ER004

Expression uses an undeclared data source

ER005

Value not truncated to the standard's max length

ER006

FIRST() instead of FIRSTORNULL()

ER007

Hard-coded IBAN, BIC, URL, GUID, e-mail, registration number

ER008

Mandatory element missing or disabled

ER009

Date/time without explicit ISO format

ER010

Data source declared but never used

Example

$ er-guard rebase base-v9.1.xml base-v9.2.xml derived-dh-v9.1.1.xml

## ER Guard rebase check
Base changed 3 things; your derived version changed 8.
1 conflicts, 2 base changes merge cleanly.

🔴 …/RmtInf/Ustrd: Microsoft changed the expression you overrode:
   base now LEFT(IF(ISEMPTY(@.PaymentNote), @.InvoiceList, @.PaymentNote), 140),
   yours CONCATENATE(@.PaymentNote, " ", FIRST(@.Invoices).Number)
$ er-guard lint derived-dh-v9.1.1.xml
❌ ER008 Mandatory element missing  …/GrpHdr/InitgPty
   ISO 20022 pain.001: <GrpHdr> requires <InitgPty>, which is missing from the format.
   Impact: Rejected by the bank (schema validation, pain.002 status RJCT).
⚠️ ER002 No empty-value guard  …/RmtInf/Ustrd
   If the source is blank the file contains <Ustrd/> (minimum length 1).
   Fix: set the element's 'enabled' binding to NOT(ISEMPTY(@.PaymentNote)) …
⚠️ ER006 FIRST() on possibly empty list  …/RmtInf/Ustrd

Install

# from GitHub (builds on install, Node 20+)
npm i -g github:D365Wale/er-guard
er-guard summary "ISO20022 Credit transfer.version.9.xml"   # check it recognised your export
er-guard lint    "ISO20022 Credit transfer.version.9.xml"

Export a configuration in F&O: Electronic reporting → Configurations → Exchange → Export as XML file.

As an MCP server

Any MCP host (Copilot CLI, VS Code, Claude, …):

{
  "mcpServers": {
    "er-guard": { "command": "er-guard-mcp" }
  }
}

All tools are read-only. They analyse files and never connect to an F&O environment.

On the Microsoft Copilot harness

examples/copilot-sdk/review-er.ts builds a rebase-review agent with @github/copilot-sdk: the harness runs the loop and model routing, ER Guard brings the MCP tools and the er-guard skill, and a permission handler blocks everything except ER Guard tools and file reads.

npx tsx examples/copilot-sdk/review-er.ts old-base.xml new-base.xml derived.xml

In CI (ER configs in git)

.github/workflows/er-guard-example.yml lints every changed ER export on pull requests and uploads SARIF, so findings appear as code-scanning alerts. --fail-on error|warning|none controls the exit code.

How recognition works (and its limits)

ER exports have no published schema, so ER Guard does not depend on exact tag names:

  1. Format components: any ER…Format… element/attribute/item/component with a Name (plus the patterns in src/profile.ts).

  2. Expressions: every ExpressionAsString (including nested <ERModelExpressionItem … SyntaxVersion="1"/> items) is linked to the component whose ID/GUID appears on the nearest enclosing node. Wrappers named Enabled. mark condition bindings.

  3. Model mappings: exports without format components are treated as mappings, so model paths aren't reported as dangling bindings.

  4. Diff identity: components match by GUID first and path second, so a rename is reported as a rename.

If er-guard summary reports 0 elements for one of your exports, run er-guard inspect and pass a profile override:

{ "formatElementTag": "^ER.*(XMLElement|MyComponent)", "bindingTargetAttrs": ["ComponentId"] }
er-guard lint my.xml --profile my-profile.json

Status: v0.1, early. The fixtures are synthetic ER-style exports. They follow the structure seen in real exports (GUID component IDs, nested ERModelExpressionItem) and reproduce real-world defects, but they are not Microsoft files. If er-guard summary shows 0 elements for one of your exports, please open an issue with the er-guard inspect output. It contains tag and attribute names only, no customer data.

Performance: ~6 MB / 30,000 components parse in under 2 s; lint, diff and rebase each take under 1 s.

What ER Guard does not do: run the format, validate against each bank's usage rules (CGI-MP, EPC, local), or check SAF-T/Intrastat/FatturaPA/camt specifics yet. The generic rules still apply to every format.

Roadmap

  • Knowledge packs: camt.053/054 (import formats), FatturaPA, SAF-T, Slovakia/Germany CIUS specifics

  • Model-mapping analysis (unused data sources, expensive ALLITEMS/FILTER patterns)

  • Optional live mode via d365fo-mcp-server: pull configs straight from an environment

  • VS Code extension view

Develop

npm i && npm run build && npm test
node scripts/make-fixtures.mjs   # regenerate fixtures

MIT © Dhruv Khatri

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables D365 F\&O development including object creation, modification, deletion, and analysis through the MCP standard.
      42
      MIT
    • F
      license
      A
      quality
      B
      maintenance
      Provides read-only analysis of Mitsubishi GX Works3 PLC projects via MCP, enabling device tracing, cross-referencing, ladder inspection, linting, and report generation without modifying source projects.
      15
      75 PyPI
      10
      -
    • A
      license
      B
      quality
      C
      maintenance
      A read-only MCP server for Microsoft Intune and Entra ID that enables list, get, search, and reporting operations for tenant visibility, audits, troubleshooting, and health reporting without write actions. It includes authentication helpers, report exports, and metadata discovery tools.
      36
      1
      MIT