@slamb2k/dvx 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +292 -0
- package/dist/chunk-QFYVVOAX.js +1041 -0
- package/dist/chunk-QFYVVOAX.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1503 -0
- package/dist/index.js.map +1 -0
- package/dist/server-KJMLJWZI.js +318 -0
- package/dist/server-KJMLJWZI.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# dvx
|
|
2
|
+
|
|
3
|
+
Agent-first CLI and MCP server for Microsoft Dataverse (CE / Sales / Service).
|
|
4
|
+
|
|
5
|
+
dvx treats the Dataverse Web API the way [gws](https://github.com/nicholasgasior/gws) treats Google Workspace — a single binary with a dual surface (CLI + MCP stdio) that derives its capabilities at runtime from the API's own schema. No hand-authored tool lists, no hardcoded entity definitions, no 20-row ceilings.
|
|
6
|
+
|
|
7
|
+
## Why dvx?
|
|
8
|
+
|
|
9
|
+
Dataverse is a uniquely hostile API for AI agents:
|
|
10
|
+
|
|
11
|
+
- **Opaque GUIDs everywhere** — every entity, relationship, and option set is a UUID with no human-readable context
|
|
12
|
+
- **Enormous, per-org $metadata** — the OData metadata document can exceed 50MB, and every org's schema is different
|
|
13
|
+
- **Polymorphic lookups** — a single lookup field can reference multiple entity types
|
|
14
|
+
- **Invisible business logic** — server-side plugins, workflows, and business rules execute silently on write operations
|
|
15
|
+
- **Complex batch format** — OData `$batch` uses multipart MIME boundaries with changeset semantics
|
|
16
|
+
|
|
17
|
+
Microsoft's [official Dataverse MCP Server](https://github.com/microsoft/PowerApps-Tooling/tree/main/src/Mcp) takes a static approach: it pre-registers tools and caps query results at 20 rows with no pagination. dvx takes the opposite approach — it discovers the schema at runtime and streams results without artificial limits.
|
|
18
|
+
|
|
19
|
+
> **For a detailed comparison with use cases across 9 domains, see [docs/comparison-vs-official-mcp.md](docs/comparison-vs-official-mcp.md).**
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# npm (all platforms)
|
|
25
|
+
npm install -g dvx
|
|
26
|
+
|
|
27
|
+
# Linux binary (x64)
|
|
28
|
+
curl -fsSL https://raw.githubusercontent.com/slamb2k/dvx/main/scripts/install.sh | sh
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
macOS users: install via npm. Pre-built macOS binaries are not currently published.
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### 1. Create an auth profile
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Service principal (non-interactive, CI/CD)
|
|
39
|
+
dvx auth create \
|
|
40
|
+
--name myorg \
|
|
41
|
+
--environment-url https://myorg.crm.dynamics.com \
|
|
42
|
+
--tenant-id <tenant-id> \
|
|
43
|
+
--client-id <client-id> \
|
|
44
|
+
--client-secret <secret>
|
|
45
|
+
|
|
46
|
+
# Delegated (interactive browser login)
|
|
47
|
+
dvx auth login \
|
|
48
|
+
--name myorg \
|
|
49
|
+
--environment-url https://myorg.crm.dynamics.com \
|
|
50
|
+
--tenant-id <tenant-id> \
|
|
51
|
+
--client-id <client-id>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. Query data
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# List all entities
|
|
58
|
+
dvx entities
|
|
59
|
+
|
|
60
|
+
# Show schema for an entity
|
|
61
|
+
dvx schema account
|
|
62
|
+
|
|
63
|
+
# OData query
|
|
64
|
+
dvx query --odata '/accounts?$select=name,revenue&$top=10'
|
|
65
|
+
|
|
66
|
+
# FetchXML query
|
|
67
|
+
dvx query --fetchxml '<fetch top="10"><entity name="account"><attribute name="name"/></entity></fetch>'
|
|
68
|
+
|
|
69
|
+
# From file (auto-detects OData vs FetchXML)
|
|
70
|
+
dvx query --file my-query.xml --output ndjson
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3. Mutate records
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Create
|
|
77
|
+
dvx create account --json '{"name": "Contoso Ltd", "revenue": 1000000}'
|
|
78
|
+
|
|
79
|
+
# Update
|
|
80
|
+
dvx update account 00000000-0000-0000-0000-000000000001 \
|
|
81
|
+
--json '{"revenue": 2000000}'
|
|
82
|
+
|
|
83
|
+
# Upsert (match on a field)
|
|
84
|
+
dvx upsert account --match-field name \
|
|
85
|
+
--json '{"name": "Contoso Ltd", "revenue": 2000000}'
|
|
86
|
+
|
|
87
|
+
# Delete
|
|
88
|
+
dvx delete account 00000000-0000-0000-0000-000000000001 --confirm
|
|
89
|
+
|
|
90
|
+
# All mutations support --dry-run
|
|
91
|
+
dvx create account --json '{"name": "Test"}' --dry-run
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 4. Batch operations
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# From a JSON file of operations
|
|
98
|
+
dvx batch --file operations.json
|
|
99
|
+
|
|
100
|
+
# Atomic (wrapped in OData changeset)
|
|
101
|
+
dvx batch --file operations.json --atomic
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Batch input format:
|
|
105
|
+
```json
|
|
106
|
+
[
|
|
107
|
+
{ "method": "POST", "path": "/accounts", "body": { "name": "Acme" } },
|
|
108
|
+
{ "method": "PATCH", "path": "/accounts(00000000-...)", "body": { "revenue": 500000 } }
|
|
109
|
+
]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 5. Execute custom actions
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Global action
|
|
116
|
+
dvx action WinOpportunity --json '{"Status": 3, "OpportunityClose": {...}}'
|
|
117
|
+
|
|
118
|
+
# Bound action
|
|
119
|
+
dvx action QualifyLead --entity lead --id 00000000-... --json '{...}'
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 6. Interactive demo
|
|
123
|
+
|
|
124
|
+
`dvx demo` walks through dvx's differentiators with live Dataverse calls, comparison callouts, and auto-cleanup.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Interactive tier selection (prompts if TTY)
|
|
128
|
+
dvx demo
|
|
129
|
+
|
|
130
|
+
# Non-interactive — specify tier directly
|
|
131
|
+
dvx demo --tier read # Schema discovery, OData, FetchXML — no data changes
|
|
132
|
+
dvx demo --tier write # Read + CRUD lifecycle with [dvx-demo] prefix, auto-cleanup
|
|
133
|
+
dvx demo --tier full # Write + batch, WhoAmI action, impersonation, aggregation
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Each step prints a comparison callout showing what native Dataverse MCP and PAC CLI cannot do. A summary table with pass/skip/fail status and elapsed times is printed at the end.
|
|
137
|
+
|
|
138
|
+
## MCP Server
|
|
139
|
+
|
|
140
|
+
dvx exposes a [Model Context Protocol](https://modelcontextprotocol.io) server for AI agent integration.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# stdio transport (default — for Claude Desktop, Cursor, etc.)
|
|
144
|
+
dvx mcp --entities account,contact,opportunity
|
|
145
|
+
|
|
146
|
+
# HTTP transport (for remote/multi-session deployments)
|
|
147
|
+
dvx mcp --transport http --port 3000 --entities account,contact
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Meta-tools (always available)
|
|
151
|
+
|
|
152
|
+
| Tool | Description |
|
|
153
|
+
|------|-------------|
|
|
154
|
+
| `list_entities` | List all entity logical names in the org |
|
|
155
|
+
| `discover_entity` | Fetch full schema for an entity (attributes, types, relationships) |
|
|
156
|
+
| `execute_query` | Run OData or FetchXML queries with full pagination |
|
|
157
|
+
| `execute_action` | Execute any global or bound Dataverse action |
|
|
158
|
+
| `batch_execute` | Run multiple operations in a single request, optionally atomic |
|
|
159
|
+
|
|
160
|
+
Meta-tools solve the **MCP dictionary problem** — instead of registering hundreds of tools upfront (one per entity), agents discover entities on-demand and use generic query/mutation tools. This keeps the tool list small and the agent's context window clean.
|
|
161
|
+
|
|
162
|
+
### Dynamic entity tools (per `--entities` scope)
|
|
163
|
+
|
|
164
|
+
When you pass `--entities account,contact`, dvx generates 4 typed tools per entity:
|
|
165
|
+
|
|
166
|
+
- `create_account` / `create_contact`
|
|
167
|
+
- `update_account` / `update_contact`
|
|
168
|
+
- `get_account` / `get_contact`
|
|
169
|
+
- `query_account` / `query_contact`
|
|
170
|
+
|
|
171
|
+
These provide typed input schemas derived from the entity's actual attributes — an agent can see exactly which fields exist, their types, and which are required.
|
|
172
|
+
|
|
173
|
+
### Claude Desktop configuration
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"mcpServers": {
|
|
178
|
+
"dataverse": {
|
|
179
|
+
"command": "dvx",
|
|
180
|
+
"args": ["mcp", "--entities", "account,contact,opportunity,lead"]
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Authentication
|
|
187
|
+
|
|
188
|
+
dvx supports three authentication patterns:
|
|
189
|
+
|
|
190
|
+
| Pattern | Use Case | Flow |
|
|
191
|
+
|---------|----------|------|
|
|
192
|
+
| **Service Principal** | CI/CD, server-to-server, MCP server | Client credentials (`ConfidentialClientApplication`) |
|
|
193
|
+
| **Delegated (PKCE)** | Interactive CLI use, per-user context | Browser-based OAuth with PKCE (`PublicClientApplication`) |
|
|
194
|
+
| **Impersonation** | Service principal acting as a specific user | `--as-user <EntraObjectId>` adds `CallerObjectId` header |
|
|
195
|
+
|
|
196
|
+
Secrets are **never written to disk**. Service principal client secrets are passed via environment variables. Delegated tokens are cached in `.dvx/msal-cache.json` (mode `0600`).
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
# Manage profiles
|
|
200
|
+
dvx auth list
|
|
201
|
+
dvx auth select myorg
|
|
202
|
+
|
|
203
|
+
# Impersonate a user (requires prvActOnBehalfOfAnotherUser privilege)
|
|
204
|
+
dvx create account --json '{"name": "Test"}' --as-user 00000000-...
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Key Features
|
|
208
|
+
|
|
209
|
+
- **Runtime schema discovery** — entity definitions fetched from `EntityDefinitions` API, never hardcoded
|
|
210
|
+
- **SQLite schema cache** — persistent cache with configurable TTL (default 5 min), survives restarts
|
|
211
|
+
- **FetchXML with auto-pagination** — paging cookies handled transparently
|
|
212
|
+
- **NDJSON streaming** — page through results emitting one record per line, no memory buffering
|
|
213
|
+
- **Retry with backoff** — automatic retry on 429/5xx with `Retry-After` header support
|
|
214
|
+
- **Dry-run on all mutations** — `--dry-run` prints what would execute without sending
|
|
215
|
+
- **Field masking** — `--fields` or `$select` on all queries, never returns full records by default
|
|
216
|
+
- **Batch operations** — up to 1000 ops per request with optional atomic changesets
|
|
217
|
+
- **Input validation** — GUIDs, entity names, action names, and URLs validated before any HTTP call
|
|
218
|
+
- **Typed errors** — full error hierarchy (`DvxError` → `AuthError`, `DataverseError`, `ValidationError`, etc.)
|
|
219
|
+
|
|
220
|
+
## Configuration
|
|
221
|
+
|
|
222
|
+
### Environment variables
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# Required
|
|
226
|
+
DATAVERSE_URL=https://yourorg.crm.dynamics.com
|
|
227
|
+
DATAVERSE_CLIENT_ID=<app-registration-client-id>
|
|
228
|
+
DATAVERSE_CLIENT_SECRET=<from-keychain-or-env>
|
|
229
|
+
DATAVERSE_TENANT_ID=<entra-tenant-id>
|
|
230
|
+
|
|
231
|
+
# Optional
|
|
232
|
+
DVX_SCHEMA_CACHE_TTL_MS=300000 # Schema cache TTL (default: 5 min)
|
|
233
|
+
DVX_SCHEMA_CACHE_PATH=.dvx/cache.db # SQLite cache location
|
|
234
|
+
DVX_MAX_ROWS=5000 # Per-query row cap
|
|
235
|
+
DVX_DEBUG=true # Verbose HTTP logging
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Local files (managed by dvx)
|
|
239
|
+
|
|
240
|
+
| File | Purpose |
|
|
241
|
+
|------|---------|
|
|
242
|
+
| `.dvx/config.json` | Auth profiles (no secrets) |
|
|
243
|
+
| `.dvx/msal-cache.json` | MSAL delegated token cache (0600) |
|
|
244
|
+
| `.dvx/cache.db` | SQLite schema cache |
|
|
245
|
+
|
|
246
|
+
## Architecture
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
┌──────────────┐ ┌──────────────┐
|
|
250
|
+
│ CLI (human) │ │ MCP (agent) │
|
|
251
|
+
└──────┬───────┘ └──────┬───────┘
|
|
252
|
+
│ │
|
|
253
|
+
└────────┬───────────┘
|
|
254
|
+
│
|
|
255
|
+
┌────────▼────────┐
|
|
256
|
+
│ DataverseClient │ ← single client, all operations
|
|
257
|
+
├─────────────────┤
|
|
258
|
+
│ AuthManager │ ← service principal or PKCE
|
|
259
|
+
│ SchemaCache │ ← SQLite-backed, TTL-based
|
|
260
|
+
│ withRetry() │ ← 429/5xx backoff
|
|
261
|
+
└────────┬────────┘
|
|
262
|
+
│
|
|
263
|
+
┌────────▼────────┐
|
|
264
|
+
│ Dataverse │
|
|
265
|
+
│ Web API │
|
|
266
|
+
└─────────────────┘
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
All operations flow through a single `DataverseClient` class. No direct HTTP calls in command handlers or MCP tools. Schema is always fetched live or from cache — never hardcoded.
|
|
270
|
+
|
|
271
|
+
## Comparison with Microsoft's Official Dataverse MCP Server
|
|
272
|
+
|
|
273
|
+
See **[docs/comparison-vs-official-mcp.md](docs/comparison-vs-official-mcp.md)** for a detailed feature comparison and use cases across Sales, Customer Service, Field Service, Marketing, Finance, ALM, Data Migration, Security, and Reporting.
|
|
274
|
+
|
|
275
|
+
Key differences at a glance:
|
|
276
|
+
|
|
277
|
+
| Capability | dvx | Official Dataverse MCP |
|
|
278
|
+
|-----------|-----|----------------------|
|
|
279
|
+
| Query row limit | 5,000 (configurable) | **20 rows, hard-coded** |
|
|
280
|
+
| Pagination | Full (OData + FetchXML) | None |
|
|
281
|
+
| FetchXML | Full with auto-paging | Not supported |
|
|
282
|
+
| Batch operations | $batch with changesets | Not supported |
|
|
283
|
+
| Custom actions | `execute_action` | Not supported |
|
|
284
|
+
| Impersonation | `CallerObjectId` header | Not supported |
|
|
285
|
+
| Schema discovery | Runtime via EntityDefinitions | Static tool list |
|
|
286
|
+
| Dry-run | All mutations | Not supported |
|
|
287
|
+
| NDJSON streaming | Yes | No |
|
|
288
|
+
| CLI interface | Full featured | MCP only |
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
MIT
|