@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 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