Skip to content

Safety and Permissions

Ryan James edited this page Aug 17, 2026 · 1 revision

Four server-side flags decide what an agent connected to this server can do. They are the guardrail between a prompt-injected or simply confused agent and your data: with the defaults, there is no tool it can call that deletes a record, drops a column, or overwrites a file on your disk.

They are not a substitute for Dataverse security roles — both apply, and the stricter wins.

Flag Default Stops
DATAVERSE_ALLOW_WRITE off Creating, updating, publishing, and all schema/solution/plug-in mutation
DATAVERSE_ALLOW_DELETE off Deleting records, tables, columns, relationships, components
DATAVERSE_WHITELIST (empty — allows all) Tool calls aimed at an environment you did not approve
DATAVERSE_FILE_BASE_DIR (unset — allows all) Solution export/import reading or writing outside one directory

Write and delete

The gates work by registration, not refusal. A gated tool is defined in the code but never exposed over MCP, so the agent cannot see it, cannot call it, and cannot be talked into calling it. The tool list itself is the enforcement.

Configuration Tools registered
Neither flag 97
DATAVERSE_ALLOW_WRITE=true 177
Both flags 200

Only the exact string true (any case) enables a flag — 1 and yes silently mean off.

Two behaviours do not follow the pattern:

  • dataverse_execute_batch is always registered so that read-only batch queries stay available. It inspects the operations it is given and refuses at call time: Batch operations containing POST/PUT/PATCH methods require DATAVERSE_ALLOW_WRITE=true in the MCP server environment., and the equivalent for DELETE.
  • dataverse_export_solution needs no write flag. It does not mutate the org — but with output_path it does write a .zip to the server's filesystem. That is what DATAVERSE_FILE_BASE_DIR is for.

Environment whitelist

Every tool call carries its own dataverse_url, and the server mints a bearer token for whatever host it is given. Without a whitelist, an agent that has been fed a hostile instruction can point your credentials at any Dataverse environment in the world.

"DATAVERSE_WHITELIST": "yourorg.crm.dynamics.com,yourorg-uat.crm.dynamics.com"

Comma-separated hostnames. A scheme or trailing path is tolerated and stripped; hosts are canonicalised (trailing dot removed, IDNA-encoded, lowercased) on both sides before comparison, so Unicode and punycode spellings of the same host match. Invalid entries are skipped with a warning rather than failing startup. A rejected call returns:

dataverse_url host 'other.crm.dynamics.com' is not in the configured DATAVERSE_WHITELIST. Add it to DATAVERSE_WHITELIST to permit access to this environment.

Independently of the whitelist, every dataverse_url must be https, carry no credentials, path, query or fragment, and use no port other than 443 — a non-standard port is rejected outright so it cannot be used to sidestep host matching.

Fail closed

DATAVERSE_REQUIRE_WHITELIST=true rejects every tool call while DATAVERSE_WHITELIST is empty, instead of falling back to "allow everything":

DATAVERSE_REQUIRE_WHITELIST is set but DATAVERSE_WHITELIST is empty; refusing to mint a token for an unapproved host. Populate DATAVERSE_WHITELIST with the allowed environment hostname(s).

Set it wherever a config mistake could otherwise open the server to every environment — shared hosts, multi-tenant deployments, anything not your own laptop. The startup log makes the state obvious either way: the allowed hosts are listed when the whitelist is set, an ERROR is logged when it is required but empty, and a WARNING when it is neither.

dataverse_list_environments is not covered. It takes no dataverse_url — it queries the Power Platform admin API for the environments you can administer — so the whitelist does not apply to it. It reads metadata only, no Dataverse data. Drop the core category if that matters, but note that core cannot be disabled via DATAVERSE_TOOLS.

File path confinement

DATAVERSE_FILE_BASE_DIR confines the output_path and input_path arguments on dataverse_export_solution, dataverse_import_solution and dataverse_stage_and_upgrade_solution. Paths are resolved (~ expanded, symlinks and .. collapsed) and must land inside the directory:

"DATAVERSE_FILE_BASE_DIR": "C:\\dataverse-solutions"

Anything outside it — .. traversal or an absolute path elsewhere — is rejected with Path is outside the permitted DATAVERSE_FILE_BASE_DIR directory (<base>); refusing to access '<path>'. Unset, these paths are unrestricted, which means an agent can be steered into overwriting a startup script or reading an SSH key.

Two profiles to paste

Read-only against production

Nothing can be changed and nothing can leave the org. The whitelist is required rather than merely set, so a later edit that empties it fails closed instead of opening up.

{
  "mcpServers": {
    "dataverse-prod": {
      "command": "uvx",
      "args": ["dataverse-mcp"],
      "env": {
        "DATAVERSE_AUTH_TYPE": "interactive",
        "DATAVERSE_WHITELIST": "yourorg.crm.dynamics.com",
        "DATAVERSE_REQUIRE_WHITELIST": "true",
        "DATAVERSE_TOKEN_CACHE_PROFILE": "prod"
      }
    }
  }
}

Omitting DATAVERSE_ALLOW_WRITE and DATAVERSE_ALLOW_DELETE is the point — do not set them to "false" and assume that is stronger; absent and "false" are identical here.

Full access against a dev sandbox

{
  "mcpServers": {
    "dataverse-dev": {
      "command": "uvx",
      "args": ["dataverse-mcp"],
      "env": {
        "DATAVERSE_AUTH_TYPE": "interactive",
        "DATAVERSE_ALLOW_WRITE": "true",
        "DATAVERSE_ALLOW_DELETE": "true",
        "DATAVERSE_WHITELIST": "yourorg-dev.crm.dynamics.com",
        "DATAVERSE_REQUIRE_WHITELIST": "true",
        "DATAVERSE_FILE_BASE_DIR": "C:\\dataverse-solutions",
        "DATAVERSE_TOKEN_CACHE_PROFILE": "dev"
      }
    }
  }
}

The whitelist matters more here, not less: this is the entry that can delete things, so it must not be able to reach production. Run both entries side by side and the agent picks the right one by name.

Deletes in Dataverse are permanent and cascade — dataverse_delete_table takes the table's data with it. Enable DATAVERSE_ALLOW_DELETE where you would be comfortable letting a colleague run the same command unsupervised.

Clone this wiki locally