Blogs


  • Connect Claude and Codex Directly to your Microsoft Dataverse


    Dataverse can now act as an MCP server. That means Claude, Claude Code, GitHub Copilot, and OpenAI Codex can read your tables, run queries, create records, and even create schema — against a real Power Platform environment, with your security roles and row-level security still applied.

    NOTE: be carefull to run this on production environments and I recommend only using it on development or trial environments, once you tested everything you can then ask the AI to pack evrything on a solution you can then export and import to production. 

    The Dataverse MCP server went generally available on December 15, 2025. Since then the setup has gotten considerably simpler than it was during preview, and the client list has grown well past Copilot Studio. This post is the short version: what you need, what to click, and where the official documentation lives.


    What you get

    Once connected, the agent has a working tool surface against your environment:

    Tool What it does
    search Find table schemas, skills, and scopes by keyword
    describe Full details for any table, record, schema, or app
    read_query Run Dataverse SQL SELECT queries
    create_record / update_record / delete_record Row-level CRUD (delete requires explicit approval)
    create_table / update_table / delete_table Schema creation and modification
    upsert_skill / delete_skill Manage business skills and playbooks
    init_file_upload / commit_file_upload / file_download File operations via SAS URLs

    Worth stating plainly, because it surprises people: create_table and update_table are in the GA endpoint, not preview. You can hand an agent a data model and have it stand up the tables.


    Step 1 — Enable the MCP server on the environment

    This is admin-side and it's the step people skip.

    1. Go to the Power Platform admin centerManageEnvironments.
    2. Select your environment → SettingsProductFeatures.
    3. Find Dataverse Model Context Protocol and turn on Allow MCP clients to interact with Dataverse MCP server.
    4. Select Advanced Settings — this is the allowed-clients list.

    By default, only Copilot Studio is enabled. Anything else — Claude, Codex, VS Code — has to be added here explicitly. This is the governance model Microsoft introduced at GA, and it's the single most common reason a correctly configured client still gets rejected.

    📄 Configure the Dataverse MCP server for an environment


    Step 2 — Pick your connection path

    There are two, and the choice matters more than the docs let on.

    Local proxy

    An npm package (@microsoft/dataverse) runs a local process that handles auth and talks to Dataverse on your behalf. Requires Node.js 18+.

    Two prerequisites:

    • A tenant admin grants consent once for the Dataverse CLI app:
      https://login.microsoftonline.com/{your-tenant-id}/adminconsent?client_id=0c412cc3-0dd6-449b-987f-05b053db9457
    • That same app ID (0c412cc3-0dd6-449b-987f-05b053db9457) is set to Is Enabled = Yes in Advanced Settings. If it isn't listed, add it manually with any name.

    Remote endpoint

    Connect straight to https://<yourorg>.crm.dynamics.com/api/mcp. No local process, no Node.js. You register your own Entra app instead:

    1. Microsoft Entra admin centerIdentityApplicationsApp registrationsNew registration.
    2. Note the Application (client) ID.
    3. API permissionsAdd a permissionMicrosoft APIsDynamics CRMmcp.tools.
    4. Back in PPAC Advanced Settings, add a client entry with that client ID and set Is Enabled = Yes.

    That mcp.tools permission is the one everybody misses. Without it the endpoint rejects you even though the client shows as allowed.

    One more thing that isn't in the Microsoft docs: Entra ID doesn't support Dynamic Client Registration. Clients that expect to self-register against the authorization server can't. You have to supply the pre-registered client ID explicitly in the client's advanced connector settings — otherwise you get a generic connection failure with nothing useful in it.

    📄 Connect to Dataverse with MCP in non-Microsoft clients


    Step 3 — Configure the client

    Claude Desktop

    SettingsDeveloperEdit Config, then:

    json
    {
      "mcpServers": {
        "Dataverse": {
          "command": "npx",
          "args": ["-y", "@microsoft/dataverse", "mcp", "https://yourorg.crm.dynamics.com"]
        }
      }
    }

    Quit and reopen — not just close the window. You'll get an auth prompt on first launch. Tools then appear under Search and tools.

    For a faster, more reliable start, install globally and point at the binary directly instead of going through npx:

    bash
    npm install -g @microsoft/dataverse

    Then use the resolved path (which dataverse) as command, with args: ["mcp", "<your org URL>"]. On a cold npx, the package download can outrun the client's initialization timeout and surface as a spurious "Server disconnected."

    Claude Code

    bash
    claude mcp add dataverse -t stdio -- npx -y @microsoft/dataverse mcp https://yourorg.crm.dynamics.com

    Restart, authenticate when prompted, then verify with /mcp. Test it with something like show me the tables in Dataverse or describe the account table.

    Codex

    Codex isn't named in the Microsoft docs, but it's a standard MCP host and it works. Codex stores MCP config in ~/.codex/config.toml, shared across the CLI, the IDE extension, and the ChatGPT desktop app on the same machine.

    For the local proxy (STDIO):

    bash
    codex mcp add dataverse -- npx -y @microsoft/dataverse mcp https://yourorg.crm.dynamics.com

    Or by hand in ~/.codex/config.toml:

    toml
    [mcp_servers.dataverse]
    command = "npx"
    args = ["-y", "@microsoft/dataverse", "mcp", "https://yourorg.crm.dynamics.com"]

    For the remote endpoint (Streamable HTTP):

    bash
    codex mcp add dataverse --url https://yourorg.crm.dynamics.com/api/mcp
    codex mcp login dataverse

    Then confirm with codex mcp list and /mcp inside a session. If you scope the config to a project rather than ~/.codex/, remember Codex ignores project-local config in untrusted directories — either mark the project trusted or keep the server in the global config.

    📄 Codex MCP documentation

     

    ----
     

    Controlling what the agent can actually do

    Once the connector is live, Claude Desktop lets you set permissions per tool, split into read-only and write/delete groups — four tools in the first, eleven in the second.


    Tool permissions panel for the Dataverse connector, showing read-only and write/delete tools with per-tool allow, ask, and deny controls

    Tool permissions panel for the Dataverse connector, showing read-only and write/delete tools with per-tool allow, ask, and deny controls

    Each tool gets one of three settings: always allow, ask every time, or block outright. This is worth spending two minutes on rather than accepting the defaults, because it gives you a second layer of control that’s independent of Dataverse itself:

    • Dataverse security roles decide what the authenticated user can reach.
    • Tool permissions decide what the agent is allowed to do with that reach.

    A sensible starting posture: allow the read-only four (describe, read_query, search, plus file download) without prompting, set anything that creates or modifies to ask, and block the delete tools entirely unless you have a reason not to. delete_record and delete_table already require explicit approval server-side, but blocking them at the client means the question never gets asked in the first place.

    If you’re pointing an agent at anything resembling a production environment, do both: a stripped-down application user and a tight tool permission set. Neither substitutes for the other.


    Preview tools

    If you want the newest tools before they hit GA, enable Allow MCP clients to interact with Dataverse MCP server (Preview version) in the same Features panel, then point at /api/mcp_preview. With the local proxy, append --preview to the command.

    Preview tools aren't covered by support agreements and can change without notice. Fine for a lab, not for a client environment.

    📄 Preview tools in Dataverse MCP server


    Two things to know before you turn this on

    Billing. As of December 15, 2025, Dataverse MCP tool usage is metered when accessed by an AI agent built outside Copilot Studio — unless you hold qualifying Dynamics 365 Premium licenses or a Microsoft 365 Copilot USL. If you're experimenting in a trial, check where you land on that.

    Security. The MCP server respects Dataverse security roles and row-level security. The agent sees exactly what the authenticated user sees — no more. That's the right default, and it also means a stripped-down application user is the safest way to expose an environment to an agent.


    Why this is worth your afternoon

    The interesting part isn't querying data conversationally. It's that an agent can now build in Dataverse: create the tables, define the columns and relationships, populate realistic sample data, and iterate on the model in the same conversation where you're describing it. Work that used to be an afternoon of clicking through the maker portal is now a paragraph of English.

    The setup is roughly thirty minutes end to end, most of it in the admin center.

    📄 Connect to Dataverse with Model Context Protocol


    Continue reading...



  • D365 CRM: Use Selenium IDE to select controls in the Entity Form for Test Automation


    By default, when you are building an automated test process using the Selenium IDE, this program will select the controls by ID or CSS Class by default. But some pages have the little problem that the ID or the Class value change in every refresh/session so this will throw an exception in the test process. An example of this is the Model Driven form of D365 CRM. Let’s take the example to select the Save button of the ribbon:

     

    In the next refresh/session the Id will change like this:

     

    Notice how the Id of the button change from contact|NoRelationship|Form|Mscrm.SavePrimary01-button to contact|NoRelationship|Form|Mscrm.SavePrimary00-button. How to avoid it? Using the “xpath contains” selector instead of the id like this:

    xpath=//button[contains(@id, contact|NoRelationship|Form|Mscrm.SavePrimary')]

    The xpath will look for a button where the attribute id contains “contact|NoRelationship|Form|Mscrm.SavePrimary”. You can use other attributes instead of “id” like “class”, “title”, etc.

    Source:
    https://ui.vision/rpa/docs/selenium-ide/locators 

    Enjoy it!
     

    Continue reading...



  • D365 CRM: Generate PDF Document of Word Template using C#


    In this article I will explain how to generate a PDF document of a Word Template using the C# CRM API. It is very simple. First create or take an existing Word Template in CRM:

     

    Take the Word Template name and the entity logical name related. Here is the code:
    NOTE: To use this, you need to run the following Nuget to use the CRM API:
    Install-Package Microsoft.CrmSdk.XrmTooling.CoreAssembly

    1.    public byte[] GeneratePDFFromWordTemplate(Guid? wordTemplateId, string wordTemplateName, int? entityTypeCode, string entityName, Guid entityId)
    2.    {
    3.                // Get the Entity Type code if not known
    4.                if (entityTypeCode == null)
    5.                {
    6.                    entityTypeCode = GetObjectTypeCodeOfEntity(entityName);
    7.                }
    8.     
    9.                // Get the Word Template ID if not known
    10.                if (wordTemplateId == null)
    11.                {
    12.                    wordTemplateId = GetWordTemplateID(service, entityTypeCode, wordTemplateName);
    13.                }
    14.     
    15.                // Instance the Organization Request with the attributes to generate the PDF
    16.                OrganizationRequest exportPdfAction = new OrganizationRequest("ExportPdfDocument");
    17.     
    18.                exportPdfAction["EntityTypeCode"] = entityTypeCode;
    19.                exportPdfAction["SelectedTemplate"] = new EntityReference("documenttemplate", (Guid)wordTemplateId);
    20.                exportPdfAction["SelectedRecords"] = "[\'{" + entityId + "}\']";
    21.     
    22.                OrganizationResponse convertPdfResponse = (OrganizationResponse)service.Execute(exportPdfAction);
    23.     
    24.                return convertPdfResponse["PdfFile"] as byte[];
    25.    }

    This function will return a byte array of the PDF file generated. Here step by step:
    -First, if the entity type code is not specified by parameter, the code will query the entity type code of the entity related, for example “account”. This is because if you are using a custom entity, the type code will change in the different environments where your solution is imported. But if you are using a system entity like “account”, will be the same so is not necessary to call this function.
    -Then the code will query the Word Template ID, if this id is not specified by parameter, by the Template Name and Entity Type Code retrieved in the previous step (or taking the parameter if is specified).
    -The code will generate the Organization Request to CRM using “ExportPdfDocument” action request type and will fill its attributes with the Entity Type Code, the Word Template ID and the record ID.
    -Finally, the code executes the Organization Request returning the byte array of the PDF generated in the “PdfFile” attribute of the response. Then use this byte array to accomplish your objective like save the file in disk or generate a download in your web app.
    -If you need the code to retrieve the Word Template ID by name, here it is:

    1.    private Guid GetWordTemplateID(IOrganizationService service, int? entityTypeCode, string wordTemplateName)
    2.    {
    3.                QueryExpression query = new QueryExpression("documenttemplate");
    4.                query.ColumnSet.AddColumns("name", "associatedentitytypecode");
    5.                query.Criteria.AddCondition("name", ConditionOperator.Equal, wordTemplateName);
    6.                query.Criteria.AddCondition("associatedentitytypecode", ConditionOperator.Equal, (int)entityTypeCode);
    7.     
    8.                EntityCollection templates = service.RetrieveMultiple(query);
    9.     
    10.                if (templates.Entities.Count == 0)
    11.                {
    12.                    throw new Exception($"No template found with name {wordTemplateName}");
    13.                }
    14.                if (templates.Entities.Count > 1)
    15.                {
    16.                    throw new Exception($"More than one template found with name {wordTemplateName}");
    17.                }
    18.                return templates.Entities[0].Id;
    19.    }

    -And if you need the code to retrieve the Entity Type Code by the Entity Logical name, here it is:

    1.    public int GetObjectTypeCodeOfEntity(string entityName)
    2.    {
    3.                RetrieveEntityRequest retrieveEntityRequest = new RetrieveEntityRequest
    4.                {
    5.                    EntityFilters = EntityFilters.Entity,
    6.                    LogicalName = entityName
    7.                };
    8.     
    9.                RetrieveEntityResponse retrieveAccountEntityResponse = (RetrieveEntityResponse)service.Execute(retrieveEntityRequest);
    10.                EntityMetadata AccountEntity = retrieveAccountEntityResponse.EntityMetadata;
    11.     
    12.                return (int)retrieveAccountEntityResponse.EntityMetadata.ObjectTypeCode;
    13.    }

    Enjoy it!

    Continue reading...



  • D365 CRM: Hide Create New Record in Lookup Field


    By default the lookup field in a form will have the “+ New Record“ when you try to search for an existing one.

    To hide this option you need to:

    1. Create an Unmanaged solution in the environment.

    2. Add the entity with the form that contains the lookup.

    3. Export the solution.

    4. Extract the zip file.

    5. Open the customization.xml file to edit.

    6. Locate in the XML the lookup control.

    7. Inside the <control><pararemeters> node add the following:

      1. <IsInlineNewEnabled>false</IsInlineNewEnabled>

    8. Save the file.

    9. Generate the zip file.

    10. Import to the environment.

    11. Publish the Customizations.


    Continue reading...



  • D365 CRM: Formatting the Phone Number in US format using JavaScript


    If you need to format a phone number field in an Entity Form in CRM, use the following JavaScript code to accomplish this:

        function formatFieldPhoneNumber(executionContext, fieldName) {
            formContext = executionContext.getFormContext();
            var value = formContext.getAttribute(fieldName).getValue();
            var phoneFormat = formatPhoneNumber(value);
         
            formContext.getAttribute(fieldName).setValue(phoneFormat);
        }
         
        let formatPhoneNumber = (str) => {
            //Filter only numbers from the input
            let cleaned = ('' + str).replace(/\D/g, '');
         
            //Check if the input is of correct
            let match = cleaned.match(/^(1|)?(\d{3})(\d{3})(\d{4})$/);
         
            if (match) {
                //Remove the matched extension code
                //Change this to format for any country code.
                let intlCode = (match[1] ? '+1 ' : '')
                return [intlCode, '(', match[2], ') ', match[3], '-', match[4]].join('')
            }
         
             return null;
        }

    Simple add the function “formatFieldPhoneNumber” in the OnChange event of your phone attribute field in the Form passing the execution context as a first parameter and the field name (telephone1 for ex.) as a second parameter and try it.
     
    Enjoy it!

    Continue reading...



  • How to create a Web Job in Azure


    Hi, the purpose of this blog post is to show developers how to create a Web Job.
    -Go to Azure Portal. 
    -Search for Resource group. Create a Resource group if you don’t have any:

     
     
    -Search for APP service and create an app service 
    -Give any name you want to the instance:
     
      
     
    -Select the publish code, or if you need any docker, select Runtime stack (either core or asp.net frame work or tomcat). Go to Monitoring tab if you want to enable Application insights. By default, it will be enabled.
     
    -Click on Review + Create and click on Create.
    -Go to Azure portal -> Resource groups -> Resource (which you have created) -> WebJob App Service. Click on “Get Publish profile” and download and import it into Visual studio to publish the web job.
    -Go to Azure portal and search for Storage accounts and click on it. 
    -Click on Add. 
    -Click on Review +Create.
     
    -Go to Storage account, click on the Storage account previously created. Click on Access keys and copy the connection string.
     
      
     
    Now go to Visual Studio to create a Console APP
     
    File->New->Project
     
      
     
    Right click on solution -> click add -> New Item -> select JSON type and name it Settings.job
     
      
     
    Open the “Settings.job” and add the following CRON expression:
     
    {
    "schedule": "0 0 */4 * * *"
    }
     
    Note: The basic format of the CRON expressions in Azure is:
     
    {second} {minute} {hour} {day} {month} {day of the week}

    Here more documentation about this:
    https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer?tabs=in-process&pivots=programming-language-csharp#ncrontab-expressions 
     
    Go to App.config and add the below line in the connectionstring tag
     
    <add name="AzureWebJobsDashboard" connectionString= ”<connection string copied from storageaccount> " />
    <add name="AzureWebJobsStorage" connectionString = ”<connection string copied from storageaccount>" />  
     
    Click on "Microsoft Azure App service" or Import the profile which we downloaded in the earlier steps.   
     
    Below, the screen should appear. Go to the settings tab, set configuration to “Release,” and publish.
     
    Wait for the message below to appear in the output window of Visual Studio to assure that the deployment was successful.
     
    Go to the Azure Portal on the below screen and click on WebJob. You should be able to find your WebJob here.
     
    Original Source:
    https://www.c-sharpcorner.com/blogs/how-to-create-a-webjob-in-azure-using-visual-studio

    Official Documentation:
    https://docs.microsoft.com/en-us/azure/app-service/webjobs-dotnet-deploy-vs

    Enjoy it!


    Continue reading...



  • D365 CRM: How to access in Canvas APP the form fields of an Entity record


    By default, when you create a Canvas App and try to access the form fields of an entity record, these fields will not be present in the designer:

     

    To do this you need to add first the Canvas APP in the form. Copy your Canvas APP Id in the Details section:

     

    And paste it in the form editor:


     
    The Entity name field will auto populate the entity logical name of the form, in this case Contact.

    Then click the Customize button. This will be open a new window to edit the Canvas APP but now with a new component: ModelDrivenFormIntegration.
     
    Then select the control you need to use to show the field value, for example fullname. In the Text function put “[@ModelDrivenFormIntegration].Item.'Full Name'”. But this will throw an error because we need to specify to ModelDrivenFormIntegration the DataSource. To do this add the Entity in the Data section:

     

    And map that in the DataSource property of the ModelDrivenFormIntegration.
     
    And now you will see the values correctly:

     

    Save and publish the Canvas APP.
    Save and publish the form.
     
    Enjoy it!

    Continue reading...



  • Edit Form, our new Product


    In the previous versions of D365/Dynamics CRM you could access in the Form Editor clicking this button in the ribbon, and Microsoft removes/hides this button.

    Now with our product Edit Form you can recover this button in the new versions.

    With the Edit form solution you can easily edit the form of the table you are working on, click on Edit Form and select the modern or classic option.

    This solution helps you edit the form without having to go and find the solution where the form is installed.

    NOTE: you need to be System Administrator or System Customizer to be able to see the Edit Form button.

    Check our product section to download Edit Form FREE and install it in your D365 environment.
     


    Continue reading...



  • D365 CRM: Call Azure Function from Dynamics CRM using Plugin Webhook


    D365 CRM has the feature to call an Azure Function app as a plugin step triggered in a target event of an entity record, for example in the create of an account record. To archive this you need to develop in Visual Studio a project of type Function App an create a Http Trigger Get function in this project with the following code to read the request of CRM:
     


    This simple code will get the request body as a string that you can parse in a JSON object. You can use RemoteExecutionContext class to actually get all the contextual information into the Function app and then use it further.
    Once ready your code, Publish in Azure.
    Open Plugin Registration Tool to register the new Webhook:
     


    Enter the Webhook details. Select Authentication type as WebhookKey:

     

    To get the URL and the key, go to the Function App deployed in portal and look </> Get function URL to copy the function URL:

     

    The key will be the value after code= in the URL. Paste it in the Webhook and Save.
     
    Then create a New Step inside of the Webhook in a target event of the entity (for example in the Create of account) and test it.
     
    You can check the log of the function to see the request body that CRM sent to the Function App:
     
    And with this, you can call Azure Function using Plugins with D365 CRM.

    Enjoy it!

    Continue reading...



  • D365 CRM: How to get the connection string of your CRM Organization stored in an Azure Key Vault


    It is a good practice for security purpose to store in an Azure Key Vault your credentials and/or connections to the different system such as the D365 CRM connection string. Here I will share a portion of code to retrieve a Secret stored in an Azure Key Vault, for example a Connection String to use in the CRM API.
    Important Note: You will need for this a Client ID and a Secret Key generated to authenticate to the Key Vault.
    -First of all, create in your Azure Key Vault a Secret that contains your connection string of CRM.

     

    -Take a note of your Client ID, Secret Key, Vault URI, and the Secret Name created in the previous step.
    -Use the following code to get the Secret value of the Key Vault:

     

    This will return the secret value stored in that Key Vault Secret, in this case the connection string to connect to the CRM API.

    Enjoy it!
     

    Continue reading...