Ask GitHub Copilot for a field extension on a standard table in a real Finance and Operations environment. It will produce something that looks completely reasonable. Build it, and you will get a handful of errors — every one of them a field or a method that does not exist.
This is not Copilot being bad at its job. It is Copilot being asked something impossible. The same thing happens with Cursor, with Claude, and with every other assistant, for exactly the same reason.
The model has never seen your codebase
Your F&O installation is private. It has never been public, it is not on GitHub, and no model has been trained on it. Your customisations, your ISV layers, your extensions, the table someone added in 2019 that half the business now depends on — all of it is invisible.
So when you ask for a field on CustTable, the model does the only thing it can. It predicts what a plausible D365 field looks like, based on the public X++ it has seen and the shape of the language.
And it is very good at plausible. That is the actual problem. The output is not wrong in a way you can spot at a glance — it is wrong in a way you find out about at build time, or worse, in code review three days later.
Why better prompting does not fix it
You cannot prompt your way to information the model does not have.
This is worth sitting with, because a lot of effort gets spent in the wrong place. Prompt engineering changes how a model reasons about what it knows. It does not add facts. No instruction, however carefully written, tells the model what your EDTs are called.
What teams try instead
The workarounds are all reasonable, and all run out at roughly the same point.
- Pasting the table definition into the chat. This genuinely works — until you need three tables, a form pattern and an enum, and you are spending more time assembling context than you would have spent writing the code.
- A custom instructions file. Helps with house style and naming conventions. Does nothing about symbols the model cannot see.
- Writing it yourself. Which is what most teams quietly went back to, and there is no shame in it.
The fix is access, not intelligence
The missing piece is not a smarter model. It is giving the model a way to look things up in your actual metadata before it writes anything.
That is what Dynamics MCP does. It indexes your real installation — tables, classes, forms, EDTs, enums, labels, security artefacts, across every model including your own and your ISVs' — and exposes it to the assistant as tools it can call.
The behaviour this produces is more interesting than the generation itself. Ask for a field that does not exist, and instead of inventing one, the assistant tells you it cannot find it.
Same model. Same prompt. The only difference is that it could check.
That refusal is the whole product. Generating good X++ is a nice outcome; refusing to generate confident nonsense is the one that saves you the build.
What that looks like in practice
Reads are scoped rather than wholesale. Getting the signature of one method before writing a Chain of Command extension costs a few dozen tokens rather than pulling an entire table definition into context — CustTable's XML is around 93,000 tokens, and the SalesTable form is over 300,000, which does not fit in most context windows at all.
Generation goes further than text. Objects can be written into the AOT, registered in your project, compiled with xppc, and the real diagnostics handed back. And because the X++ compiler never actually validates report definitions, an SSRS design can be loaded through the reporting engine and rendered to an image in a couple of seconds — so you find out a report is broken before you deploy it, not after.
Try it against your own environment
There is a free tier and nothing to install locally. You create an API key, point your assistant at the endpoint, and ask it about a table only your business has. The first honest "I cannot find that" is usually the moment it clicks.