Why AI is bad at X++ — and what fixes it

Why AI is bad at X++ — and what fixes it

MCP · Azure DevOpsYour delivery board, readable by an agentMCP · DataverseOne consent, and the data layer opensMCP · Dynamics 365 F&OConnecting F&O to an agent Microsoft hasn’t documented

Provenance first. Unlike the other servers in this series, this one is not a Microsoft product. It is a community project — d365fo-mcp-server by dynamics365ninja — with no Microsoft support and no Microsoft security review. It is genuinely useful, and it should never be presented as a Microsoft offering.

The failure mode

Ask a general-purpose AI assistant to write X++ and watch what happens. It will produce something that looks entirely plausible: correct-ish syntax, sensible naming, a confident explanation of what it does.

Then it will not compile. Because it invented the method signature. Or referenced a field that does not exist on that table. Or used an API deprecated three platform versions ago. Or hardcoded a label string instead of using the label that is already defined. Or broke Chain-of-Command in a way that only shows up at runtime.

This is not the model being careless. It is the model doing exactly what it was trained to do — pattern-match to the most probable continuation — in a domain where it has no evidence.

Why X++ specifically

Three properties combine badly:

  • The corpus is private. There is no public GitHub full of production X++. What exists sits inside customer environments behind licensing.
  • Every installation is different. Standard Microsoft models, plus ISV models, plus your own extensions. Around 580,000 symbols in a typical install, and a large fraction of them are unique to that install.
  • The APIs are historically deep. Decades of platform evolution means many plausible-looking method names existed once and no longer do.
The model is not hallucinating because it is a bad model. It is hallucinating because your codebase is invisible to it.

That framing matters, because it points straight at the solution. You do not fix this with a better model or a longer prompt. You fix it by making the codebase visible.

What the server does

It pre-indexes the entire local D365FO installation and exposes it as tools. On our reference environment:

1.16M
symbols indexed — 1,162,110 classes, tables, forms, methods across 168 models
364K
labels indexed — 364,446, searchable across models
23
tools exposed to the agent

Four layers, and only the last one knows anything about your code:

LayerWhat it gives youWhat it does not do
Visual Studio + dev toolsThe X++ IDE — compile, designers, deployHas no AI
AI assistantWrites code, reasons in chatHas never seen your codebase
MCPThe protocol — the line the AI calls overIs an empty pipe on its own
This serverThe expert that knows every class, table, field and label in your install

A concrete example

"Add a field to the CustTable extension and update the sales invoice posting."

Without grounding

Guesses the posting method and its signature. May use a deprecated API. Invents an EDT. Hardcodes the label. Result: compile errors and rework — after you have already read and half-trusted the code.

With grounding

Searches your metadata. Finds the real Chain-of-Command wrapper, an existing EDT and an existing label. Validates every reference. Writes valid X++ into your model. Result: first-time compile.

Writes go through Microsoft's own IMetadataProvider rather than string-manipulating XML files, which means valid metadata and automatic .rnrproj registration. Identifiers are checked against the index before anything reaches disk — made-up references are rejected rather than written and discovered later.

Setup, honestly

This is the most involved server in the series. Budget 30–40 minutes on a clean machine. Our index build alone ran to about 21 minutes — roughly 12 extracting 178,599 XML files, then 9 building the database.

  1. Node.js, .NET SDK, .NET Framework 4.8 Developer Pack, Git, and a D365FO installation
  2. Clone the repository and npm install
  3. Add nuget.org as a package source — a common blocker, since many F&O VMs ship with only the offline Visual Studio source
  4. Build the C# metadata bridge against the local F&O assemblies
  5. Configure paths, custom models and object prefix
  6. Build the index — memory-hungry, runs at a 6 GB heap
  7. Register the stdio server with your MCP client

The blocker nobody warns you about: a non-standard packages path. The bridge auto-detects the F&O assemblies at K:\AosService\PackagesLocalDirectory or the C:\ equivalent. On a Unified Developer Experience machine they live somewhere else entirely — under %LOCALAPPDATA%\Microsoft\Dynamics365\. Auto-detection misses them silently and the build fails with sixty-one compile errors, all variations of "the type or namespace Microsoft.Dynamics does not exist".

That error reads like a broken repository. It is a path problem. Passing the location explicitly fixes it in one line:

Code
dotnet build -c Release -p:D365BinPath="<your PackagesLocalDirectory>\bin"

Worth a second warning on the same theme: the machine's own X++ configuration file can be stale — ours pointed at a CustomXppMetadata folder that did not exist, and referenced a different user profile. If a tool offers to auto-detect your environment, verify what it detected before you trust the result. Confirm the packages path resolves to a folder that actually contains your models.

It is machine-bound. Unlike the other three servers, this one must run where the D365FO installation lives — it reads PackagesLocalDirectory directly. For a team, index once to shared storage rather than making every developer wait twenty minutes.

Keep it in sync: the index is a point-in-time snapshot and goes stale as the codebase changes. After a platform upgrade, rebuild both the index and the bridge — a stale bridge fails at runtime with MissingMethodException.

Reading works before writing does. Search, metadata lookup and validation light up as soon as the index is built. Writing needs somewhere to write to — a custom or ISV model, and a naming prefix. A stock installation with only Microsoft models has neither, so the write tools have no valid target until you point them at one. Fine for a grounding demonstration; worth knowing before you promise a colleague it will generate code on their machine this afternoon.

The wider principle

Strip away the X++ specifics and this is the same idea as the ERP MCP server refusing to let an agent guess an entity name. Both say: do not let the model rely on its priors when the ground truth is available.

One applies it to business data, the other to source code. As a pattern for enterprise AI it generalises well beyond Dynamics — anywhere your domain is private, the answer is not a better prompt, it is a grounding layer.

Sources

  • Community project: https://github.com/dynamics365ninja/d365fo-mcp-server — not a Microsoft product
Previous article← Microsoft AI Tour Sydney — What Satya Nadella Actually Showed
Discussion

Ask a question or leave feedback

This is a review interaction. The WordPress version will store, thread and moderate comments.

Thanks — submitted for moderation in this review.
Search the whole site · Ctrl/⌘ + K