D365 F&O: Orchestrating Custom APIs via Dataverse MCP

Architecture, integration and automation · 12 min read
TL;DR: The Dataverse MCP Server now supports Custom APIs, allowing external AI agents to invoke complex D365 Finance & Operations logic as atomic, single-call transactions that eliminate partial data failures.
D365 F&ODataverseCopilot & AIIntegrationArchitecture

Large language models and conversational agents excel at understanding natural language context, but they struggle with database transactions. When an external autonomous agent attempts to execute a multi-step ERP process in Dynamics 365 Finance and Operations through discrete REST operations, network blips or business validation failures frequently leave transactions half-completed. The arrival of Custom API support within the Dataverse Model Context Protocol (MCP) Server eliminates this vulnerability by packaging multi-step ERP workflows into atomic, strongly typed tools that any external agent can safely invoke.

At a glance

  • 🔹 Applies to: Dynamics 365 Finance, Dynamics 365 Supply Chain Management (version 10.0.45 PQU-7, 10.0.46 PQU-2, 10.0.47 or later), Microsoft Dataverse, Power Platform admin center.
  • 🔹 Prerequisites: Power Platform Administrator role (for MCP client configuration), System Administrator or System Customizer security privileges, Virtual Entities (mserp_*) configured, Visual Studio for C# plug-in development.
  • 🔹 Effort level: Medium (plug-in authoring, Custom API contract definition, and MCP agent registration).
  • 🔹 Outcome: Encapsulate complex D365 F&O business logic into first-class Dataverse MCP tools that provide server-side validation, transactional Dataverse integrity, and consistent execution across any MCP-compliant agent platform.

How it works

The Model Context Protocol (MCP) is an open standard that allows host applications—such as Visual Studio Code, Claude Desktop, or Microsoft Copilot Studio—to introspect and execute external tools via structured JSON-RPC communication. The Dataverse MCP Server exposes environment metadata and operations directly to these clients through the /api/mcp endpoint. Previously, external agents interacting with Dataverse or D365 F&O virtual entities had to execute basic Create, Read, Update, and Delete (CRUD) primitives sequentially. A composite workflow required multiple conversational turns or client-side batching, exposing your ERP to severe data integrity risks if an intermediate call timed out.

With Custom API support, the transactional boundary moves entirely inside Dataverse. A Custom API defines a formal request-response contract (unbound or bound to an entity) backed by a compiled C# plug-in. When an AI agent invokes the tool via MCP, the Dataverse MCP Server deserializes the LLM arguments into the Custom API request contract and dispatches the execution into the Dataverse transaction pipeline. Inside the plug-in, developers execute business logic against D365 F&O Virtual Entities (mserp_*) or orchestrate operations across multiple tables. If any validation fails or an exception occurs, the plug-in throws an InvalidPluginExecutionException, rolling back Dataverse transactions and preventing unvalidated ERP updates from executing.

Dataverse MCP Custom API Transaction Flow
Figure 1: Figure 1: Architectural flow showing atomic transaction boundaries between MCP agents, Dataverse Custom APIs, and D365 F&O Virtual Entities.
⚠️ Watch outCustom Actions are not supported in the Dataverse MCP Server. You must migrate legacy process actions to modern Dataverse Custom APIs (customapi) to expose them as tools to MCP-connected AI agents.
Integration Approach Transaction Guarantee Agent Schema Discovery Rollback & Compensation Best Suited For
Direct OData / Data Entity Calls Non-atomic (individual HTTP calls) Poor; agent requires low-level schema knowledge Client-orchestrated manual compensation Simple, read-only lookups or single-record updates
Dynamics 365 ERP Dynamic MCP Server Atomic per tool call (Entity / Form / Action) Built-in ERP tool discovery (Data/Form/Action tools) F&O TTS transaction blocks on backend X++ Direct X++ class execution and core F&O operations
Dataverse Custom API via Dataverse MCP Atomic for Dataverse records; external virtual entity calls execute independently Rich, typed request/response parameter contracts Server-side database rollback on plug-in failure Composite workflows spanning Power Platform & F&O

Step-by-step solution

1. Enable the Dataverse MCP Server in Power Platform Admin Center

Log in to the Power Platform admin center (admin.powerplatform.microsoft.com) using an account with Power Platform Administrator permissions. Navigate to Manage > Environments, select your target environment, and select Settings > Product > Features. Locate the Dataverse Model Context Protocol group and toggle Allow MCP clients to interact with Dataverse MCP server to On. Under Advanced Settings, select the specific MCP clients you want to allow (such as Microsoft GitHub Copilot or Claude) and set Is Enabled to Yes.

ℹ️ Good to knowBy default, Microsoft Copilot Studio is automatically authorized to communicate with the Dataverse MCP server. External clients accessing the /api/mcp endpoint require explicit allow-listing in Advanced Settings.

2. Ensure Required D365 F&O Virtual Entities Are Generated

Verify that your D365 F&O environment has generated the necessary virtual entity schemas in Dataverse. In the Power Apps Maker portal (make.powerapps.com), go to Tables and search for the prefix mserp_. Ensure the entities relevant to your transaction—such as mserp_salesorderheaderentity, mserp_salesorderlineentity, and mserp_customercreditlimitentity—are marked as active and visible. If missing, navigate to Advanced Find, query the Available Finance and Operations Entities table, select the required entities, set the Visible check box to Yes, and save.

3. Develop the C# Plug-in Assembly with Atomic Logic

Open Visual Studio and create a .NET Framework 4.6.2 Class Library project. Reference the Microsoft.CrmSdk.CoreAssemblies NuGet package. Implement the IPlugin interface, extracting the Custom API input parameters from context.InputParameters and validating ERP pre-conditions. Use the IOrganizationService to perform updates against the F&O virtual entities. Wrap critical business rules in validation blocks that throw an InvalidPluginExecutionException upon failure to ensure a zero-state rollback.

using System;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

namespace Contoso.Dataverse.Plugins
{
    public class ReleaseOrderCreditHoldCustomApi : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            var service = serviceFactory.CreateOrganizationService(context.UserId);

            if (!context.InputParameters.Contains("SalesOrderNumber") || context.InputParameters["SalesOrderNumber"] == null)
            {
                throw new InvalidPluginExecutionException("SalesOrderNumber parameter is mandatory.");
            }

            string orderNumber = (string)context.InputParameters["SalesOrderNumber"];
            string releaseReason = context.InputParameters.Contains("ReasonCode") ? (string)context.InputParameters["ReasonCode"] : "AI_OVERRIDE";
            decimal overrideLimit = context.InputParameters.Contains("OverrideAmount") ? (decimal)context.InputParameters["OverrideAmount"] : 0m;

            // 1. Query F&O Sales Order Virtual Entity
            var query = new QueryExpression("mserp_salesorderheaderentity")
            {
                ColumnSet = new ColumnSet("mserp_salesordernumber", "mserp_salesorderstatus", "mserp_orderholdstatus", "mserp_invoicecustomeraccountnumber")
            };
            query.Criteria.AddCondition("mserp_salesordernumber", ConditionOperator.Equal, orderNumber);
            var results = service.RetrieveMultiple(query);

            if (results.Entities.Count == 0)
            {
                throw new InvalidPluginExecutionException(string.Format("Order {0} not found in F&O.", orderNumber));
            }

            var orderRecord = results.Entities[0];

            // 2. Perform business checks
            if (overrideLimit > 50000m)
            {
                throw new InvalidPluginExecutionException("Autonomous credit overrides exceeding 50,000 USD require Human-In-The-Loop approval.");
            }

            // 3. Update order hold status atomically
            Entity updateHeader = new Entity("mserp_salesorderheaderentity", orderRecord.Id);
            updateHeader["mserp_orderholdstatus"] = false;
            service.Update(updateHeader);

            // 4. Populate typed response contract
            context.OutputParameters["IsReleased"] = true;
            context.OutputParameters["StatusMessage"] = string.Format("Order {0} credit hold released with reason {1}.", orderNumber, releaseReason);
        }
    }
}

4. Register Assembly and Define the Custom API Metadata

Sign the compiled assembly with a strong-name key (.snk) and deploy it to Dataverse using the Plugin Registration Tool (PRT). Next, in the Power Apps Maker portal, create a new unmanaged solution and select New > More > Other > Custom API. Define the Unique Name (e.g., contoso_ReleaseOrderCreditHold), Display Name, and Binding Type as Unbound. Set the Plugin Type to reference the registered class name. Write a concise, natural language Description, as the MCP Server presents this field directly to AI agents during tool discovery.

💡 Pro tipLLMs use the Custom API Description to determine when to call the tool. Be explicit: "Releases a sales order credit hold in D365 F&O when credit evaluation criteria are satisfied. Accepts SalesOrderNumber, ReasonCode, and OverrideAmount."

5. Create Request and Response Parameter Contracts

In your solution, navigate into your Custom API and add Custom API Request Parameters: create SalesOrderNumber (String, Required), ReasonCode (String, Optional), and OverrideAmount (Decimal, Optional). Next, add Custom API Response Properties: create IsReleased (Boolean) and StatusMessage (String). Every parameter must have a descriptive label and help text so the MCP protocol can serialize typed schemas for agent reasoning.

6. Expose and Verify Tool Invocation in the MCP Agent

Launch your MCP client configuration (such as the claude_desktop_config.json or VS Code settings). Configure the MCP server connection to point to your Dataverse instance using your Microsoft Entra ID credentials. Trigger tool introspection in your agent platform. Verify that contoso_ReleaseOrderCreditHold appears in the agent tool registry with its typed input JSON schema. Execute a test call using natural language to ensure the Dataverse MCP server correctly dispatches the request to your plug-in and returns the expected ERP status.

Worked example

Contoso Industrial Machinery manufactures industrial equipment and manages spare parts distribution in Dynamics 365 Supply Chain Management. When high-priority "Machine Down" orders are placed, customers frequently breach temporary credit ceilings, placing the orders on credit hold. Dispatch specialists previously spent significant manual effort per incident reviewing open balances, verifying pending customer payments in Accounts Receivable, releasing the sales order hold, and logging documentation in the ERP.

Contoso deployed an autonomous dispatch agent in Copilot Studio connected to the Dataverse MCP Server. To safeguard the ERP against partial updates, Contoso implemented an unbound Custom API named contoso_AogOrderCreditEvaluation backed by a C# plug-in. When a customer service agent or automated dispatch monitor alerts the agent that Order SO-100482 is blocked, the agent invokes the Custom API with the following JSON-RPC payload:

{
  "server_name": "Dataverse",
  "tool_name": "contoso_AogOrderCreditEvaluation",
  "arguments": {
    "SalesOrderNumber": "SO-100482",
    "CustomerAccount": "US-014",
    "RequestedExposure": 34500.00,
    "Justification": "Critical drill rig down at production site Alpha"
  }
}

The Dataverse plug-in verifies that the customer's payment history in mserp_customercreditlimitentity reflects no delinquent invoices over 60 days, checks that the override amount is under 50,000 USD, updates the mserp_orderholdstatus flag to false on mserp_salesorderheaderentity, and posts an audit note to the F&O order log. During integration testing, an intentional failure scenario was simulated where the virtual entity service timed out during hold removal. The Dataverse Custom API aborted the pipeline, rolled back the evaluation record, and returned a cleanly typed failure code. The agent caught this error and routed the ticket to the credit manager rather than leaving the order in an ambiguous, unreleased state.

-85%
resolution time
0%
orphaned records
100%
transaction atomicity
Order Credit Hold Resolution Metrics (Before vs After MCP Custom APIs)
Figure 3: Comparison of process duration and transactional integrity errors before and after implementing Dataverse Custom APIs.

Automation and AI opportunities

Pairing Custom APIs in the Dataverse MCP Server with Dynamics 365 F&O unlocks several architectural patterns across the Power Platform and Azure ecosystems:

  • Copilot Studio Autonomous Credit Agents: Deploy autonomous agents that monitor customer orders in D365 F&O. When an order goes on hold, the agent uses Dataverse MCP to evaluate real-time inventory and credit limits, applying the Custom API to execute the release without human intervention.
  • Unified F&O Business Events Escalations: Trigger a Power Automate cloud flow from a D365 F&O Business Event (e.g., "Customer credit limit changed"). The flow invokes an MCP agent to re-evaluate all pending sales orders for that customer and execute batch Custom API tool calls.
  • Fabric IQ Grounding: Combine the transaction capabilities of Dataverse Custom APIs with analytical querying from Fabric IQ MCP. An agent can query historical sales and payment trends via Fabric IQ, determine whether an exception is warranted, and invoke the Dataverse Custom API to apply the decision.

Common pitfalls and troubleshooting

  1. Symptom: The agent cannot find the Custom API tool during MCP tool discovery.
    Cause: The operation was created as a legacy Dataverse Custom Action rather than a modern Custom API, or the Custom API is marked as IsPrivate = true.
    Fix: Verify in the Power Apps Maker portal that the component is a Custom API (customapi entity) and ensure the Is Private attribute is set to No.
  2. Symptom: HTTP 403 Forbidden when external MCP client connects to /api/mcp.
    Cause: The MCP client platform (e.g., Claude Desktop, GitHub Copilot) is not enabled in the Power Platform admin center allow-list.
    Fix: In PPAC, navigate to Environments > Settings > Product > Features > Dataverse Model Context Protocol > Advanced Settings. Open the relevant client record and set Is Enabled to Yes.
  3. Symptom: Custom API plug-in execution throws a generic timeout error on F&O Virtual Entities.
    Cause: Complex multi-table virtual entity joins exceed the 2-minute Dataverse sandbox execution limit.
    Fix: Optimize virtual entity OData queries by selecting specific column sets (avoid ColumnSet(true)) or delegate long-running calculations to an asynchronous D365 F&O batch job triggered via business events.
  4. Symptom: The LLM agent passes incorrect or malformed parameter types.
    Cause: Custom API Request Parameters lack clear natural language descriptions, leaving the agent guessing the string formatting or numeric constraints.
    Fix: Populate the Description field on all Request Parameters with explicit data formats (e.g., "D365 F&O Sales Order ID in format SO-XXXXXX").
  5. Symptom: Duplicate transaction execution when agent retries connection drops.
    Cause: The Custom API plug-in is not idempotent, re-executing logic on every invocation.
    Fix: Implement idempotency tokens or check whether the target ERP state (e.g., mserp_orderholdstatus == false) is already satisfied before executing updates.

Best-practice checklist

  • ✅ Build Custom APIs instead of legacy Custom Actions to ensure native visibility across Dataverse MCP endpoints.
  • ✅ Write descriptive, prompt-grounded text in the Description attributes of the Custom API and all its parameters to guide LLM reasoning.
  • ✅ Enforce strict boundary validation in C# plug-ins; throw InvalidPluginExecutionException to guarantee database transaction rollbacks.
  • ✅ Always specify explicit column sets when reading F&O virtual entities (mserp_*) to prevent OData payload bloat.
  • ✅ Implement transactional idempotency checks to protect ERP records from automated agent retries.
  • ✅ Secure the Custom API with Dataverse security roles and assign privilege requirements directly to the Custom API record.
  • ✅ Monitor MCP usage and latency through Power Platform admin center telemetry and Application Insights.

FAQ

How do Dataverse Custom APIs differ from the Dynamics 365 ERP Dynamic MCP Server?

The Dynamics 365 ERP Dynamic MCP Server (supported in F&O versions 10.0.47, 10.0.46 PQU-2, and 10.0.45 PQU-7) exposes F&O data entities, forms, and X++ action classes directly. Dataverse Custom APIs via the Dataverse MCP server are ideal when your business process spans both Power Platform/Dataverse records and F&O virtual entities within a unified server-side transaction.

Can I use Custom APIs with on-premises or Cloud-Hosted Development Environments (CHE)?

No. The Dataverse MCP server is cloud-hosted, and F&O virtual entity or ERP MCP integration requires a Tier 2+ environment or a Unified Developer Environment. Cloud-Hosted Environments (CHE) do not support the ERP MCP server.

Are Custom APIs executed synchronously or asynchronously when called by an MCP agent?

Custom APIs exposed through the Dataverse MCP Server execute synchronously. The MCP protocol expects immediate execution feedback to formulate the next turn in the model conversation. If a background process is required, your Custom API should initiate a batch task in D365 F&O and return the generated Batch Job ID as an output parameter.

Dataverse MCP and Custom API Architecture
Figure 2: Figure 2: Key architectural components and operational pillars of Dataverse MCP Custom APIs.

Key takeaways

  • 🎯 Dataverse Custom APIs convert multi-step, fragile ERP sequences into atomic, single-call tools discoverable by any MCP-compliant AI agent.
  • 🎯 Server-side plug-ins provide strong transactional guarantees in Dataverse: failing fast on validation prevents orphaned records and blocks invalid updates from reaching ERP endpoints.
  • 🎯 Detailed parameter descriptions are vital; they serve as the prompt schema that LLMs use to construct valid payloads.
  • 🎯 Managed Environments and Power Platform admin center client allow-listing provide enterprise-grade governance over external agent access.

Further reading

Note: written with AI assistance and reviewed by Gaurav Jadaun. Always validate in a sandbox environment before applying to production.

Comments

Popular posts from this blog

Monthly Inventory Close in Microsoft Dynamics AX 2012

Product Configuration Technology Ax2012R3

How to configure Fixed Assets Module in Ax2012