Skip to content
Back to skills

Dataverse Connect

CSecurity

One-step, idempotent setup and connection diagnostics for a Microsoft Dataverse environment: installs tools, authenticates the Dataverse CLI and PAC CLI, writes `.env`, copies the Python auth helper into the project, registers the Dataverse MCP server, and verifies the connection. USE FOR: connect to Dataverse, set up a new Dataverse project, switch environment, fix Dataverse authentication, dataverse auth create, pac auth create, register Dataverse MCP server, MCP not working, allowlist MCP ...

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 5, 2026
ai-agentspythonbashnodeazuregitapici/cdsecurity

Works with

  • claude code
  • vscode
  • terminal
  • cli
  • api
  • mcp

Security analysis

C72/100
  • mediumUses curl or wget to download content
  • criticalExfiltrates credentials via HTTP — exact pattern from Snyk ToxicSkills study
  • mediumInstalls packages at runtime which could introduce malicious dependencies
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 7 files and shows the line behind each finding

Scanned October 5, 2026

npx -y skills add atc-net/atc-agentic-toolkit --skill dataverse-connect --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dataverse Connect?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Dataverse Connect
[![Security: C — Skills Directory](https://www.skillsdirectory.com/api/skills/atc-net-dataverse-connect/badge)](https://www.skillsdirectory.com/skills/atc-net-dataverse-connect)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: dataverse-connect
description: >
  One-step, idempotent setup and connection diagnostics for a Microsoft Dataverse environment:
  installs tools, authenticates the Dataverse CLI and PAC CLI, writes `.env`, copies the Python
  auth helper into the project, registers the Dataverse MCP server, and verifies the connection.
  USE FOR: connect to Dataverse, set up a new Dataverse project, switch environment, fix Dataverse
  authentication, dataverse auth create, pac auth create, register Dataverse MCP server, MCP not
  working, allowlist MCP client, dataverse mcp allow, scripts/auth.py --check, privilege preflight.
  DO NOT USE FOR: creating tables or columns (use dataverse-metadata), reading or writing records
  (use dataverse-data), querying data (use dataverse-query), solution export or import
  (use dataverse-solution), environment settings (use dataverse-admin).
---

# Dataverse Connect

One-step, idempotent connection to a Microsoft Dataverse environment. Every step checks whether it
is already done and skips if so.

> **Environment-first rule** — metadata and plug-in registrations are created **in the environment**
> via API or scripts, then pulled into the repo. Never hand-write solution XML to create components.

Execute the steps in order. Step 0 may short-circuit the flow when the workspace is already set up.

## When to use

- Starting a new Dataverse project or onboarding an existing repo
- Switching to another environment or tenant
- Authentication prompts, expired caches, or `NOT REACHABLE` results
- The Dataverse MCP server is missing, not connected, or returns 403

## Bundled files

This skill ships the following files in its own base directory. They are copied into the user's
project in Step 4. `<skill-base-dir>` below means the directory that contains this `SKILL.md`
(the host announces it when the skill loads; otherwise locate it, e.g. under the installed
`dataverse` plugin's `skills/dataverse-connect/` folder).

| File | Purpose |
|---|---|
| `scripts/auth.py` | Credential chain + `get_client()` / `get_token()` for Python scripts; CLI flags `--check`, `--ping`, `--diagnose`, `--bootstrap URL` |
| `scripts/requirements.txt` | Python dependencies (Dataverse SDK, `azure-identity`, `msal`, `msal-extensions`, `requests`, `pandas`) |
| `scripts/enable_mcp_client.py` | Programmatic MCP client allowlisting (fallback, see [mcp-configuration.md](references/mcp-configuration.md#7-allowlist-the-mcp-client-in-the-environment)) |
| `assets/CLAUDE.md.template` | Project-level agent instructions with `{{DATAVERSE_URL}}`, `{{SOLUTION_NAME}}`, `{{PUBLISHER_PREFIX}}` placeholders |

---

## Step 0: Detect existing setup

Re-running setup on a configured workspace overwrites `.env`, re-registers MCP, and wastes time.
Run these checks first. If **all four pass**, skip to Step 7.

1. **`.env` is complete** — exists at the workspace root with non-empty `DATAVERSE_URL`, `TENANT_ID`,
   and `MCP_CLIENT_ID`.
2. **MCP is registered** — the host's MCP list has a `dataverse-*` (Claude Code) or
   `DataverseMcp*` (GitHub Copilot) entry pointing at `DATAVERSE_URL`.
3. **Both auth surfaces match `.env`** — `dataverse auth who` shows a profile whose
   `Environment Url` equals `DATAVERSE_URL`, and `pac org who` succeeds against a PAC profile for
   the same URL. Dataverse CLI auth covers connect / data / query / metadata / MCP / Python; PAC auth
   covers `dataverse-solution` and `dataverse-admin`. Both are front-loaded here so neither prompts
   later.
4. **Python SDK is importable and current**:

   ```bash
   python -c "from PowerPlatform.Dataverse.client import DataverseClient; import pandas; from importlib.metadata import version; v=version('PowerPlatform-Dataverse-Client'); assert int(v.split('.')[0])>=1, f'SDK {v} is outdated, need >=1.0.0'"
   ```

**All pass:** confirm the detected setup (URL, profile, MCP server) and jump to Step 7. Do not
rewrite `.env`, re-register MCP, or re-run `pip install`.

**Any fail:** run Steps 1–7 but honor each step's skip condition. A partially configured workspace
does not need a full redo — if only `.env` and MCP are missing, start at Step 3.

---

## Step 1: Ensure tools are installed

Check every tool independently and report all missing tools at once. Install commands are in
[tools-setup.md](references/tools-setup.md).

| Tool | Check | Needed for |
|---|---|---|
| Python 3 | `python --version` | `scripts/auth.py`, SDK scripts |
| Git | `git --version` | Source control |
| Node.js | `node --version` | Dataverse CLI and MCP stdio proxy |
| Dataverse CLI | `npm list -g @microsoft/dataverse` | Auth, MCP proxy, scripted data-plane calls |
| PAC CLI | `pac` (prints a version banner; `pac --version` is invalid) | Solutions, admin |
| .NET SDK | `dotnet --version` | PAC CLI (not the Dataverse CLI — it bundles its runtime) |
| Azure CLI | `az --version` | Fallback environment discovery, CI/CD |

If `winget` installs a tool that is not yet on `PATH`, ask the user to restart the terminal. If
`pac` is not found, see [tools-setup.md](references/tools-setup.md#pac-cli-path-setup).

**Python dependencies** — check before installing:

```bash
python -c "from PowerPlatform.Dataverse.client import DataverseClient; import azure.identity, msal, msal_extensions, requests, pandas; print('OK')"
```

If it does not print `OK`, install from this skill's base directory:

```bash
pip install -r <skill-base-dir>/scripts/requirements.txt
```

`msal` + `msal-extensions` let `scripts/auth.py` reuse the `dataverse auth create` token cache —
one sign-in for CLI, MCP, and Python.

**Dataverse CLI** — install **only if missing**. On managed devices each `@latest` fetch can trigger
npm-registry security prompts (see
[tools-setup.md](references/tools-setup.md#corporate-managed-devices-package-registry)):

```bash
npm install -g @microsoft/dataverse@latest
```

**Skip condition:** all tools present and the Python check prints `OK`.

---

## Step 2: Discover the environment and authenticate

Two tools, two Entra ID apps, two token caches — front-load both now:

1. **`dataverse auth create`** (app `0c412cc3-0dd6-449b-987f-05b053db9457`) covers Dataverse CLI,
   MCP, and Python.
2. **`pac auth create`** (PAC's own app) covers `dataverse-solution` and `dataverse-admin`.

Look for existing profiles before asking for a URL:

```bash
dataverse auth list
dataverse auth who
pac auth list        # still useful for environment discovery (pac org list / pac env list)
```

- **A Dataverse CLI profile matches the target** → reuse it; take `DATAVERSE_URL` and `TENANT_ID`
  from the profile.
- **No profile, or it points elsewhere** → ask: "Connect to an existing environment or create a new
  one?"

If the target URL is in a different tenant or region than the current profile, create a new profile
rather than reusing the old one:

```bash
dataverse auth create --environment <url>               # interactive (WAM broker on Windows, no browser tab)
dataverse auth create --environment <url> --deviceCode  # remote / SSH / no browser
```

On an admin-consent error the CLI prints the correct consent URL to share with a tenant admin — do
not construct one yourself.

Switch between existing profiles:

```bash
dataverse auth select --name <profile-name>
```

Create a new environment (requires admin permissions):

```bash
pac admin create --name "<name>" --type "<type>" --region "<region>"
```

On a permissions error, send the user to the
[Power Platform admin center](https://admin.powerplatform.microsoft.com/) to create it, then connect.

**Confirm and extract values:**

```bash
dataverse auth who
dataverse org who
```

Parse `DATAVERSE_URL` and `TENANT_ID` from the output. If no tenant ID is shown, read it from the
`WWW-Authenticate` challenge:

```bash
curl -sI https://<org>.crm.dynamics.com/api/data/v9.2/ \
  | grep -i "WWW-Authenticate" \
  | sed -n 's|.*login\.microsoftonline\.com/\([^/]*\).*|\1|p'
```

### Step 2b: Front-load PAC CLI auth

PAC uses its own app, so it needs a separate sign-in. Use the same account as Step 2.

```bash
pac auth list                                                  # skip if a profile for DATAVERSE_URL exists
pac auth create --name <orgid> --environment <DATAVERSE_URL>
```

If PAC CLI is not installed, skip with a note that `dataverse-solution` / `dataverse-admin` will need
it later.

---

## Step 3: Write `.env`

Offer the authentication options:

> How would you like to authenticate with Dataverse?
>
> 1. **Interactive login (recommended)** — sign in via browser. No app registration needed; the token
>    stays cached across sessions.
> 2. **CI/CD service principal** — `CLIENT_ID` + `CLIENT_SECRET` or `CLIENT_CERTIFICATE_PATH`.

Write `.env` yourself — do not ask the user to create it. `MCP_CLIENT_ID` depends on the host:

| Host | `MCP_CLIENT_ID` |
|---|---|
| GitHub Copilot | `aebc6443-996d-45c2-90f0-388ff96faa56` |
| Claude Code | `0c412cc3-0dd6-449b-987f-05b053db9457` |

```python
tool_type = "<copilot | claude>"
mcp_client_id = (
    "aebc6443-996d-45c2-90f0-388ff96faa56"
    if tool_type == "copilot"
    else "0c412cc3-0dd6-449b-987f-05b053db9457"
)

with open(".env", "w") as f:
    f.write(f"DATAVERSE_URL={dataverse_url}\n")
    f.write(f"TENANT_ID={tenant_id}\n")
    f.write(f"MCP_CLIENT_ID={mcp_client_id}\n")
    f.write(f"SOLUTION_NAME={solution_name}\n")
    f.write("PUBLISHER_PREFIX=\n")  # filled in when the solution is created
    f.write("PAC_AUTH_PROFILE=nonprod\n")
    if client_id:
        f.write(f"CLIENT_ID={client_id}\n")
    if client_secret:
        f.write(f"CLIENT_SECRET={client_secret}\n")
```

Ensure secrets and build output are git-ignored:

```python
import os

GITIGNORE_ENTRIES = [
    ".env", ".vscode/settings.json", ".claude/mcp_settings.json",
    ".token_cache.bin", ".dataverse/", "*.snk", "__pycache__/", "*.pyc",
    "solutions/*.zip", "plugins/**/bin/", "plugins/**/obj/",
]
gitignore = open(".gitignore").read() if os.path.exists(".gitignore") else ""
missing = [e for e in GITIGNORE_ENTRIES if e not in gitignore]
if missing:
    with open(".gitignore", "a") as f:
        f.write("\n" + "\n".join(missing) + "\n")
```

**Skip condition:** `.env` exists with all required values.

---

## Step 4: Set up the project structure

For a new project (no `scripts/` folder):

```bash
mkdir -p solutions plugins scripts
cp <skill-base-dir>/scripts/auth.py scripts/
cp <skill-base-dir>/scripts/requirements.txt scripts/
```

If the project root has no `CLAUDE.md`, copy `<skill-base-dir>/assets/CLAUDE.md.template` to
`CLAUDE.md` and replace `{{DATAVERSE_URL}}`, `{{SOLUTION_NAME}}`, and `{{PUBLISHER_PREFIX}}` with
the values from `.env`. Never overwrite an existing `CLAUDE.md`.

Every Python script in the project then imports the helper the same way:

```python
import os, sys
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_client

client = get_client()
```

**Skip condition:** `scripts/auth.py` exists.

---

## Step 5: Verify the connection

```bash
dataverse auth who
pac org who
python scripts/auth.py --check
```

All three must resolve the same user and environment — that proves the Dataverse CLI cache, the PAC
profile, and Python's reuse of the shared cache are wired together.

`scripts/auth.py` flags:

| Flag | What it does | Exit codes |
|---|---|---|
| `--check` | Makes a **real data-plane call** and prints `REACHABLE: ... N non-private tables` or `NOT REACHABLE: ...`. A token can be minted while the org domain is blocked, so this is the only proof of a working connection. | 0 reachable, 2 not reachable |
| `--ping` | Lightweight check using only stdlib + `msal` (no SDK import). Also writes `.env` and copies `auth.py` to `scripts/` if missing. | 0 reachable, 1 no config / no token, 2 org unreachable |
| `--diagnose` | Prints which credential tiers are available and which one the next call will use, **without prompting**. | 0 |
| `--bootstrap URL` | One-shot setup shortcut: probes the URL for the tenant, writes `.env`, copies `auth.py` to `scripts/`, saves user-level config. Does not authenticate — run `--check` after `pip install`. | 0 |

**On failure:**

| Symptom | Fix |
|---|---|
| `dataverse auth who` fails | Re-run Step 2 |
| `pac org who` fails | Re-run Step 2b |
| `--check` prints a device-code URL | The browser/WAM cache has no Python-reusable token. Re-run `dataverse auth create --environment <url> --deviceCode`, then retry. Still prompting → check `pip show msal msal-extensions`. |
| `--check` prints `NOT REACHABLE` with a connection or timeout error | A network, proxy, or firewall blocks the org domain — not an auth problem. Ask the user to allow `*.dynamics.com`. Do not report success. |
| Auth hangs or picks the wrong identity | Run `python scripts/auth.py --diagnose` (see [tools-setup.md](references/tools-setup.md#credential-chain-and---diagnose)) |
| Other Python error | Check the SDK install and `.env` |

Before metadata work, confirm the account holds the needed customization privileges — see
[tools-setup.md](references/tools-setup.md#privilege-preflight).

---

## Step 6: Configure the MCP server

**Skip** when the host's MCP list or config already contains a Dataverse server for the selected
URL (`claude mcp list` for Claude Code; `~/.copilot/mcp-config.json` or `.mcp.json` for GitHub
Copilot).

Otherwise follow [mcp-configuration.md](references/mcp-configuration.md):

1. Detect the host (Claude Code or GitHub Copilot) from context.
2. Confirm `MCP_CLIENT_ID` in `.env` matches the host (Step 3 table).
3. Take the environment URL from `.env`.
4. Default to the GA endpoint (`/api/mcp`).
5. Register the server for the host.
6. Handle tenant admin consent and the environment allowlist — prefer
   `dataverse mcp allow <MCP_CLIENT_ID>` over the portal (one-time per tenant / environment).

MCP configuration only takes effect after a restart:

- **Claude Code** — run the `claude mcp add` command, then tell the user:
  > Dataverse MCP server registered. Restart Claude Code to enable the MCP tools — use
  > `claude --continue` to resume this session without losing context. On restart a browser window
  > may open to sign in; that is the MCP proxy authenticating on your behalf.
- **GitHub Copilot** — write the JSON config, then tell the user:
  > Dataverse MCP server configured. Restart your editor (or Copilot CLI session) for the change to
  > take effect.

Pause until the user has restarted.

---

## Step 7: Final verification

After the restart, **both** checks must pass before declaring setup complete.

1. **The MCP server is connected** — Claude Code: `claude mcp list` shows the `dataverse-*` server
   as connected. GitHub Copilot: the server appears in the MCP tool list. This proves the server
   starts, not that data operations work.
2. **The agent lists tables via `describe` / `search` and returns data** — ask:
   "List the tables in my Dataverse environment." This proves auth, tenant consent, environment
   allowlist, and endpoint reachability end to end.

**Reading failures:**

- Check 1 fails → the server cannot start. Re-run Step 6; confirm Node.js / `npx` are installed and
  the registration succeeded.
- Check 1 passes, Check 2 fails → the server speaks MCP but cannot reach or read Dataverse. Run
  `npx @microsoft/dataverse mcp <DATAVERSE_URL> --validate` and judge by the **GA** (`/api/mcp`)
  result only — see
  [mcp-configuration.md](references/mcp-configuration.md#reading---validate-output).

Do **not** use `--validate` as a success gate on first-time setup — with a cold token cache it can
fail with `MsalClientException` or `403` while MCP works on the next real call.

For what MCP can and cannot do versus the SDK / Web API, see the Tool Capabilities matrix in
`dataverse-overview`.

When both checks pass, tell the user:

> Connected to Dataverse at `<DATAVERSE_URL>`. Tools installed, authenticated, MCP live.
>
> Next steps:
>
> - Create tables, columns, and relationships (`dataverse-metadata`)
> - Write and import data (`dataverse-data`)
> - Query and analyze data (`dataverse-query`)
> - Export and promote solutions (`dataverse-solution`)
>
> To load sample data, ask: "Load demo data into my Dataverse environment."

---

## Safety rules

- **Confirm the target environment** before any write: show `dataverse auth who` / `pac org who`
  output and get the user's confirmation. Never assume the active profile is correct.
- **No fabrication** — report only counts and rows actually returned, anchored to something
  verifiable (org URL, a metadata GUID, the real number). If a call fails, say what failed.
- **MCP tools exist only after a restart.** Do not claim they are callable in the session that
  registered them, do not spin up a side `npx @microsoft/dataverse mcp` proxy as a workaround, and if
  the user explicitly asked for MCP, do not silently fall back to the SDK or Web API — surface the
  restart requirement instead.
- **Least privilege** — grant System Customizer for customization work, not System Administrator.
- **Never commit secrets** — `.env`, token caches, and `.dataverse/` stay git-ignored.

## References

| Reference | When to load |
|---|---|
| [tools-setup.md](references/tools-setup.md) | Installing tools, PAC CLI on Git Bash / `PATH`, managed-device npm registry, PAC / GitHub / Azure CLI auth, credential chain, privilege preflight |
| [mcp-configuration.md](references/mcp-configuration.md) | Registering the MCP server for Claude Code or GitHub Copilot, admin consent, allowlisting, MCP troubleshooting |

Files in this skill

  • SKILL.md17.2 KB
  • assets/CLAUDE.md.template2.7 KB
  • references/mcp-configuration.md14.5 KB
  • references/tools-setup.md12.2 KB
  • scripts/auth.py43.5 KB
  • scripts/enable_mcp_client.py2.2 KB
  • scripts/requirements.txt101 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…