How We Connected Dataverse and Business Central to Claude via MCP

For those of us who live and breathe Business Central, the idea of having Claude as an AI layer directly on top of our ERP data is not a dream anymore. We already have a working MCP bridge connecting Claude Desktop to Business Central through OData v4, and the team uses it every day. The natural next step was to replicate that success with Dataverse and Dynamics 365 CRM, so Claude could answer questions pulling from both systems at once.

What followed was one of the most frustrating and instructive days I have had in a while. I want to share the whole journey because I think it is worth documenting honestly, not just the final setup.


The Plan

Microsoft released an official npm package called @microsoft/dataverse that acts as an MCP server for Dataverse environments. The configuration looked simple enough: add it to your claude_desktop_config.json, point it to your org URL, and in theory Claude Desktop would discover all available tools automatically.

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

We had already done the groundwork on the Power Platform side: enabled Managed Environment, turned on both GA and Preview MCP toggles in PPAC, registered Claude Desktop as an Allowed MCP Client, and granted admin consent for the Dataverse CLI app ID. Everything looked right on paper.


What Happened

Claude Desktop showed the Dataverse server as running, which was promising. But under Connectors, it said “This connector has no tools available.” Zero tools. Every time.

The logs told a more precise story:

[info] Server started and connected successfully
[info] Message from client: {"method":"tools/list"}
[info] Message from client: {"method":"notifications/cancelled",
        "params":{"reason":"McpError: MCP error -32001: Request timed out"}}

The initialize handshake completed fine in about 7 seconds. But tools/list never got a response. Claude Desktop cancelled it after exactly 30 seconds, every single time.

We tried everything: different configurations, environment variables, clearing the MSAL auth cache, enabling Managed Environment (which we had not done initially), waiting hours for propagation. Nothing changed the outcome.


The Breakthrough: MCP Inspector

Before giving up entirely, we reached for the MCP Inspector, a visual debugging tool that lets you connect to any MCP server and fire requests manually. This was the most useful step of the entire day.

npx @modelcontextprotocol/inspector npx @microsoft/dataverse mcp https://your-org.crm.dynamics.com

Opening http://localhost:6274, we connected successfully. When we clicked List Tools, the inspector returned:

{ "error": "MCP error -32603: An error occurred." }

This was the real error. Not a timeout. Not a configuration problem on our end. The remote Dataverse MCP endpoint was returning an internal server error. Our colleague connected with Claude Code from a different machine and got all 11 tools working perfectly. Same environment, same configuration.


Understanding the Root Cause

After digging through the logs in detail, we found two issues specific to the official Microsoft server, and one universal requirement that applies regardless of which server you use.

Why the Official Server Fails on Claude Desktop

Hardcoded timeout. Claude Desktop has a 30-second timeout for MCP operations baked into the compiled application. The timeout field in claude_desktop_config.json is silently ignored. Claude Code, on the other hand, respects a configurable timeout. That is why our colleague succeeded with Claude Code and we kept failing with Claude Desktop. The official @microsoft/dataverse server needs more than 30 seconds to complete the tools/list response on first launch, so Claude Desktop cancels the request every time.

stdout pollution. npm writes warning messages to stdout. In a stdio MCP transport, stdout is the JSON-RPC communication channel. When npm outputs a warning like warn: Mode..., Claude Desktop’s JSON parser reads it, fails to parse it as valid JSON, and the connection breaks. This appears in the logs as:

Unexpected token 'w', "warn: Mode"... is not valid JSON

These two issues are specific to the @microsoft/dataverse package running under Claude Desktop. A community server that responds quickly and runs with node directly (instead of npx) avoids both problems entirely.

The Universal Requirement: Application User in Dataverse

This one applies no matter which MCP server you choose. If you authenticate with client credentials against Dataverse, you must have an Application User created in Power Platform Admin Center. Without it, Azure AD will authenticate your service principal successfully, but Dataverse will reject every request with “The user is not a member of the organization.”

The App Registration can exist in Azure AD, the permissions can be granted, admin consent can be in place, but none of that matters until Dataverse itself recognizes the service principal as a user in the environment. The fix is creating an Application User in Power Platform Admin Center → Settings → Users + Permissions → Application Users, linking it to your App Registration, and assigning it a Security Role. This was the single most important thing we did all day, and the step most likely to be missing from other guides.


The Solution: codeurali/mcp-dataverse

The community-built server codeurali/mcp-dataverse solved the timeout and stdout problems at once. It uses Client Credentials (service principal) instead of interactive OAuth, so there is no browser popup, no auth latency, and no stdout pollution. It responds to tools/list in under 2 seconds, well within any timeout window.


Architecture Overview

Before diving into the installation steps, here is how the dual MCP setup fits together:

Claude Desktop
  ├── BusinessCentral MCP → OData v4 → BC API (Client Credentials)
  └── Dataverse MCP       → Web API  → Dataverse (Client Credentials)

Both servers run locally as Node/Python processes launched by Claude Desktop. Each authenticates independently with its own service principal, and Claude sees the combined toolset in a single conversation.


Step-by-Step Setup

Step 1: App Registration in Azure AD

Go to portal.azure.com → Azure Active Directory → App Registrations → New registration. Name it something descriptive like “Dataverse MCP Server”, select Single tenant under Supported account types, and click Register.

Once the registration is created, navigate to API Permissions and add the following:

API Permission Type
Dynamics CRM user_impersonation Delegated
Dynamics CRM mcp.tools Delegated
Microsoft Graph User.Read Delegated

Click “Grant admin consent for [your tenant]” to authorize these permissions at the tenant level.

Then go to Certificates & Secrets → New client secret. Use a description like “Dataverse MCP” and set the expiration to 24 months. Copy the Value immediately after creation because it is only shown once. If you navigate away without copying it, you will need to create a new one.


Step 2: Enable MCP in Power Platform Admin Center

Go to admin.powerplatform.microsoft.com → Environments → your environment → Settings → Product → Features. Look for the toggle labeled “Dataverse Model Context Protocol” and enable it. Note that the environment must have Managed Environment enabled for this toggle to appear. If you do not see it, go to the environment overview and enable Managed Environment first, then return to Features.


Step 3: Create Application User in Dataverse

This is the step that cost us the most time, and the one most likely to be missed in other guides.

Go to Power Platform Admin Center → Environments → your environment → Settings → Users + permissions → Application users. Click “+ New app user”. Search for your App Registration by name or Client ID, select the correct Business Unit, and assign the System Administrator Security Role. Click Create.

Without this step, the error will be “The user is not a member of the organization” even though Azure AD has everything configured correctly. Azure AD authenticates the service principal, but Dataverse does not recognize it as a valid user in the environment until the Application User exists.


Installation

The server is distributed exclusively via npm (the GitHub repository does not include source code), so installation is straightforward. From PowerShell:

mkdir "$env:USERPROFILE\mcp-dataverse" -Force
Set-Location "$env:USERPROFILE\mcp-dataverse"
npm init -y
npm install mcp-dataverse

Then create a config.json file in that same folder with your Dataverse credentials:

{
  "environmentUrl": "https://your-org.crm.dynamics.com",
  "authMethod": "client-credentials",
  "tenantId": "<your-tenant-id>",
  "clientId": "<your-app-client-id>",
  "clientSecret": "<your-client-secret>"
}

Important: do not use PowerShell’s Out-File or the > redirection operator to write this file. Both generate UTF-8 with BOM (Byte Order Mark), which inserts invisible characters at the beginning of the JSON that break the parser. Use [System.IO.File]::WriteAllText() with an explicit UTF-8 encoding without BOM instead:

[System.IO.File]::WriteAllText(
  "$env:USERPROFILE\mcp-dataverse\config.json",
  '{
  "environmentUrl": "https://your-org.crm.dynamics.com",
  "authMethod": "client-credentials",
  "tenantId": "<your-tenant-id>",
  "clientId": "<your-app-client-id>",
  "clientSecret": "<your-client-secret>"
}',
  [System.Text.UTF8Encoding]::new($false)
)

Claude Desktop Configuration

Open your Claude Desktop config:

notepad "$env:APPDATA\Claude\claude_desktop_config.json"

Add the Dataverse entry alongside your existing Business Central server. The key detail is that codeurali/mcp-dataverse reads its credentials from a config.json file pointed to by the MCP_CONFIG_PATH environment variable:

{
  "mcpServers": {
    "BusinessCentral": {
      "command": "python",
      "args": [
        "-m", "bc_mcp_proxy",
        "--TenantId", "<your-tenant-id>",
        "--ClientId", "<your-bc-client-id>",
        "--Environment", "Production",
        "--Company", "Your Company Name",
        "--ConfigurationName", "ClaudeIntegration"
      ]
    },
    "Dataverse": {
      "command": "node",
      "args": [
        "C:\\Users\\you\\mcp-dataverse\\node_modules\\mcp-dataverse\\dist\\server.js"
      ],
      "env": {
        "MCP_CONFIG_PATH": "C:\\Users\\you\\mcp-dataverse\\config.json"
      }
    }
  }
}

Validate the JSON before saving:

Get-Content "$env:APPDATA\Claude\claude_desktop_config.json" | ConvertFrom-Json

Then do a full exit from Claude Desktop (File → Exit, not just closing the window) and reopen it.


73 Tools Instead of 11

One thing that surprised me positively about codeurali/mcp-dataverse is the scope of what it exposes. The official Microsoft server gives you 11 data-focused tools. This community server gives you 73, covering the full administrative and schema layer of Dataverse:

Tables, columns, relationships, option sets, security roles and their privileges, teams and users, business units and their hierarchies, solutions and publishers, and even utilities like generate_mermaid_diagram (generates entity relationship diagrams from your schema) and generate_webapi_call (produces ready-to-use HTTP calls, curl commands, and JavaScript for the Dataverse Web API).

For someone working on implementations, this is enormously useful.


The Dual MCP Setup in Practice

For those of us who work with Business Central alongside Dynamics 365, having both MCP servers running simultaneously changes what kinds of questions you can ask Claude in a single conversation. Instead of opening two systems, exporting to Excel, and cross-referencing manually, you can ask things like:

“Which opportunities in CRM were awarded this quarter and do we have open invoices in Business Central for those customers?”

“The estimate in Dataverse shows a 42% gross margin for this project. What are the actual costs posted in BC so far?”

“Who are our top accounts by CRM pipeline value versus accounts receivable balance in BC?”

“Show me the security roles in this Dataverse environment and which ones have write access to the Opportunity table.”

These are questions that previously required two open browser tabs and a spreadsheet. Now they are a single prompt.


Troubleshooting

Error Cause Fix
Server disconnected The path to server.js in claude_desktop_config.json is wrong or the file does not exist. Verify that node_modules\mcp-dataverse\dist\server.js exists at the path you configured. Run npm install mcp-dataverse again if needed.
Invalid JSON in config.json The file was created with PowerShell’s Out-File or >, which adds a UTF-8 BOM (invisible bytes at the start). Recreate the file using [System.IO.File]::WriteAllText() with [System.Text.UTF8Encoding]::new($false) to write UTF-8 without BOM.
The user is not a member of the organization The service principal authenticates via Azure AD but has no Application User in Dataverse. Create an Application User in Power Platform Admin Center → Settings → Users + Permissions → Application Users and assign a Security Role.
environmentUrl: Required The config.json is missing the environmentUrl field, or MCP_CONFIG_PATH does not point to the correct file. Check that MCP_CONFIG_PATH in claude_desktop_config.json matches the actual path to your config.json, and that the file contains environmentUrl.
Authentication fails after weeks of working The client secret has expired. Go to Azure AD → App Registrations → Certificates & Secrets, create a new secret, and update config.json with the new value.

Security Considerations

A few recommendations worth following from day one. Rotate your client secret every 12 months, even if Azure lets you set a 24-month expiration; this limits exposure if the credential leaks. Assign the minimum Security Role required for your use case instead of defaulting to System Administrator in production environments. Never commit config.json to a Git repository because it contains your client secret in plain text; add it to your .gitignore immediately. And if you are sharing the setup script with your team, distribute the config.json template with placeholder values and let each person fill in their own credentials locally.


What I Would Tell Microsoft

We opened a support case asking specifically whether Claude Desktop is an officially supported MCP client, whether there is a configuration that reduces tools/list latency below 30 seconds for non-Microsoft clients, and whether a Client Credentials flow is planned as an alternative to the current interactive OAuth requirement. I will share whatever comes back.

For now, if you are a Business Central or Dataverse developer who wants to use Claude Desktop as your AI interface, codeurali/mcp-dataverse is the practical path forward. It is stable, fast, and does not require Copilot licenses or consume Copilot Credits.

The Business Central community has always thrived on shared knowledge. This one took a full day to figure out, so hopefully this post saves you that time.

One last thing: if anyone out there has managed to get the official @microsoft/dataverse npm package working with Claude Desktop on Windows, specifically getting tools/list to respond before the 30-second timeout, I would genuinely love to know how. Drop a comment below or reach out directly. There is a cold drink in it for whoever cracks that one. 😄

Leave a Reply

Your email address will not be published. Required fields are marked *