MCP in practice · From Copilot Studio to Claude Code

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.

8 chapters2 MCP clients tested3 enterprise MCP serversX++ grounding included
The journey described in this guide
Copilot StudioERP Transformation Agent
same MCP layer
Claude CodeAlternate MCP client
Azure DevOps MCP
Dynamics 365 ERP MCP
Dataverse MCP
F&O Dev MCP
Community
The client changed. The source systems, MCP capability and security boundaries did not.
Copilot StudioFirst orchestration surface for the ERP Transformation Agent
Claude CodeSame MCP servers through a different client
3 enterprise MCPsAzure DevOps · D365 ERP · Dataverse
X++ groundingLocal metadata instead of invented APIs
← Back to Blog
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 thesis · API to MCP · Reading time 6 min

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 integrationMCP 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

ActorWho is asking AgentWhat reasons MCPExposes capability ServiceHolds logic DataWhat is returned
Identity / Credentialcuts across all five. Who the agent acts as determines what it can see, what it can change and what the audit trail says afterwards.
Presentation: ERP Transformation Through AI Agents and MCPMelbourne Biz Apps User Group · 13 Aug 2026
View presentation & agent project on GitHub ↗ Presentation repository: https://github.com/faisalfareedtech/AI-ERP_Transformation_Director_Agent
End of chapter 1
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.
Copilot Studio · 3 MCP tools · Reading time 6 min

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.

Copilot Studio AI ERP Transformation Agent configured with Azure DevOps MCP, Dynamics 365 ERP MCP and Microsoft Dataverse MCP
Copilot Studio: the AI ERP Transformation Agent with Azure DevOps, Dynamics 365 ERP and Dataverse MCP tools connected. Click the image for the full-size view.

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.

ERP Transformation Director Agent instructions
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

  1. Create an agent and give it a clear enterprise role rather than a generic assistant persona.
  2. Add Azure DevOps MCP, Dynamics 365 ERP MCP and Microsoft Dataverse MCP as tools.
  3. Paste the instruction set above.
  4. Start with read-only, known-answer questions and verify the response against the source system.
  5. 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?

End of chapter 2
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.
Claude Code · Same enterprise capability · Reading time 6 min

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 serverConnection shape used in the experimentIdentity / boundary
Azure DevOps MCPLocal stdio server launched through the Microsoft npm packageInteractive Entra sign-in; existing Azure DevOps permissions
Dynamics 365 ERP MCPDirect HTTP connection to the F&O /mcp endpointDelegated Entra identity + environment allow-list
Dataverse MCPMicrosoft local proxy forwarding to the Dataverse MCP endpointDelegated 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.

End of chapter 3
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.
Azure DevOps MCP · Live ·  Reading time 5 min

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

ItemDetail
Package@azure-devops/mcp — v2.9.0 on npm
PublisherMicrosoft — github.com/microsoft/azure-devops-mcp
TransportLocal stdio, launched with npx
AuthenticationInteractive Entra browser sign-in
Credential on diskNone

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:

Code / configuration
{
  "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
End of chapter 4
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.
Dynamics 365 ERP MCP · Live · Reading time 9 min

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.

Claude Code MCP configuration
{
  "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

  1. Register an Entra app as a public client using PKCE and a localhost callback.
  2. Add its client ID to Allowed MCP clients in the F&O environment. This is an administrator's deliberate gate.
  3. Configure Claude Code to call the environment's /mcp endpoint.
  4. 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.

End of chapter 5
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.
Dataverse MCP · Preview ·  Reading time 6 min

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:

Code / configuration
{
  "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:

Code / configuration
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:

Code / configuration
MCP initialize failed for https://<org>.crm.dynamics.com/api/mcp: 403 Forbidden

Token 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

Code / configuration
npx -y @microsoft/dataverse auth create --environment https://<org>.crm.dynamics.com
npx -y @microsoft/dataverse auth who

Browser 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
End of chapter 6
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.
F&O Development MCP · Community project ·  Reading time 7 min

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:

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 / configuration
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
End of chapter 7
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.
Enterprise operating model · Summary · Reading time 10 min

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 / clientMCP serverEnterprise servicePurpose in the experiment
Copilot Studio · Claude CodeAzure DevOps MCPAzure DevOpsDelivery status, work items, repositories and pipelines
Copilot Studio · Claude CodeDynamics 365 ERP MCPF&O runtimeOperational ERP data and governed business capability
Copilot Studio · Claude CodeDataverse MCPDataverseProgramme, customer, risk and governance context
Claude CodeF&O Development MCPLocal D365FO development environmentGround X++ reasoning in the real local metadata
3
enterprise MCP servers used across the agent experience
2
different MCP client/orchestration surfaces tested in this article
0
new Dataverse-to-F&O integration written for the cross-system customer demo

The cross-system proof point

  1. Find the customer in DataverseUS-001, Contoso Retail San Diego.
  2. 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

1Business requestDirector asks the outcome question
2OrchestrationAgent decides which MCP capabilities are relevant
3ERP investigationF&O evidence and operational state
4Enterprise contextDataverse + delivery context
5Human approvalRequired before governed transactional change
6Governed executionApproved action only; verify outcome
7Delivery remediationEvidence becomes backlog and continuous improvement

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.
Companion presentationERP Transformation Through AI Agents and MCP · 13 Aug 2026
View presentation & agent project on GitHub ↗ Presentation repository: https://github.com/faisalfareedtech/AI-ERP_Transformation_Director_Agent
End of chapter 8