Skip to main content
Glama
xrm-mcp

xrm-mcp

by jukkan

xrm-mcp

A minimal MCP server that gives AI coding agents (Claude Code, GitHub Copilot, Codex CLI, Cursor, Gemini CLI) clean read + careful write access to Microsoft Dataverse / XRM environments via the Dataverse Web API v9.2.

xrm-mcp-300px

Why this exists: Microsoft ships its own Dataverse MCP server, but it requires Power Platform admin setup, Managed Environments, and Copilot Credits per call. xrm-mcp is a drop-in alternative that works with any Dataverse environment using your existing Azure CLI or Microsoft account. Read more →

Features

  • No Microsoft MCP billing — calls the Dataverse Web API directly, no Copilot Credits consumed

  • No admin toggles — works with any Dataverse environment you can log into

  • No Managed Environment required — standard environments work fine

  • No per-environment setup — org_url is a parameter on every tool call

  • Multi-tenant by design — connect to multiple orgs in the same session, with per-environment identity caching so switching between tenants doesn't require re-authenticating each time

  • Azure CLI + MSAL auth — tries az first, falls back to interactive device flow

  • 8 MCP tools — ping, find/list tables, describe schema, query, create, update, upsert

Related MCP server: dataverse-mcp-server

Installation

For uv, pipx, virtual environments, pinned tags, and client setup, see the installation guide.

Install via pipx (recommended):

pipx install git+https://github.com/jukkan/xrm-mcp.git

Or via pip:

pip install git+https://github.com/jukkan/xrm-mcp.git

Usage

Configuring your AI agent

Add xrm-mcp to your agent's MCP configuration.

Claude Desktop / Claude Code (~/.claude/claude_desktop_config.json or .mcp.json):

{
  "mcpServers": {
    "xrm-mcp": {
      "command": "xrm-mcp"
    }
  }
}

GitHub Copilot (VS Code) — add to .vscode/mcp.json in your workspace:

{
  "servers": {
    "xrm-mcp": {
      "type": "stdio",
      "command": "xrm-mcp"
    }
  }
}

Once configured, pass your environment URL with every request and the agent takes it from there:

"Show me all project records from https://myorg.crm4.dynamics.com created in the last 30 days"

Running the server manually

xrm-mcp

The server uses FastMCP and communicates over stdin/stdout (stdio transport).

Authentication

XRM MCP attempts authentication in the following order:

  1. Azure CLI — if az is available and logged in

  2. MSAL device flow — interactive browser-based login

Tokens are cached at ~/.xrm-mcp/cache.json.

If you work across multiple tenants (e.g. your own production environment, demo environments, and customer tenants), XRM MCP remembers which identity last worked for each org_url in ~/.xrm-mcp/identity_cache.json and prefers it on the next call — so switching tenants doesn't depend on which az account happens to be active. If a call gets a 401/403, that org's cached identity is cleared automatically and the error message tells you to just retry, which re-discovers a working identity.

To re-authenticate, delete ~/.xrm-mcp/cache.json or use az login. To reset which identity is used for a specific org, delete its entry from ~/.xrm-mcp/identity_cache.json.

Testing authentication manually

python -m xrm_mcp.auth https://yourorg.crm4.dynamics.com

MCP Tools

Read Tools

ping(org_url)

  • Verify connectivity and authentication to a Dataverse environment

  • Returns: status, org_url, user_id, business_unit_id, org_id, auth_method, tenant_id

  • Call this first when connecting to a new environment; auth_method/tenant_id let you confirm which identity answered the call

find_table(org_url, name)

  • Search for a table by display name or partial logical name

  • Use when the user says "hour entries" and you need the exact logical name

  • Returns all matching tables sorted by exact display name match first

list_tables(org_url, search="", custom_only=True, prefix="", exclude_ms_prefixes=True)

  • List Dataverse tables, defaulting to custom entities only

  • custom_only=True — only return custom entities (default)

  • prefix="na_" — filter to a specific publisher prefix

  • exclude_ms_prefixes=False — include Microsoft solution tables (msdyn_, adx_, etc.)

  • Returns: logical_name, display_name, entity_set_name, is_custom, description

describe_table(org_url, table)

  • Get columns, types and descriptions for a Dataverse table

  • Call this before querying when you need exact column names for $select or $filter

  • Returns: table_name, columns with metadata

query_records(org_url, table, select="", filter="", top=100, orderby="")

  • Query records from a table using OData filter syntax

  • Returns: {count, records}

  • Top is capped at 5000

  • select is validated against real column names before querying; an invalid column raises a clear error naming it, instead of silently returning every column

Write Tools

create_record(org_url, table, data)

  • Create a single record

  • Returns: {id}

update_record(org_url, table, record_id, data)

  • Update specific fields on an existing record

  • Returns: {success, id}

upsert_record(org_url, table, alternate_key, alternate_value, data)

  • Create or update a record using an alternate key (for sync/import scenarios)

  • Returns: {success, alternate_key, alternate_value}

Example Usage

See CLAUDE.md for detailed agent usage examples and WHY.md for how this compares to Microsoft's own Dataverse MCP server.

Requirements

  • Python 3.10+

  • Azure CLI (optional, for az authentication)

  • Access to a Dataverse / Dynamics 365 environment

Dependencies

  • fastmcp >= 3.0, < 4

  • msal >= 1.28.0, < 2

  • httpx >= 0.27.0, < 1

Version 0.2.0 moved from FastMCP 2.x to FastMCP 3.x. Existing installs need to be reinstalled (for example pipx reinstall xrm-mcp or uv tool install --force ...) so the new FastMCP version is resolved.

Development

Clone the repository:

git clone https://github.com/jukkan/xrm-mcp.git
cd xrm-mcp

Install in development mode:

pip install -e ".[dev]"

Run the tests (no Dataverse environment or credentials required):

pytest

License

MIT

Contributing

Contributions welcome! Please open an issue or pull request.

Available Tools

8 tools
create_recordC

Create a single record in an XRM table.

Inputs: data (required), table (required), org_url (required). Returns: object

describe_tableA

Get columns, types and descriptions for an XRM table.

Call this before querying when you need to know column names for $select or $filter.

Inputs: table (required), org_url (required). Returns: object

find_tableA

Search for a table by display name or partial logical name.

Use this when the user gives a friendly name like 'hour entry' or 'hours' and you don't know the exact logical name. Returns all candidate matches from this specific environment — do not use workspace files or project notes to infer table names.

Inputs: name (required), org_url (required). Returns: result

list_tablesA

Lists Dataverse tables. Defaults to custom tables only, excluding Microsoft-prefixed solution tables (msdyn_, msfp_, adx_ etc).

Use prefix='na_' to filter to a specific publisher. Use exclude_ms_prefixes=False to see all custom tables.

Inputs: org_url (required), prefix, search, custom_only, +1 more. Returns: result

pingA

Test connectivity to an XRM/Dataverse environment and verify authentication.

Call this first to verify your setup and connection to the Dataverse environment.

Inputs: org_url (required). Returns: object

query_recordsA

Query records from an XRM/Dataverse table.

Inputs: table (required), org_url (required), top, filter, +2 more. Returns: object

update_recordB

Update fields on a single existing XRM record.

Inputs: data (required), table (required), org_url (required), record_id (required). Returns: object

upsert_recordA

Create or update a record matched by an alternate key column.

Use this for sync/import scenarios where you don't have the record GUID.

Inputs: data (required), table (required), org_url (required), alternate_key (required), +1 more. Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.7/5.0

    Scored across 8 tools

    Disambiguation5/5

    Each tool targets a distinct operation (CRUD, metadata queries, connectivity test) with no functional overlap, making it easy for an agent to select the correct one.

    Naming Consistency4/5

    Most tools follow a clear verb_noun pattern (create_record, query_records, etc.), but ping uses a single verb, which is a minor inconsistency.

    Tool Count5/5

    The tool count of 8 is well-scoped for a Dataverse MCP server, covering essential operations without unnecessary bloat.

    Completeness3/5

    The tool set covers creation, update, query, and metadata, but lacks a delete_record tool and a direct get-by-ID tool, which may cause agent failures in typical workflows.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      B
      maintenance
      MCP server for Microsoft Dataverse API with safe-by-default configuration. Works with any Dataverse / Dynamics 365 environment.
      23
      228 npm
      8
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      An MCP server for OData v4 endpoints, especially Microsoft Dataverse/Dynamics 365, enabling authentication, schema discovery, querying, CRUD, and more via natural language.
      MIT