From delivery boards to ERP execution.
I started by orchestrating Azure DevOps, Dynamics 365 Finance & Operations and Dataverse through an AI ERP Transformation Agent in Copilot Studio. Then I wanted to prove that access to one agent platform should not become the bottleneck to learning MCP — so I connected the same enterprise capability layer to Claude Code. This is what worked, what failed, and why MCP changes the integration conversation.
Community
01 Why this experiment Context From integration projects to a reusable capability layer The starting problem was not AI. It was the integration model: every new consumer meant another contract, credential, build and maintenance obligation. MCP changes the question from “what integration do I build?” to “what governed capability can the agent discover?”
The job is changing
In the presentation I delivered on 13 August, I framed the ERP leadership progression as Administer → Integrate → Orchestrate. Administration and integration do not disappear. What changes is that an ERP Transformation Director increasingly has to decide how intelligent agents are allowed to participate in the enterprise — what they can discover, what they can read, what they can change and whose identity they carry.
Administer
Keep the system running. Upgrades, patches, tickets and support.
Integrate
Connect the system. Point-to-point builds, middleware and bespoke pipelines.
Orchestrate
Design how agents act. Governed autonomy, delegated identity and human approval.
The skill that mattered in 2015 was integration architecture. In 2026, the new skill is agent governance.
Every new consumer used to become another project
Traditional integration scales as a grid. F&O, Dataverse and Azure DevOps sit on one side. Portals, analytics, agents and partners sit on the other. Every line between them is another interface to scope, secure, test, deploy and maintain.
Time
Weeks or months before a new consumer sees useful data.
Cost
Build once, then keep paying for maintenance and lifecycle management.
Risk
Every integration adds another credential, surface and operational dependency.
MCP does not replace the APIs
| Traditional API integration | MCP capability layer |
|---|---|
| Exposes endpoints. | Exposes discoverable tools. |
| The caller needs the contract in advance. | The agent can discover available capability at runtime. |
| A developer builds the integration per consumer. | One MCP server can serve multiple compliant agent clients. |
| Adding a consumer usually means more code. | Adding a consumer increasingly becomes an access and governance decision. |
MCP does not replace your APIs. It gives an agent a standard way to discover and use the capability behind them.
The mental model I use
02 Copilot Studio Hands-on Building the AI ERP Transformation Agent Once I had Copilot Studio access, I connected Azure DevOps MCP, Dynamics 365 ERP MCP and Dataverse MCP to one agent. The useful part was not the chat UI — it was watching one agent choose the relevant enterprise system, correlate the evidence and return an executive answer.
The first orchestration surface
My first complete agent experience used Copilot Studio. I created an AI ERP Transformation Agent and attached three MCP tools: Azure DevOps MCP, Dynamics 365 ERP MCP and Microsoft Dataverse MCP. The screenshot below is the configuration I used.
What I asked the agent to do
I did not want a generic chatbot with three tools attached. The instructions explicitly tell the agent which system owns which type of information, to prefer MCP evidence over general knowledge, to correlate across systems, and to stop for confirmation before any transactional update.
You are an ERP Transformation Director Agent.
Your purpose is to provide trusted executive, operational and delivery insights across:
1. Microsoft Dataverse
2. Dynamics 365 Finance and Operations
3. Azure DevOps
Use Microsoft Dataverse MCP for:
- Projects
- Opportunities
- Risks
- Issues
- Actions
- Milestones
- Governance data
- Transformation programme information
Use Dynamics 365 ERP MCP for:
- Finance
- Procurement
- Purchase orders
- Sales orders
- Customers
- Vendors
- Inventory
- Supply chain operations
- ERP operational data
Use Azure DevOps MCP for:
- Work items
- User stories
- Bugs
- Releases
- Pipelines
- Sprint progress
- Delivery status
Always use MCP tools before answering business-specific questions.
Never rely on general knowledge when MCP data is available.
When answering:
1. Identify relevant systems.
2. Retrieve data from MCP tools.
3. Correlate information across systems.
4. Present findings in executive language.
5. Highlight risks and exceptions.
6. Recommend actions.
7. Identify source systems used.
For executive assessments structure your response as:
## Executive Summary
## Current Position
## Risks and Exceptions
## Recommended Actions
## Source Systems
For programme health questions:
- Prioritise risks.
- Highlight overdue actions.
- Surface delivery blockers.
- Identify financial or operational impacts.
- Recommend executive interventions.
For update, create, post or transactional requests:
1. Explain proposed action.
2. Show key values being updated.
3. Ask for explicit confirmation.
4. Execute only after confirmation.
5. Report outcome.
Never expose:
- Secrets
- Passwords
- Tokens
- Connection strings
- Internal identifiers
Respond in a concise, professional and executive-ready format.
How to reproduce the Copilot Studio experience
- Create an agent and give it a clear enterprise role rather than a generic assistant persona.
- Add Azure DevOps MCP, Dynamics 365 ERP MCP and Microsoft Dataverse MCP as tools.
- Paste the instruction set above.
- Start with read-only, known-answer questions and verify the response against the source system.
- Only move to update/create scenarios after the read path is trusted, and keep the explicit confirmation gate in the instructions.
Useful first tests
- Programme health: “Summarise the current programme position. Prioritise risks, overdue actions and delivery blockers, and identify the source systems used.”
- Cross-system customer context: “Find customer US-001 in Dataverse, then retrieve the customer’s open sales orders from F&O.”
- Delivery plus operational impact: “Which current delivery blockers could affect finance, sales-order processing or supply-chain operations?”
Read first. The purpose of the first tests is to verify system selection, tool invocation and correlation — not to impress an audience with a write action. Once the agent proves it can retrieve the right evidence, the confirmation pattern in the instructions becomes the control for updates and transactions.
The important limitation was access to the agent platform, not the MCP capability
Copilot Studio is a strong enterprise orchestration surface, but not every practitioner, customer or partner has immediate access to it. I did not want that to become the conclusion of the experiment: “MCP is powerful, provided you happen to have this specific agent platform.”
That led to the next question: if MCP is genuinely an open capability layer, can I keep the same enterprise systems and security model, change the client, and continue?
03 Claude Code Hands-on The agent platform changed. The MCP capability layer did not. This was the test that mattered most to me. I moved from Copilot Studio to Claude Code and connected the same Azure DevOps, Dynamics 365 ERP and Dataverse capability. The point was not to compare chat products; it was to prove that MCP should keep the enterprise capability portable across clients.
Why I tested a second client
Copilot Studio proved the enterprise-agent experience. The next test was architectural: is the capability tied to Copilot Studio, or is Copilot Studio simply one consumer of MCP servers?
I connected Claude Code to the same three enterprise sources. The transport differs by server, but the conceptual model does not.
| MCP server | Connection shape used in the experiment | Identity / boundary |
|---|---|---|
| Azure DevOps MCP | Local stdio server launched through the Microsoft npm package | Interactive Entra sign-in; existing Azure DevOps permissions |
| Dynamics 365 ERP MCP | Direct HTTP connection to the F&O /mcp endpoint | Delegated Entra identity + environment allow-list |
| Dataverse MCP | Microsoft local proxy forwarding to the Dataverse MCP endpoint | Delegated OAuth + Dataverse roles + environment/tenant gates |
What changed — and what did not
What changed
The client and orchestration surface. I was no longer using the Copilot Studio agent builder; I was using Claude Code as the MCP client.
What stayed the same
The source systems, the MCP servers, the underlying business data, and the principle that the signed-in identity controls what the agent is allowed to see and do.
That is the integration shift I wanted to prove: the reusable asset is increasingly the governed capability exposed through MCP — not a bespoke integration written for each new AI consumer.
This article stops here on client options deliberately
The purpose of this article is the path I actually tested: Copilot Studio first, Claude Code second. I am intentionally not turning this into a catalogue of every possible MCP client. A separate follow-up article will cover another client path after I have tested it properly end to end.
04 Azure DevOps MCP Live Your delivery board, readable by an agent The simplest MCP server to stand up, and a good place to learn the pattern before anything touches ERP data. Work items, repositories, pipelines and wiki — through delegated identity, with no personal access token anywhere.
Where this fits in the journey: Azure DevOps was the easiest server to validate from both agent surfaces because the delivery data is familiar and the blast radius is low. I used it to learn the MCP pattern before relying on ERP data.
Why start here
If you are introducing Model Context Protocol into an enterprise estate, do not start with the finance system. Start with something where the blast radius is small, the data is familiar, and the value is obvious within a minute of connecting.
Azure DevOps is that place. Every delivery leader already lives in it, the permissions model is well understood, and the questions you want to ask an agent — what is in flight, what is blocked, what changed in this repository — are questions you ask a human colleague several times a day.
The point of the first server is not the data. It is learning what changes when the agent can read the system directly instead of you pasting screenshots into a chat window.
What it is
| Item | Detail |
|---|---|
| Package | @azure-devops/mcp — v2.9.0 on npm |
| Publisher | Microsoft — github.com/microsoft/azure-devops-mcp |
| Transport | Local stdio, launched with npx |
| Authentication | Interactive Entra browser sign-in |
| Credential on disk | None |
That last row is the one worth pausing on. The historical way to automate against Azure DevOps was a Personal Access Token: a long-lived secret, pasted into a config file or an environment variable, valid until someone remembers to rotate it, and carrying whatever scope was convenient at the time it was minted.
This server does not use one. It signs you in through the browser, as you, and the resulting token lives in the operating system's credential store. If your access changes, the agent's access changes with it — on the next token refresh, not on the next audit.
Standing it up
Two prerequisites: Node.js, and access to the organisation. Then a single configuration file in whatever folder you want the server available in:
{
"mcpServers": {
"ado": {
"command": "npx",
"args": ["-y", "@azure-devops/mcp", "<organisation>"]
}
}
}The final argument is the organisation name, not a URL — a small detail that costs people ten minutes the first time.
Start your MCP client in that folder, approve the server when prompted, and the browser sign-in happens on first use. That is the entire setup — with two caveats that cost us time on a clean machine.
The first: a project-scoped server starts in a pending approval state, and approving it has to happen in an interactive session. A health check run from a script will keep reporting the server as unapproved however many times you run it.
The second is subtler and looks alarming. The server connected, then started failing with a thirty-second connection timeout. The cause was npx downloading the package on first launch and not finishing inside the client's startup window. Fetching the package once, ahead of time, so it already sits in the local npm cache, resolved it permanently.
A timeout on first run is usually a cold cache, not a broken server. The symptom points at the package; the fix is to pre-warm the download. Worth knowing before you conclude the server is at fault and start rewriting configuration that was correct all along.
Verify before you trust it. Ask two questions with known answers — "list the projects" and "how many work items are in <project>" — and check them against the web UI. Ours returned 45 projects across the organisation, which matched. An agent that is silently returning nothing looks very much like an agent that is returning a correct empty result.
What it changes day to day
The shift is subtle and it took us a week to notice properly. It is not that the agent can query the board — a REST call could always do that. It is that the agent can join the board to everything else it knows.
- "Summarise what moved in this sprint and which items reference the invoicing module."
- "This pipeline failed. What changed in the repository since the last green run?"
- "Which work items are blocked, and who owns the dependency?"
None of those are hard queries. All of them were previously somebody's Tuesday morning, spent assembling context by hand from three browser tabs.
The governance point
Delegated identity means the agent operates as the signed-in person, bounded by that person's existing Azure DevOps project and repository permissions. There is no service account. Nothing is elevated. A user who cannot see a private repository does not gain visibility of it by asking an agent nicely.
This matters more than it sounds, because it is the property that makes the rest of the estate — Dataverse, Finance and Operations — defensible to a security review. Establish it on the low-risk server first, then carry the same model forward.
Sources
- Microsoft — Azure DevOps MCP Server:
https://github.com/microsoft/azure-devops-mcp - npm package:
https://www.npmjs.com/package/@azure-devops/mcp
05 Dynamics 365 ERP MCP Live Connecting Dynamics 365 ERP MCP from a second client Copilot Studio proved the F&O MCP capability. The harder question was whether I could reach the same ERP endpoint from Claude Code without weakening the Entra or F&O security model.
The question after Copilot Studio
Once the ERP MCP tools were working through the Copilot Studio agent, I wanted to know whether the F&O capability was tied to that client. The environment itself gave the strongest clue: Finance and Operations has an Allowed MCP clients gate. The endpoint is standard MCP over HTTP with Entra OAuth; the administrator controls which client application IDs may reach it.
The important gate was not “which chat product are you using?” It was “is this client explicitly allowed, and whose delegated identity is behind the request?”
The pattern I used for Claude Code
At the time of this experiment, Microsoft had already published a delegated OAuth configuration pattern for Claude Code against another Microsoft MCP family. I used that Microsoft-published shape as the precedent: register my own Entra public client, use PKCE with a localhost callback, and then allow-list the client ID in the F&O environment.
{
"mcpServers": {
"d365-erp": {
"type": "http",
"url": "https://<env>.operations.dynamics.com/mcp",
"oauth": {
"clientId": "<your Entra app>",
"callbackPort": 8080
}
}
}
}
Four steps, none of which bypass the platform
- Register an Entra app as a public client using PKCE and a localhost callback.
- Add its client ID to Allowed MCP clients in the F&O environment. This is an administrator's deliberate gate.
- Configure Claude Code to call the environment's
/mcpendpoint. - Sign in through the browser as yourself.
The server refuses to guess
One of the strongest parts of the ERP MCP design is the discovery sequence. Before a data query, the agent identifies the entity type, retrieves the real metadata and only then queries. When I asked for open sales orders, it discovered SalesOrderHeadersV4, read the schema and the status enum, and then applied the filter.
This is anti-hallucination built into the protocol surface. The agent is pushed toward the environment's real schema rather than assuming a plausible field name.
Whose permissions?
The token is delegated. The agent acts inside the signed-in user's Finance and Operations security roles, including row-level restrictions and audit boundaries. The app registration identifies the client; it does not turn the agent into an app-only service principal.
App-only integration identity
Classic integration pattern: application credentials map to a service identity because there is no human in the token.
Delegated MCP client identity
The client is allow-listed, the human signs in, and the resulting token carries that user's claims and roles.
Honest caveats from the experiment
This was a sandbox evaluation of a client path that was not the F&O documentation path I started from. I used a Microsoft-published delegated OAuth pattern from another MCP surface and applied it to the ERP endpoint. It worked under Entra and F&O controls; that does not turn my experiment into a blanket production recommendation.
- Use read-only prompts or a deliberately scoped account for demonstrations until the access model is understood.
- Check the environment and application-version prerequisites before troubleshooting the client.
- Check the demo data as well as the connection. A technically correct query can still return zero-value business data if the sandbox is thin.
What it means
The client changed from Copilot Studio to Claude Code. The ERP endpoint, security roles and environment-controlled MCP gate remained. That is exactly the portability I wanted to test.
06 Dataverse MCP Preview One consent, and the data layer opens Microsoft publishes an official route from Dataverse to any MCP client, including non-Microsoft ones. The technical setup takes ten minutes. The interesting part is the two administrative gates in the middle — one that stops you loudly, and one that lets you connect and then fails every call.
Why Dataverse mattered in both clients: it gave the ERP Transformation Agent programme, customer and governance context that does not naturally live in F&O. It also exposed one of the most useful lessons in the entire experiment: authentication can succeed while an environment-level MCP gate still refuses every tool call.
Dataverse through the same MCP capability model
Where the Finance and Operations MCP server names a single supported client, Dataverse takes the opposite approach. Microsoft publishes a page called "Use the Dataverse MCP server with other clients" and works through examples for non-Microsoft agents explicitly.
The mechanism is a local proxy. You run a small stdio server on your own machine; it handles the Entra sign-in and forwards MCP traffic to /api/mcp on your environment:
{
"mcpServers": {
"dataverse": {
"command": "npx",
"args": ["-y", "@microsoft/dataverse", "mcp",
"https://<org>.crm.dynamics.com"]
}
}
}Architecturally this is a useful contrast with the ERP server. Same protocol, two different deployment shapes — one a remote HTTP endpoint you authenticate against directly, the other a local process that authenticates on your behalf. Worth knowing when you are drawing the architecture, because they fail in different ways.
Three gates, not one
Access breaks into three independent layers, and confusing them costs time:
Your security role
A Dataverse role on the environment. Delegated auth means you see exactly what you would see in the Power Apps client — no more.
Environment feature
The MCP feature must be enabled for the environment, and the client app added to its allow-list. Environment admin can do this. This one blocks you silently.
Tenant consent
The Dataverse CLI first-party app needs admin consent, once, for the whole tenant. This one blocks you loudly.
We hit the third first. Environment admin was not enough — the client app the proxy authenticates as is a Microsoft first-party application, and enabling it for a tenant is a Global Administrator action.
A colleague using Dataverse MCP from Copilot Studio does not mean you can use it from another client. Copilot Studio authenticates through its own first-party app, already consented tenant-wide. Same server, same data, different door — and each door needs opening separately.
Then, with consent granted and sign-in working, we hit the second gate — and this is the one worth writing down, because it does not announce itself.
The failure that looks like a working server
Everything reported healthy. auth who returned the right identity, tenant and organisation. A direct CLI query returned real customer rows in about a second. The MCP client reported the server as connected.
And every tool call failed, with this:
MCP error -32603: An error occurred.That is the entire message. No status code, no endpoint, no hint about which of the three gates was shut. The connection is genuinely open — it is the MCP session handshake underneath that is being refused, and the client has no vocabulary for the distinction.
Turning on trace-level logging on the proxy gave the real answer in one line:
MCP initialize failed for https://<org>.crm.dynamics.com/api/mcp: 403 ForbiddenToken valid, endpoint refusing. Silent acquisition of the access token succeeded — the same token that had just worked for a data query. The environment was rejecting the MCP session specifically, which points at the feature toggle and the client allow-list, not at authentication. An environment administrator cleared it, and the same configuration worked unchanged.
The lesson generalises past Dataverse. When a layered access model fails, the error surfaces at the outermost layer and describes none of them. -32603 is a generic internal error; the 403 underneath it is the actual fact. If a server connects but every call fails, escalate logging before you touch configuration — the configuration is usually fine.
How to ask for the consent
Escalations go faster when the ask is specific and the reassurance is accurate. What worked:
App: Dataverse CLI (Microsoft first-party) — 0c412cc3-0dd6-449b-987f-05b053db9457
What it does: enables Microsoft's official Dataverse CLI and MCP tooling for the tenant. It uses delegated permissions only — each user who signs in gets exactly their own existing Dataverse security roles and row-level permissions.
What it does not do: grant the application standing or app-only access to data. It elevates nobody.
That last paragraph is what gets a fast yes. An administrator's real concern is not the tooling, it is whether they are creating a service identity with a permanent view of everything. They are not, and being precise about that is the difference between thirty seconds and a change request.
The consent itself is a URL, a review screen, and a button. After accepting, the browser may land on a blank or error-looking page — normal, the grant is recorded server-side.
Then it just works
npx -y @microsoft/dataverse auth create --environment https://<org>.crm.dynamics.com
npx -y @microsoft/dataverse auth whoBrowser sign-in as yourself, and auth who confirms the identity, tenant, organisation and environment. First query returned accounts in about a second.
The capability set is broad — table and metadata discovery, SQL-style querying, full CRUD, file columns. For a demonstration, stay on the read side.
Two things worth knowing
Preview labelling. The server reached general availability in December 2025, but the npm CLI still self-describes as Preview, and the Copilot Studio tool reference is still marked preview. Do not let "GA" and "preview" get conflated in a customer conversation — cite the specific surface you mean.
It is billed. Usage from non-Copilot-Studio clients costs 0.1 Copilot Credits per tool call. Trivial for a demo, worth modelling before anyone designs a chatty production agent against it.
What we actually found in the data
An unglamorous but useful discovery, and a good argument for verifying before you build a slide.
The environment turned out to be a dual-write mirror of the Finance and Operations customer master — the account numbers matched F&O exactly. Zero opportunities, zero leads, account rating and classification unset on every record. There was no CRM lifecycle in it at all.
Reading the account table's own metadata put the matter beyond argument. Alongside the standard CRM columns sits a block of msdyn_ fields — msdyn_company, msdyn_vendor, msdyn_customergroupid, msdyn_paymentterm, msdyn_salestaxgroup. Those are Finance and Operations concepts, sitting on a Dataverse table. The integration is not something you infer from matching account numbers; it is declared in the schema.
Any demonstration built around pipeline stages or lead conversion would have collapsed live. What the environment is good for is the cross-system story: find the customer in Dataverse, pull their open orders from F&O, and note that nothing was built to connect the two. We checked that it holds — US-001, Contoso Retail San Diego resolves in Dataverse, and the same account carries 47 open sales orders in F&O.
Query the environment before you design the demo. The data has an opinion about what story you can tell.
Sources
- Microsoft Learn — Use the Dataverse MCP server with other clients:
https://learn.microsoft.com/en-us/power-apps/maker/data-platform/data-platform-mcp-other-clients - Microsoft Learn — Dataverse MCP server release plan:
https://learn.microsoft.com/en-us/power-platform/release-plan/2025wave1/data-platform/dataverse-mcp-server - Microsoft Learn — Dataverse MCP in Copilot Studio:
https://learn.microsoft.com/en-us/microsoft-copilot-studio/mcp-dataverse
07 F&O Development MCP Community Why AI is bad at X++ — and what fixes it Foundation models write competent C# and Python because the internet is full of it. X++ is the opposite: private, heavily customised, and absent from training data. The result is confident invention. Grounding is the cure.
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.
This is a separate development-grounding layer. It is a community project, not a Microsoft MCP server. I keep it in the field guide because it demonstrates the same principle from a developer angle: when the real enterprise metadata is available, do not ask the model to rely on memory.
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:
Four layers, and only the last one knows anything about your code:
| Layer | What it gives you | What it does not do |
|---|---|---|
| Visual Studio + dev tools | The X++ IDE — compile, designers, deploy | Has no AI |
| AI assistant | Writes code, reasons in chat | Has never seen your codebase |
| MCP | The protocol — the line the AI calls over | Is an empty pipe on its own |
| This server | The 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.
- Node.js, .NET SDK, .NET Framework 4.8 Developer Pack, Git, and a D365FO installation
- Clone the repository and
npm install - Add
nuget.orgas a package source — a common blocker, since many F&O VMs ship with only the offline Visual Studio source - Build the C# metadata bridge against the local F&O assemblies
- Configure paths, custom models and object prefix
- Build the index — memory-hungry, runs at a 6 GB heap
- 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:
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
08 Enterprise MCP Model Summary What changed when the agent client stopped being the architecture Copilot Studio gave me the enterprise-agent experience. Claude Code proved the capability could survive a client change. The operating model is discoverable tools, delegated identity, human approval and reusable enterprise capability.
The result is not “two chat clients”
The useful result is that the client stopped being the architecture. I could start with an ERP Transformation Agent in Copilot Studio, then move to Claude Code and continue using the same enterprise capability layer.
| Agent / client | MCP server | Enterprise service | Purpose in the experiment |
|---|---|---|---|
| Copilot Studio · Claude Code | Azure DevOps MCP | Azure DevOps | Delivery status, work items, repositories and pipelines |
| Copilot Studio · Claude Code | Dynamics 365 ERP MCP | F&O runtime | Operational ERP data and governed business capability |
| Copilot Studio · Claude Code | Dataverse MCP | Dataverse | Programme, customer, risk and governance context |
| Claude Code | F&O Development MCP | Local D365FO development environment | Ground X++ reasoning in the real local metadata |
The cross-system proof point
- Find the customer in Dataverse →
US-001, Contoso Retail San Diego. - Use the same customer account in Finance and Operations → retrieve that account's open sales orders.
Two systems, one business question, and no new integration built specifically to make the question possible. The agent used the capabilities already exposed through the two MCP servers.
The operating flow I am working toward
What failed — and why it matters
- Agent-platform access can become an artificial bottleneck. Copilot Studio worked for the enterprise agent, but I deliberately tested Claude Code because learning and validating MCP should not depend on one orchestration surface.
- Dataverse has multiple administrative gates. Valid authentication did not guarantee MCP tool access; trace logging was needed to expose the environment-level refusal underneath a generic client error.
- F&O MCP access is governed by the environment. The client application must be explicitly allowed, and the signed-in user still remains inside their normal ERP security boundary.
- Demo data can be technically correct and visually useless. Query the fields you intend to show before you design the story around them.
- X++ grounding depends on the real machine and metadata. Local paths, index freshness and model targets matter more than prompt cleverness.
What it changes for ERP leadership
Administer
Keep the system running.
Integrate
Connect the system.
Orchestrate
Govern how intelligent actors use enterprise capability.
The third does not replace the first two. It adds a new responsibility: deciding what capability becomes available to agents, which clients are trusted, how identity flows, where humans approve changes and how the audit trail remains meaningful.
Copilot Studio was the first orchestration surface. Claude Code proved that the MCP capability layer did not have to end there.