Claude Code (Part 16)
Neil Haddley โข October 17, 2026
Installing Microsoft's official Dataverse plugin from the Claude marketplace, connecting it to a Dataverse environment, and reading and writing records in plain English
In Part 13 I built my own Claude Code skill, dv-generate, which writes Dataverse solution XML without touching a live environment. Microsoft has since published an official Dataverse plugin on the Claude marketplace. It works the other way round: it connects Claude Code to a live environment, and then reads and writes tables and records there.
The plugin is open source (MIT), and Microsoft describes it in Dataverse Plugin Is Now on the Claude Marketplace.
Installing the plugin
I opened Claude Code in an empty folder and installed the plugin from the official marketplace.
PROMPT
1/plugin install dataverse@claude-plugins-official
The Dataverse-skills repository lists the nine skills, in this order:
| Skill | What it does |
|---|---|
dv-connect | One-time setup: installs the Dataverse CLI, the Python SDK and the PAC CLI, signs in to the environment, and registers the Dataverse MCP server |
dv-query | Reads, filters, pages through and aggregates records, answers questions in plain English, and loads results into pandas DataFrames |
dv-data | Creates, updates and deletes single records, and bulk imports: CSV files, several tables with foreign keys, upserts by alternate key, and AI-generated sample data |
dv-metadata | Authors and edits the data model: tables, columns, relationships, forms and views |
dv-solution | Manages the solution lifecycle: create, export, import, promote across environments, and validate deployments |
erp-xpp | The Finance and Operations X++ lifecycle: authoring, SDK installation, compiling, deploying, database sync and verification (Windows only) |
dv-admin | Environment administration: bulk delete, retention and archiving, organisation and OrgDB settings, the recycle bin, audit, and Power Platform admin center toggles |
dv-security | Assigns security roles, manages user access, adds application users, configures business units, and elevates an admin |
dv-overview | The cross-cutting rules and tool routing, loaded before any other skill so that each request goes to the right specialist |
The same skills install into other coding agents too. The README gives install commands for GitHub Copilot, Gemini CLI, Codex CLI, Cursor and Google Antigravity, as well as Claude Code.
The plugin chooses the tool for each job. The Dataverse MCP server handles simple reads and small writes, the Python SDK handles bulk work, the Dataverse CLI handles data commands, and the PAC CLI handles solutions and tenant operations.
Connecting to an environment
PROMPT
1connect to my Dataverse Environment
The dv-connect skill first looked for existing PAC CLI sign-ins on my Mac and asked which environment this folder should use. I typed the address of a development environment that was not in the list, so Claude created a new Dataverse CLI sign-in for it, using a device code.
Asking about the environment
The PAC CLI sign-in was still waiting for me, but I asked a question anyway.
PROMPT
1What tables are in this environment?
I liked that Claude did not simply report 489 custom tables. It worked out that the msdyn_, mspp_ and adx_ tables come from Microsoft, and picked out the one table with a real publisher prefix.
Writing a record
PROMPT
1Create an account named Contoso
Reading records
PROMPT
1list todo items
The nine skills, with examples
I never named a skill in my session. I asked in plain English, and Claude chose the skill whose description matched. To see each skill at work, I went back to the same folder and typed one or two prompts for each skill into a single Claude Code session, one after another, against my development environment. Under each prompt is what Claude Code showed: the skill it loaded, the commands it ran (shortened), and its reply.
dv-overview: the rules the others follow. It loads first for any Dataverse, Dynamics 365 or Power Platform request. It does no work itself. It holds the rules: check what is already connected before setting anything up, use Python rather than Node.js for scripts, confirm the environment before any change, and always ask for the publisher prefix, because a prefix can never be changed. It also says what is out of scope: canvas apps, Power Automate flows and Business Central. There is no prompt for it, but it shows up in the first transcript below.
dv-connect: one-step setup. It checks the tools, chooses the environment, signs in to the Dataverse CLI and the PAC CLI, writes a .env file, and registers the MCP server. Every step checks whether it is already done and skips it if so.
PROMPT
1Connect to my Dataverse environment
This was the first prompt of my session, and the screenshots above show what happened: a choice of environment, two device-code sign-ins, and the consent screen.
dv-query: reading and analysing records. Filtered reads, counts, SQL, FetchXML for joins and totals, and pandas DataFrames. If dataverse auth who shows an active sign-in, it skips the setup and queries straight away.
PROMPT
1How many accounts are there in my Dataverse environment?
TEXT
1โ I'll load the Dataverse overview skill first, then use the query skill to count accounts. 2โ Skill(dataverse:dv-overview) 3โ Skill(dataverse:dv-query) 4โ ToolSearch(dataverse) 5 โฟ No matching deferred tools found 6โ Bash(dataverse auth who) 7 โฟ User: admin@p8lf.onmicrosoft.com 8 Environment: https://org62b9d1ca.crm.dynamics.com/ 9โ No MCP tools found, but there's an active CLI profile. I'll use the Dataverse CLI directly to count accounts. 10โ Bash(dataverse data count --table accounts) 11 โฟ Count: 1 12โ There is 1 account in your Dataverse environment (https://org62b9d1ca.crm.dynamics.com/, org dev-no-721df8ab). 13 14 Note: Dataverse MCP tools aren't set up in this session โ I used the Dataverse CLI instead. If you'd like 15 conversational querying going forward, I can set up MCP.
The overview's first rule is visible here: Claude looked for MCP tools, found none, found the CLI sign-in, and used that.
PROMPT
1Total the open Todo items by priority
TEXT
1โ Bash(dataverse api request --path "/api/data/v9.2/EntityDefinitions?...") 2 โฟ ERROR: Absolute URL host '' does not match the environment host 'org62b9d1ca.crm.dynamics.com'. 3โ Bash(dataverse api request --path "EntityDefinitions?...") 4 โฟ HTTP Error 404 - Not Found 5โ Bash(dataverse api request --path "api/data/v9.2/EntityDefinitions?$select=LogicalName,EntitySetName") 6 โฟ ... 7โ Found it: table hadd_todo. Let me get its EntitySetName and check the priority/status columns, then total by priority. 8โ Bash(dataverse api request --path "api/data/v9.2/hadd_todos?$apply=filter(statecode eq 0)/groupby((hadd_priority),aggregate($count as total))") 9 โฟ {"value":[{"hadd_priority":100000002,"total":6},{"hadd_priority":100000000,"total":4},{"hadd_priority":100000001,"total":4}]} 10โ Open Todo items by priority (table hadd_todo, filtered to Active/open records): 11 12 | Priority | Count | 13 |-------------|--------| 14 | High | 6 | 15 | Medium | 4 | 16 | Low | 4 | 17 | Total open | 14 | 18 19 (One additional record, "Do this thing," has no priority set and is marked Inactive, so it's excluded as not open.)
This one took 27 turns. Claude first had to find which table "Todo items" meant, and on the way it hit the leading-slash problem described at the end of this section three times before it found the form that works. The total itself came from an OData $apply aggregate, calculated by Dataverse rather than by fetching every record.
dv-data: writing records. Create, update, delete and upsert, CSV import, loading several related tables in the right order, and sample data. Sample records use made-up @example.com emails.
PROMPT
1Import contacts.csv into the contacts table, matching each Company to an existing account
TEXT
1โ Read(contacts.csv) 2 โฟ firstname,lastname,emailaddress1,company 3 Dan,Kowalski,dan.kowalski@example.com,Contoso 4 ... 5โ Bash(dataverse data query --table accounts --select "name,accountid") 6โ The single account is named "Contoso," which matches all 3 rows in contacts.csv. 7โ Skill(dataverse:dv-data) 8โ Before I write anything, I need to confirm the target environment โ this is the first write action in this session. 9 10 I'm about to create 3 contact records in https://org62b9d1ca.crm.dynamics.com/ (org dev-no-721df8ab), 11 each linked to the existing account Contoso: 12 13 | First | Last | Email | Company โ Account | 14 |-------|----------|---------------------------|-------------------| 15 | Dan | Kowalski | dan.kowalski@example.com | Contoso | 16 | Ella | Fischer | ella.fischer@example.com | Contoso | 17 | Finn | Walsh | finn.walsh@example.com | Contoso | 18 19 Is this the correct environment, and should I go ahead? 20 21> Yes, that is the right environment. Go ahead. 22 23โ Bash(dataverse data create --table contacts --data '{"firstname":"Dan", ..., 24 "parentcustomerid_account@odata.bind":"/accounts(f31f8a9c-...)"}') 25โ First record created and correctly linked to Contoso. Creating the remaining two. 26โ All 3 contacts from contacts.csv were imported into the contacts table, each linked to the existing Contoso account.
Claude stopped before its first write in the session and asked me to confirm the environment, as the overview's rules require.
PROMPT
1Add 20 sample Todo items
TEXT
1โ Bash(dataverse api request --path "api/data/v9.2/EntityDefinitions(LogicalName='hadd_todo')/Attributes?...") 2โ Bash(dataverse data create --table hadd_todos --data '{"hadd_title":"Water the office plants", ...}') 3 โฟ {"id":"e5f33d9e-...","table":"hadd_todos"} 4โ That worked. Continuing with the remaining 15. 5 ... 6โ All 20 created. Let me verify the final count. 7โ Bash(dataverse data count --table hadd_todos) 8 โฟ Count: 35 9โ Added 20 sample Todo items to hadd_todo, mixing Home/Work/Errands categories, Low/Medium/High priorities, 10 due dates through mid-November, and a few marked completed. Table total is now 35 (was 15 before this batch).
This time Claude did not ask again: I had already confirmed the environment earlier in the session.
dv-metadata: the data model. Tables, columns, relationships, forms and views, and reading the schema that is already there. It makes every change in the live environment and then pulls it into the repo, which I come back to below.
PROMPT
1What columns and relationships does the Todo table have?
TEXT
1โ Skill(dataverse:dv-metadata) 2โ Bash(dataverse api request --path ".../OneToManyRelationships" ... ".../ManyToOneRelationships" ... ".../ManyToManyRelationships") 3โ Columns on hadd_todo 4 5 | Display | Logical Name | Type | Notes | 6 |-------------|--------------------|----------|-------------------------------------------------| 7 | Title | hadd_title | String | Primary name column | 8 | Description | hadd_description | String | | 9 | Category | hadd_category | Picklist | Home=100000000, Work=100000001, Errands=100000002 | 10 | Priority | hadd_priority | Picklist | Low=100000000, Medium=100000001, High=100000002 | 11 | Due Date | hadd_duedate | DateTime | | 12 | Completed | hadd_completed | Boolean | | 13 14 Relationships: none custom โ hadd_todo has no lookups to other business tables (no link to Account/Contact) 15 and no N:N relationships. So it's a standalone table โ nothing currently ties a Todo item to an Account or Contact.
PROMPT
1Add an Effort choice column to the Todo table, with Small, Medium and Large
TEXT
1โ Bash(ls .env) 2 โฟ No such file or directory 3โ Bash(dataverse data query --sql "SELECT uniquename, customizationprefix, friendlyname FROM publisher WHERE customizationprefix = 'hadd'") 4โ Found it โ the table lives in solution TodoSolution (and also surfaced via TodoExtensions). Two publishers share 5 the hadd prefix: Haddley and HaddleyDev. Which solution should the new Effort column go into โ TodoSolution, 6 TodoExtensions, or something else? 7 8> TodoExtensions 9 10โ Write(effort_column.json) 11โ Bash(dataverse api request --method POST --path "api/data/v9.2/EntityDefinitions(LogicalName='hadd_todo')/Attributes" 12 --body-file effort_column.json --header "MSCRM.SolutionUniqueName:TodoExtensions") 13โ Added the Effort choice column to hadd_todo in the TodoExtensions solution: 14 15 | Display Name | Logical Name | Type | Options | 16 |--------------|--------------|----------|---------------------------------------------------------| 17 | Effort | hadd_effort | Picklist | Small (100000000), Medium (100000001), Large (100000002) | 18 19 One thing: this working directory isn't a git repo, so I can't do the usual export โ unpack โ commit 20 pull-to-repo step. Want me to export/unpack the TodoExtensions solution into the folder anyway?
Claude would not guess where the column belonged, and asked. Earlier I had added a solution called TodoExtensions, with its own publisher, because the Todo table came from a managed solution whose publisher is read-only. Then, without being asked, Claude offered the second half of the environment-first loop: export the change back into the folder.
dv-solution: moving work between environments. Create a publisher and a solution, export and unpack it into Git, pack and import it into another environment, and check that the import worked.
PROMPT
1Export the TodoExtensions solution and unpack it into this folder
TEXT
1โ Skill(dataverse:dv-solution) 2โ Bash(pac auth list) 3โ Bash(pac solution export --name TodoExtensions --path solutions/TodoExtensions.zip --managed false 4 --environment https://org62b9d1ca.crm.dynamics.com/) 5 โฟ Connected to... dev-no-721df8ab 6 Solution export succeeded. 7โ Export succeeded. Now unpacking into the folder. 8โ Bash(pac solution unpack --zipfile solutions/TodoExtensions.zip --folder solutions/TodoExtensions --packagetype Unmanaged) 9โ Bash(rm solutions/TodoExtensions.zip; find solutions/TodoExtensions -type f) 10โ Bash(grep -c "hadd_effort" solutions/TodoExtensions/Entities/hadd_todo/Entity.xml) 11 โฟ 3 12โ Exported and unpacked TodoExtensions into solutions/TodoExtensions/ โ the zip was deleted, leaving the source 13 files (Entity.xml for hadd_todo, Customizations.xml, Solution.xml). Confirmed the new hadd_effort Effort column is in there.
Claude checked pac auth list before exporting, and then checked that the new column was really in the exported XML. Dataverse wrote that XML, not Claude.
dv-security: access. Security roles, application users, business units, and making yourself an admin.
PROMPT
1Which security roles do I have in this environment?
TEXT
1โ Skill(dataverse:dv-security) 2โ Bash(dataverse api request --path "api/data/v9.2/WhoAmI") 3โ Bash(dataverse api request --path "api/data/v9.2/systemusers(20cd6e0f-...)/systemuserroles_association?$select=name,roleid") 4โ You (admin@p8lf.onmicrosoft.com, in org62b9d1ca.crm.dynamics.com) have these security roles: 5 - System Administrator 6 - System Customizer 7 - Basic User
PROMPT
1Give me the Environment Maker role in this environment
TEXT
1โ I'll assign Environment Maker to admin@p8lf.onmicrosoft.com on https://org62b9d1ca.crm.dynamics.com/ and verify it landed. 2โ Bash(pac admin assign-user --user admin@p8lf.onmicrosoft.com --role "Environment Maker" 3 --environment https://org62b9d1ca.crm.dynamics.com/) 4 โฟ Connected to... dev-no-721df8ab 5โ Output reported success, but exit 0 isn't proof โ let me verify directly against the user's role list. 6โ Bash(dataverse api request --path "api/data/v9.2/systemusers(20cd6e0f-...)/systemuserroles_association?$select=name") 7โ Confirmed โ Environment Maker is now on your role list for admin@p8lf.onmicrosoft.com in dev-no-721df8ab, 8 alongside System Administrator, System Customizer, and Basic User.
"Exit 0 isn't proof" comes straight from the skill, which has a section with that title.
dv-admin: running the environment. Bulk delete, retention and archiving, audit, the recycle bin, and organisation settings, limited to an allowlist of 37 settings. It refuses anything outside that list. I asked for one setting on the list and one that is not.
PROMPT
1Is auditing turned on in this environment? Set the audit log retention to 90 days, and turn off autosave
TEXT
1โ Skill(dataverse:dv-admin) 2โ Bash(pac org list-settings --filter isauditenabled --environment https://org62b9d1ca.crm.dynamics.com/) 3 โฟ isauditenabled Yes 4โ Auditing is on (isauditenabled = Yes). Now setting the audit log retention to 90 days. 5โ Bash(pac org update-settings --name auditretentionperiodv2 --value 90 --environment https://org62b9d1ca.crm.dynamics.com/) 6 โฟ Setting 'auditretentionperiodv2' updated successfully to '90'. 7โ Here's where things stand: 8 - Auditing: already turned on (isauditenabled = Yes). 9 - Audit log retention: set to 90 days, confirmed. 10 - Autosave: I can't do this one โ isautosaveenabled is explicitly outside dv-admin's allowlisted settings 11 (only 37 specific PPAC toggles are in scope; autosave isn't one of them). You'd need to turn that off 12 yourself via the Power Platform admin center.
PROMPT
1Delete all the completed Todo items
TEXT
1โ Bash(dataverse data query --table hadd_todos --select "hadd_title,hadd_completed" --filter "hadd_completed eq true") 2โ Found 3 completed Todo items: 3 4 | Title | ID | 5 |----------------------|----------------------------------------| 6 | Review pull requests | eaf33d9e-eec9-f111-aaaf-6045bd094f74 | 7 | Return library books | 55e088a7-eec9-f111-aaaf-6045bd094f74 | 8 | Plan weekend trip | e23960bb-eec9-f111-aaaf-6045bd094f74 | 9 10 I'll delete these 3 records from hadd_todo. Confirm? 11 12> Yes, delete them 13 14โ Bash(dataverse data delete --table hadd_todos --id eaf33d9e-... --no-confirm (and the other two)) 15 โฟ Record deleted successfully. 16โ All 3 completed Todo items ("Review pull requests," "Return library books," "Plan weekend trip") were deleted.
For three records, Claude did not schedule a bulk delete job. It listed the records, asked, and deleted them one by one.
erp-xpp: Finance and Operations code. Write, compile, deploy and database-sync X++ customisations with pac package commands.
PROMPT
1Create an X++ custom service that returns a customer's credit limit
I did not run this one. The skill only runs on Windows, so it cannot run on my Mac.
Under the hood: A leading slash that breaks on a Mac
The skills document the raw API escape hatch as dataverse api request --path "/api/data/v9.2/WhoAmI". On my Mac that fails with "Absolute URL host '' does not match the environment host", and Claude ran into it three times while totalling the Todo items. Without the leading slash, --path "api/data/v9.2/WhoAmI", it works, and once Claude had found that form it used it for the rest of the session. My guess is that the CLI, which is written in .NET, reads a path that starts with / as an absolute file address on macOS and Linux, so it never treats it as relative to the environment.
My skill and Microsoft's plugin
I read the plugin's skill files after the session, and the dv-metadata skill states its central rule plainly: "Do not write solution XML by hand to create new tables, columns, forms, or views." Its workflow is environment-first:
- Make the change in the environment, through the Dataverse metadata API.
- Pull the change into the repo with
pac solution exportandpac solution unpack. - Commit the result.
The reason it gives is that "the environment validates metadata far more reliably than an agent editing XML", and that an exported solution is "generated by Dataverse itself and is always valid". My dv-generate skill from Part 13 does exactly what that rule forbids: it writes the solution XML by hand, offline, and I import the zip. Its long list of hard-won rules about XML that fails to import is the cost of that choice.
The plugin also has a gap that dv-generate fills. Its overview says that tables, forms and views are covered, but that composing a model-driven app itself, with its site map and navigation, "is not yet a first-class skill". dv-generate writes the AppModule and SiteMap along with the table.
dv-generate (Part 13) | Microsoft's Dataverse plugin | |
|---|---|---|
| Who made it | Me | Microsoft |
| Needs an environment | No | Yes |
| How a new table is made | Hand-written solution XML, packed into a zip that I import | Created in the environment, then exported and unpacked into the repo |
| Model-driven app shell (AppModule and SiteMap) | Yes | Not yet |
| Signing in | None | Device code for the Dataverse CLI and the PAC CLI |
| Good for | Starting a solution offline | Questions, records, solutions, security and administration on a real environment |
















