mcp-dataverse 0.3.7 → 0.3.9

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.
Files changed (208) hide show
  1. package/CAPABILITIES.md +1039 -1039
  2. package/LICENSE +21 -21
  3. package/README.md +114 -113
  4. package/dist/auth-provider.factory-MSMLSOX3.js +1 -0
  5. package/dist/chunk-24RDOMG4.js +29 -0
  6. package/dist/chunk-OXKMMPM3.js +37 -0
  7. package/dist/chunk-PAX4NW5B.js +1 -0
  8. package/dist/chunk-SUDI4JM6.js +3 -0
  9. package/dist/config.loader-VTIKUDN7.js +1 -0
  10. package/dist/dataverse-client-advanced-T5ZJMRLK.js +1 -0
  11. package/dist/doctor.js +2 -102
  12. package/dist/http-server.js +3 -61
  13. package/dist/install.js +8 -233
  14. package/dist/server.js +43 -202
  15. package/dist/setup-auth.js +18 -41
  16. package/package.json +95 -94
  17. package/server.json +51 -50
  18. package/dist/auth/auth-provider.factory.d.ts +0 -4
  19. package/dist/auth/auth-provider.factory.d.ts.map +0 -1
  20. package/dist/auth/auth-provider.factory.js +0 -5
  21. package/dist/auth/auth-provider.factory.js.map +0 -1
  22. package/dist/auth/auth-provider.interface.d.ts +0 -21
  23. package/dist/auth/auth-provider.interface.d.ts.map +0 -1
  24. package/dist/auth/auth-provider.interface.js +0 -2
  25. package/dist/auth/auth-provider.interface.js.map +0 -1
  26. package/dist/auth/device-code-auth-provider.d.ts +0 -18
  27. package/dist/auth/device-code-auth-provider.d.ts.map +0 -1
  28. package/dist/auth/device-code-auth-provider.js +0 -167
  29. package/dist/auth/device-code-auth-provider.js.map +0 -1
  30. package/dist/config/config.loader.d.ts +0 -3
  31. package/dist/config/config.loader.d.ts.map +0 -1
  32. package/dist/config/config.loader.js +0 -39
  33. package/dist/config/config.loader.js.map +0 -1
  34. package/dist/config/config.schema.d.ts +0 -16
  35. package/dist/config/config.schema.d.ts.map +0 -1
  36. package/dist/config/config.schema.js +0 -20
  37. package/dist/config/config.schema.js.map +0 -1
  38. package/dist/dataverse/dataverse-client-advanced.d.ts +0 -53
  39. package/dist/dataverse/dataverse-client-advanced.d.ts.map +0 -1
  40. package/dist/dataverse/dataverse-client-advanced.js +0 -199
  41. package/dist/dataverse/dataverse-client-advanced.js.map +0 -1
  42. package/dist/dataverse/dataverse-client.actions.d.ts +0 -11
  43. package/dist/dataverse/dataverse-client.actions.d.ts.map +0 -1
  44. package/dist/dataverse/dataverse-client.actions.js +0 -25
  45. package/dist/dataverse/dataverse-client.actions.js.map +0 -1
  46. package/dist/dataverse/dataverse-client.batch.d.ts +0 -10
  47. package/dist/dataverse/dataverse-client.batch.d.ts.map +0 -1
  48. package/dist/dataverse/dataverse-client.batch.js +0 -74
  49. package/dist/dataverse/dataverse-client.batch.js.map +0 -1
  50. package/dist/dataverse/dataverse-client.d.ts +0 -46
  51. package/dist/dataverse/dataverse-client.d.ts.map +0 -1
  52. package/dist/dataverse/dataverse-client.js +0 -275
  53. package/dist/dataverse/dataverse-client.js.map +0 -1
  54. package/dist/dataverse/dataverse-client.metadata.d.ts +0 -41
  55. package/dist/dataverse/dataverse-client.metadata.d.ts.map +0 -1
  56. package/dist/dataverse/dataverse-client.metadata.js +0 -124
  57. package/dist/dataverse/dataverse-client.metadata.js.map +0 -1
  58. package/dist/dataverse/dataverse-client.utils.d.ts +0 -14
  59. package/dist/dataverse/dataverse-client.utils.d.ts.map +0 -1
  60. package/dist/dataverse/dataverse-client.utils.js +0 -65
  61. package/dist/dataverse/dataverse-client.utils.js.map +0 -1
  62. package/dist/dataverse/http-client.d.ts +0 -38
  63. package/dist/dataverse/http-client.d.ts.map +0 -1
  64. package/dist/dataverse/http-client.js +0 -111
  65. package/dist/dataverse/http-client.js.map +0 -1
  66. package/dist/dataverse/types.d.ts +0 -68
  67. package/dist/dataverse/types.d.ts.map +0 -1
  68. package/dist/dataverse/types.js +0 -2
  69. package/dist/dataverse/types.js.map +0 -1
  70. package/dist/doctor.d.ts +0 -7
  71. package/dist/doctor.d.ts.map +0 -1
  72. package/dist/doctor.js.map +0 -1
  73. package/dist/http-server.d.ts +0 -3
  74. package/dist/http-server.d.ts.map +0 -1
  75. package/dist/http-server.js.map +0 -1
  76. package/dist/install.d.ts +0 -3
  77. package/dist/install.d.ts.map +0 -1
  78. package/dist/install.js.map +0 -1
  79. package/dist/resources/resource-provider.d.ts +0 -11
  80. package/dist/resources/resource-provider.d.ts.map +0 -1
  81. package/dist/resources/resource-provider.js +0 -79
  82. package/dist/resources/resource-provider.js.map +0 -1
  83. package/dist/server.d.ts +0 -3
  84. package/dist/server.d.ts.map +0 -1
  85. package/dist/server.js.map +0 -1
  86. package/dist/setup-auth.d.ts +0 -3
  87. package/dist/setup-auth.d.ts.map +0 -1
  88. package/dist/setup-auth.js.map +0 -1
  89. package/dist/tools/actions.tools.d.ts +0 -206
  90. package/dist/tools/actions.tools.d.ts.map +0 -1
  91. package/dist/tools/actions.tools.js +0 -256
  92. package/dist/tools/actions.tools.js.map +0 -1
  93. package/dist/tools/annotations.tools.d.ts +0 -94
  94. package/dist/tools/annotations.tools.d.ts.map +0 -1
  95. package/dist/tools/annotations.tools.js +0 -225
  96. package/dist/tools/annotations.tools.js.map +0 -1
  97. package/dist/tools/audit.tools.d.ts +0 -51
  98. package/dist/tools/audit.tools.d.ts.map +0 -1
  99. package/dist/tools/audit.tools.js +0 -170
  100. package/dist/tools/audit.tools.js.map +0 -1
  101. package/dist/tools/auth.tools.d.ts +0 -23
  102. package/dist/tools/auth.tools.d.ts.map +0 -1
  103. package/dist/tools/auth.tools.js +0 -36
  104. package/dist/tools/auth.tools.js.map +0 -1
  105. package/dist/tools/batch.tools.d.ts +0 -52
  106. package/dist/tools/batch.tools.d.ts.map +0 -1
  107. package/dist/tools/batch.tools.js +0 -89
  108. package/dist/tools/batch.tools.js.map +0 -1
  109. package/dist/tools/crud.tools.d.ts +0 -260
  110. package/dist/tools/crud.tools.d.ts.map +0 -1
  111. package/dist/tools/crud.tools.js +0 -290
  112. package/dist/tools/crud.tools.js.map +0 -1
  113. package/dist/tools/customization.tools.d.ts +0 -127
  114. package/dist/tools/customization.tools.d.ts.map +0 -1
  115. package/dist/tools/customization.tools.js +0 -285
  116. package/dist/tools/customization.tools.js.map +0 -1
  117. package/dist/tools/environment.tools.d.ts +0 -106
  118. package/dist/tools/environment.tools.d.ts.map +0 -1
  119. package/dist/tools/environment.tools.js +0 -274
  120. package/dist/tools/environment.tools.js.map +0 -1
  121. package/dist/tools/file.tools.d.ts +0 -73
  122. package/dist/tools/file.tools.d.ts.map +0 -1
  123. package/dist/tools/file.tools.js +0 -160
  124. package/dist/tools/file.tools.js.map +0 -1
  125. package/dist/tools/guardrails.d.ts +0 -22
  126. package/dist/tools/guardrails.d.ts.map +0 -1
  127. package/dist/tools/guardrails.js +0 -56
  128. package/dist/tools/guardrails.js.map +0 -1
  129. package/dist/tools/impersonate.tools.d.ts +0 -44
  130. package/dist/tools/impersonate.tools.d.ts.map +0 -1
  131. package/dist/tools/impersonate.tools.js +0 -87
  132. package/dist/tools/impersonate.tools.js.map +0 -1
  133. package/dist/tools/metadata.tools.d.ts +0 -279
  134. package/dist/tools/metadata.tools.d.ts.map +0 -1
  135. package/dist/tools/metadata.tools.js +0 -400
  136. package/dist/tools/metadata.tools.js.map +0 -1
  137. package/dist/tools/org.tools.d.ts +0 -32
  138. package/dist/tools/org.tools.d.ts.map +0 -1
  139. package/dist/tools/org.tools.js +0 -65
  140. package/dist/tools/org.tools.js.map +0 -1
  141. package/dist/tools/output.utils.d.ts +0 -63
  142. package/dist/tools/output.utils.d.ts.map +0 -1
  143. package/dist/tools/output.utils.js +0 -78
  144. package/dist/tools/output.utils.js.map +0 -1
  145. package/dist/tools/progress.d.ts +0 -15
  146. package/dist/tools/progress.d.ts.map +0 -1
  147. package/dist/tools/progress.js +0 -29
  148. package/dist/tools/progress.js.map +0 -1
  149. package/dist/tools/quality.tools.d.ts +0 -36
  150. package/dist/tools/quality.tools.d.ts.map +0 -1
  151. package/dist/tools/quality.tools.js +0 -97
  152. package/dist/tools/quality.tools.js.map +0 -1
  153. package/dist/tools/query.tools.d.ts +0 -151
  154. package/dist/tools/query.tools.d.ts.map +0 -1
  155. package/dist/tools/query.tools.js +0 -293
  156. package/dist/tools/query.tools.js.map +0 -1
  157. package/dist/tools/relations.tools.d.ts +0 -77
  158. package/dist/tools/relations.tools.d.ts.map +0 -1
  159. package/dist/tools/relations.tools.js +0 -96
  160. package/dist/tools/relations.tools.js.map +0 -1
  161. package/dist/tools/router.tools.d.ts +0 -5
  162. package/dist/tools/router.tools.d.ts.map +0 -1
  163. package/dist/tools/router.tools.js +0 -247
  164. package/dist/tools/router.tools.js.map +0 -1
  165. package/dist/tools/search.tools.d.ts +0 -74
  166. package/dist/tools/search.tools.d.ts.map +0 -1
  167. package/dist/tools/search.tools.js +0 -142
  168. package/dist/tools/search.tools.js.map +0 -1
  169. package/dist/tools/solution.tools.d.ts +0 -113
  170. package/dist/tools/solution.tools.d.ts.map +0 -1
  171. package/dist/tools/solution.tools.js +0 -176
  172. package/dist/tools/solution.tools.js.map +0 -1
  173. package/dist/tools/teams.tools.d.ts +0 -65
  174. package/dist/tools/teams.tools.d.ts.map +0 -1
  175. package/dist/tools/teams.tools.js +0 -127
  176. package/dist/tools/teams.tools.js.map +0 -1
  177. package/dist/tools/tool-registry.d.ts +0 -35
  178. package/dist/tools/tool-registry.d.ts.map +0 -1
  179. package/dist/tools/tool-registry.js +0 -31
  180. package/dist/tools/tool-registry.js.map +0 -1
  181. package/dist/tools/trace.tools.d.ts +0 -75
  182. package/dist/tools/trace.tools.d.ts.map +0 -1
  183. package/dist/tools/trace.tools.js +0 -233
  184. package/dist/tools/trace.tools.js.map +0 -1
  185. package/dist/tools/tracking.tools.d.ts +0 -41
  186. package/dist/tools/tracking.tools.d.ts.map +0 -1
  187. package/dist/tools/tracking.tools.js +0 -76
  188. package/dist/tools/tracking.tools.js.map +0 -1
  189. package/dist/tools/users.tools.d.ts +0 -141
  190. package/dist/tools/users.tools.d.ts.map +0 -1
  191. package/dist/tools/users.tools.js +0 -321
  192. package/dist/tools/users.tools.js.map +0 -1
  193. package/dist/tools/validation.utils.d.ts +0 -6
  194. package/dist/tools/validation.utils.d.ts.map +0 -1
  195. package/dist/tools/validation.utils.js +0 -14
  196. package/dist/tools/validation.utils.js.map +0 -1
  197. package/dist/tools/views.tools.d.ts +0 -36
  198. package/dist/tools/views.tools.d.ts.map +0 -1
  199. package/dist/tools/views.tools.js +0 -92
  200. package/dist/tools/views.tools.js.map +0 -1
  201. package/dist/tools/workflow.tools.d.ts +0 -111
  202. package/dist/tools/workflow.tools.d.ts.map +0 -1
  203. package/dist/tools/workflow.tools.js +0 -449
  204. package/dist/tools/workflow.tools.js.map +0 -1
  205. package/dist/transport.d.ts +0 -6
  206. package/dist/transport.d.ts.map +0 -1
  207. package/dist/transport.js +0 -21
  208. package/dist/transport.js.map +0 -1
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 MCP Dataverse Contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MCP Dataverse Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,113 +1,114 @@
1
- # MCP Dataverse
2
-
3
- <div align="center">
4
-
5
- <img src="assets/logo.webp" alt="MCP Dataverse" width="180" />
6
-
7
- **The most complete MCP server for Microsoft Dataverse.**
8
-
9
- 63 tools · 4 resources · 10 guided workflows · Zero config auth
10
-
11
- [![npm](https://img.shields.io/npm/v/mcp-dataverse)](https://www.npmjs.com/package/mcp-dataverse)
12
- [![npm downloads](https://img.shields.io/npm/dm/mcp-dataverse)](https://www.npmjs.com/package/mcp-dataverse)
13
- [![Node 20+](https://img.shields.io/badge/Node.js-20%2B-green)](https://nodejs.org)
14
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.4-blue)](https://www.typescriptlang.org)
15
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow)](LICENSE)
16
-
17
- **[→ Full Documentation](https://codeurali.github.io/mcp-dataverse)**
18
-
19
- </div>
20
-
21
- ---
22
-
23
- ## Why MCP Dataverse?
24
-
25
- AI agents hallucinate schema, guess column names, and build broken OData queries. This server gives them **real-time access** to your Dataverse environment — schema, records, metadata, solutions — through the [Model Context Protocol](https://modelcontextprotocol.io).
26
-
27
- - **No Azure AD app registration** — device code flow, zero pre-configuration
28
- - **Works with any MCP client**VS Code, Claude, Cursor, Windsurf, Gemini, Codex CLI
29
- - **Atomic tools** — each tool does one thing well; the AI picks the right one
30
- - **Structured outputs** — every response returns `{summary, data, suggestions}`
31
- - **Guardrails** — destructive operations require explicit confirmation
32
- - **Encrypted tokens** — AES-256-GCM cached credentials, never logged
33
-
34
- ---
35
-
36
- ## Install
37
-
38
- ```bash
39
- npx mcp-dataverse install
40
- ```
41
-
42
- The interactive wizard configures your environment, registers the server in VS Code, and authenticates your Microsoft account in under 2 minutes.
43
-
44
- > Requires Node.js 20+. For other clients (Claude, Cursor, Windsurf…) see [Multi-Client Setup](https://codeurali.github.io/mcp-dataverse/multi-client-setup).
45
-
46
- ---
47
-
48
- ## Authentication
49
-
50
- **No PAC CLI, no app registration, no client secret.** Uses Microsoft's device code flow (MSAL):
51
-
52
- 1. **First tool call** → a sign-in code appears in the MCP Output panel (`View → Output → MCP`)
53
- 2. Open `https://microsoft.com/devicelogin` enter the code sign in with your work account
54
- 3. **Done.** Token is cached encrypted — all future starts are silent
55
-
56
- Re-authenticate after ~90 days of inactivity: `npx mcp-dataverse-auth`
57
-
58
- ---
59
-
60
- ## Capabilities
61
-
62
- | Category | Count | Description |
63
- | ----------------------- | ----- | -------------------------------------------------------------- |
64
- | **Metadata** | 8 | Tables, schema, relationships, option sets, entity keys |
65
- | **Query** | 3 | OData, FetchXML, paginated retrieval |
66
- | **CRUD** | 6 | Get, create, update, delete, upsert, assign |
67
- | **Actions & Functions** | 6 | Bound/unbound Dataverse actions and functions |
68
- | **Batch** | 1 | Up to 1000 operations atomically |
69
- | **Solutions** | 3 | List solutions, components, publish customizations |
70
- | **Search** | 1 | Full-text Relevance Search |
71
- | **Users & Teams** | 3 | Users, roles, teams |
72
- | **Files** | 2 | Upload/download file and image columns |
73
- | **+ more** | | Audit, trace logs, delta tracking, impersonation, annotations… |
74
- | **Assistance** | 4 | Tool router, workflow guide |
75
-
76
- [→ Full Capabilities Reference](https://codeurali.github.io/mcp-dataverse/capabilities)
77
-
78
- ---
79
-
80
- ## Troubleshooting
81
-
82
- | Symptom | Fix |
83
- | ---------------------------------- | ------------------------------------------------------------------ |
84
- | No sign-in prompt | Open **View → Output → MCP** — the device code is displayed there |
85
- | `No MSAL accounts found` | Run `npx mcp-dataverse-auth` then restart the server |
86
- | `Authentication timed out` | 5-minute window expired — restart MCP for a new code |
87
- | Server not appearing in Agent mode | Run `npx mcp-dataverse install` or `npx mcp-dataverse doctor` |
88
- | HTTP errors | Run `npx mcp-dataverse doctor` to diagnose config and connectivity |
89
-
90
- ---
91
-
92
- ## Battle-Tested
93
-
94
- All 63 tools tested on a real Dataverse production environment across 8 live sessions — **55 ✅ · 9 ⚠️ · 1 ❌ (env-specific)**. [Details & test results →](https://codeurali.github.io/mcp-dataverse/community)
95
-
96
- ---
97
-
98
- ## Roadmap
99
-
100
- | Version | Feature | Status |
101
- | ------- | ------- | ------ |
102
- | **v0.4** | Streamable HTTP transport | 🟢 90% done |
103
- | **v0.5** | MCP Resources — browsable schema | 🟡 40% done |
104
- | **v0.6** | MCP Prompts — workflow templates | 🔴 Planned |
105
- | **v1.0** | On-Behalf-Of auth (OBO) | 🔴 Planned |
106
-
107
- [→ Full Roadmap](https://codeurali.github.io/mcp-dataverse/roadmap)
108
-
109
- ---
110
-
111
- ## License
112
-
113
- [MIT](LICENSE) © [Ali Taggaz](https://www.linkedin.com/in/alitaggaz/)
1
+ # MCP Dataverse
2
+
3
+ <div align="center">
4
+
5
+ <img src="assets/logo.webp" alt="MCP Dataverse" width="180" />
6
+
7
+ **The most complete MCP server for Microsoft Dataverse.**
8
+
9
+ Give your AI agents real-time access to Dataverse query records with OData & FetchXML, manage metadata, inspect solutions, run batch operations, audit changes, and more. Zero-config auth via Microsoft device code flow.
10
+
11
+ 63 tools · 4 resources · 10 guided workflows · Zero config auth
12
+
13
+ [![npm](https://img.shields.io/npm/v/mcp-dataverse)](https://www.npmjs.com/package/mcp-dataverse)
14
+ [![npm downloads](https://img.shields.io/npm/dm/mcp-dataverse)](https://www.npmjs.com/package/mcp-dataverse)
15
+ [![CI](https://github.com/codeurali/mcp-dataverse/actions/workflows/ci.yml/badge.svg)](https://github.com/codeurali/mcp-dataverse/actions/workflows/ci.yml)
16
+ [![Node 20+](https://img.shields.io/badge/Node.js-20%2B-green)](https://nodejs.org)
17
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.4-blue)](https://www.typescriptlang.org)
18
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow)](LICENSE)
19
+
20
+ **[→ Full Documentation](https://codeurali.github.io/mcp-dataverse)**
21
+
22
+ </div>
23
+
24
+ ---
25
+
26
+ ## Why MCP Dataverse?
27
+
28
+ AI agents hallucinate schema, guess column names, and build broken OData queries. This server gives them **real-time access** to your Dataverse environmentschema, records, metadata, solutions through the [Model Context Protocol](https://modelcontextprotocol.io).
29
+
30
+ - **No Azure AD app registration** — device code flow, zero pre-configuration
31
+ - **Works with any MCP client** — VS Code, Claude, Cursor, Windsurf, Gemini, Codex CLI
32
+ - **Atomic tools** — each tool does one thing well; the AI picks the right one
33
+ - **Structured outputs** — every response returns `{summary, data, suggestions}`
34
+ - **Guardrails** — destructive operations require explicit confirmation
35
+ - **Encrypted tokens** — AES-256-GCM cached credentials, never logged
36
+
37
+ ---
38
+
39
+ ## Install
40
+
41
+ ```bash
42
+ npx mcp-dataverse install
43
+ ```
44
+
45
+ The interactive wizard configures your environment, registers the server in VS Code, and authenticates your Microsoft account in under 2 minutes.
46
+
47
+ > Requires Node.js 20+. For other clients (Claude, Cursor, Windsurf…) see [Multi-Client Setup](https://codeurali.github.io/mcp-dataverse/multi-client-setup).
48
+
49
+ ---
50
+
51
+ ## Authentication
52
+
53
+ **No PAC CLI, no app registration, no client secret.** Uses Microsoft's device code flow (MSAL):
54
+
55
+ 1. **First tool call** → a sign-in code appears in the MCP Output panel (`View → Output → MCP`)
56
+ 2. Open `https://microsoft.com/devicelogin` enter the code → sign in with your work account
57
+ 3. **Done.** Token is cached encrypted — all future starts are silent
58
+
59
+ Re-authenticate after ~90 days of inactivity: `npx mcp-dataverse-auth`
60
+
61
+ ---
62
+
63
+ ## Capabilities
64
+
65
+ | Category | Count | Description |
66
+ | ----------------------- | ----- | -------------------------------------------------------------- |
67
+ | **Metadata** | 8 | Tables, schema, relationships, option sets, entity keys |
68
+ | **Query** | 3 | OData, FetchXML, paginated retrieval |
69
+ | **CRUD** | 6 | Get, create, update, delete, upsert, assign |
70
+ | **Actions & Functions** | 6 | Bound/unbound Dataverse actions and functions |
71
+ | **Batch** | 1 | Up to 1000 operations atomically |
72
+ | **Solutions** | 3 | List solutions, components, publish customizations |
73
+ | **Search** | 1 | Full-text Relevance Search |
74
+ | **Users & Teams** | 3 | Users, roles, teams |
75
+ | **Files** | 2 | Upload/download file and image columns |
76
+ | **+ more** | … | Audit, trace logs, delta tracking, impersonation, annotations… |
77
+ | **Assistance** | 4 | Tool router, workflow guide |
78
+
79
+ [→ Full Capabilities Reference](https://codeurali.github.io/mcp-dataverse/capabilities)
80
+
81
+ ---
82
+
83
+ ## Troubleshooting
84
+
85
+ | Symptom | Fix |
86
+ | ---------------------------------- | ------------------------------------------------------------------ |
87
+ | No sign-in prompt | Open **View Output MCP** the device code is displayed there |
88
+ | `No MSAL accounts found` | Run `npx mcp-dataverse-auth` then restart the server |
89
+ | `Authentication timed out` | Restart the MCP server — a fresh code is generated automatically |
90
+ | Server not appearing in Agent mode | Run `npx mcp-dataverse install` or `npx mcp-dataverse doctor` |
91
+ | HTTP errors | Run `npx mcp-dataverse doctor` to diagnose config and connectivity |
92
+
93
+ ---
94
+
95
+ ## Performance Tip
96
+
97
+ MCP Dataverse is designed to be comprehensive, but most AI models work best with fewer tools in context. **Deselect the tools you don't need** in your client's tool picker (e.g. VS Code Chat panel) to keep the agent focused and responsive.
98
+
99
+ ---
100
+
101
+ ## Roadmap
102
+
103
+ | Version | Feature | Status |
104
+ | ------- | ------- | ------ |
105
+ | **v0.4** | HTTP transport + schema consistency + new auth methods | 🟢 In progress |
106
+ | **v0.5** | MCP Prompts — workflow templates | 🔴 Planned |
107
+
108
+ [→ Full Roadmap](https://codeurali.github.io/mcp-dataverse/roadmap)
109
+
110
+ ---
111
+
112
+ ## License
113
+
114
+ [MIT](LICENSE) © [Ali Taggaz](https://www.linkedin.com/in/alitaggaz/)
@@ -0,0 +1 @@
1
+ import{a}from"./chunk-PAX4NW5B.js";import"./chunk-24RDOMG4.js";export{a as createAuthProvider};
@@ -0,0 +1,29 @@
1
+ import{PublicClientApplication as p}from"@azure/msal-node";import{existsSync as d,mkdirSync as m,readFileSync as f,writeFileSync as g}from"fs";import{homedir as v}from"os";import{join as u}from"path";import{exec as y}from"child_process";import{platform as c}from"process";import{createCipheriv as w,createDecipheriv as C,createHash as k,randomBytes as T}from"crypto";function A(n){let e=c==="win32"?`echo|set /p="${n}"| clip`:c==="darwin"?`printf '%s' '${n}' | pbcopy`:`printf '%s' '${n}' | xclip -selection clipboard 2>/dev/null || printf '%s' '${n}' | xsel --clipboard 2>/dev/null`;y(e,()=>{})}var E="1950a258-227b-4e31-a9cf-717495945fc2",h=u(v(),".mcp-dataverse"),a=u(h,"msal-cache.json"),S=300*1e3;function l(){let n=[process.env.COMPUTERNAME??process.env.HOSTNAME??"",process.env.USERNAME??process.env.USER??"","mcp-dataverse-cache-v1"].join(".");return k("sha256").update(n).digest()}function P(n){let e=l(),t=T(16),r=w("aes-256-gcm",e,t),i=Buffer.concat([r.update(n,"utf-8"),r.final()]);return JSON.stringify({v:1,iv:t.toString("hex"),tag:r.getAuthTag().toString("hex"),d:i.toString("hex")})}function x(n){let e=JSON.parse(n);if(e.v!==1)throw new Error("Unknown cache format version");let t=Buffer.from(e.iv,"hex"),r=Buffer.from(e.tag,"hex"),i=Buffer.from(e.d,"hex"),o=C("aes-256-gcm",l(),t);return o.setAuthTag(r),o.update(i).toString("utf-8")+o.final("utf-8")}function b(){return{beforeCacheAccess:async n=>{if(d(a))try{let e=f(a,"utf-8"),t;try{t=x(e)}catch{t=e}n.tokenCache.deserialize(t)}catch{}},afterCacheAccess:async n=>{n.cacheHasChanged&&(m(h,{recursive:!0}),g(a,P(n.tokenCache.serialize()),{encoding:"utf-8",mode:384}))}}}var s=class{environmentUrl;pca;cachedToken=null;tokenExpiresAt=0;pendingAuth=null;constructor(e){this.environmentUrl=e.replace(/\/$/,""),this.pca=new p({auth:{clientId:E,authority:"https://login.microsoftonline.com/common"},cache:{cachePlugin:b()}})}async getToken(){let e=Date.now();return this.cachedToken!==null&&this.tokenExpiresAt>e+6e4?this.cachedToken:this.pendingAuth!==null?this.pendingAuth:(this.pendingAuth=this.refreshToken().finally(()=>{this.pendingAuth=null}),this.pendingAuth)}invalidateToken(){this.cachedToken=null,this.tokenExpiresAt=0}async isAuthenticated(){try{return await this.getToken(),!0}catch{return!1}}async setupViaDeviceCode(){await this.runDeviceCodeFlow()}async refreshToken(){if((await this.pca.getAllAccounts()).length===0){process.stderr.write(`
2
+ [mcp-dataverse] First-time authentication required.
3
+ Environment: ${this.environmentUrl}
4
+ Open the URL below in your browser to sign in.
5
+
6
+ `);try{return await this.runDeviceCodeFlow(),await this.acquireSilently()}catch(t){let r=t instanceof Error?t.message:String(t);throw new Error(`Authentication setup failed: ${r}
7
+
8
+ You can also authenticate manually:
9
+ npx mcp-dataverse-auth ${this.environmentUrl}
10
+ Then restart the MCP server in VS Code.`)}}try{return await this.acquireSilently()}catch{process.stderr.write(`
11
+ [mcp-dataverse] Session expired \u2014 re-authenticating.
12
+ Open the URL below in your browser to sign in again.
13
+
14
+ `);try{return await this.runDeviceCodeFlow(),await this.acquireSilently()}catch(t){this.cachedToken=null;let r=t instanceof Error?t.message:String(t);throw new Error(`Re-authentication failed: ${r}
15
+
16
+ To authenticate manually:
17
+ npx mcp-dataverse-auth ${this.environmentUrl}
18
+ Then restart the MCP server in VS Code.`)}}}async acquireSilently(){let e=await this.pca.getAllAccounts();if(e.length===0)throw new Error("No account found in cache after authentication.");let t=await this.pca.acquireTokenSilent({scopes:[`${this.environmentUrl}/.default`],account:e[0]});if(!t?.accessToken)throw new Error("Token acquisition returned an empty access token.");return this.cacheResult(t),t.accessToken}async runDeviceCodeFlow(){let e=await Promise.race([this.pca.acquireTokenByDeviceCode({scopes:[`${this.environmentUrl}/.default`],deviceCodeCallback:t=>{A(t.userCode),process.stderr.write(`
19
+ [mcp-dataverse] Sign in required
20
+
21
+ 1. Open ${t.verificationUri} in your browser
22
+ (use the browser profile linked to your Power Platform account)
23
+ 2. Paste the code: ${t.userCode} (already copied to your clipboard)
24
+ 3. Sign in with your work account
25
+
26
+ `)}}),new Promise((t,r)=>setTimeout(()=>r(new Error("Authentication timed out after 5 minutes. Please try again.")),S))]);e&&(this.cacheResult(e),process.stderr.write(`
27
+ [mcp-dataverse] Authenticated \u2713 Token cached \u2014 no sign-in needed next time.
28
+
29
+ `))}cacheResult(e){this.cachedToken=e.accessToken,this.tokenExpiresAt=e.expiresOn?.getTime()??Date.now()+3300*1e3}};export{s as a};
@@ -0,0 +1,37 @@
1
+ var y=class extends Error{constructor(e,n,s,i,r={}){super(e);this.status=n;this.data=s;this.code=i;this.responseHeaders=r;this.name="HttpError"}},R=class{baseURL;timeoutMs;defaultHeaders;tokenProvider;constructor(t){this.baseURL=t.baseURL.endsWith("/")?t.baseURL:t.baseURL+"/",this.timeoutMs=t.timeout??3e4,this.defaultHeaders={...t.headers},this.tokenProvider=t.tokenProvider??void 0}async get(t,e){return this.request("GET",t,void 0,e)}async post(t,e,n){return this.request("POST",t,e,n)}async patch(t,e,n){return this.request("PATCH",t,e,n)}async put(t,e,n){return this.request("PUT",t,e,n)}async delete(t,e){return this.request("DELETE",t,void 0,e)}resolveUrl(t){if(!t.startsWith("http"))return this.baseURL+t;let e=new URL(t),n=new URL(this.baseURL);if(e.origin!==n.origin)throw new y(`SSRF protection: request to '${e.origin}' blocked; only '${n.origin}' is permitted`,0,void 0,"SSRF_BLOCKED");return t}async request(t,e,n,s){let i=this.resolveUrl(e),r={...this.defaultHeaders,...s?.headers};this.tokenProvider&&(r.Authorization=`Bearer ${await this.tokenProvider()}`);let a=s?.timeoutMs??this.timeoutMs,u=new AbortController,c=setTimeout(()=>u.abort(),a);try{let o={method:t,headers:r,signal:u.signal};n!==void 0&&(o.body=typeof n=="string"?n:JSON.stringify(n));let d=await fetch(i,o),p={};if(d.headers.forEach((g,f)=>{p[f]=g}),!d.ok){let g=await d.text(),f;try{f=JSON.parse(g)}catch{f=g||void 0}throw new y(`Request failed with status ${d.status}`,d.status,f,void 0,p)}let m;if(s?.responseType==="text")m=await d.text();else{let g=await d.text();m=g?JSON.parse(g):{}}return{data:m,status:d.status,headers:p}}catch(o){throw o instanceof y?o:o instanceof DOMException&&o.name==="AbortError"?new y("Request timed out",0,void 0,"ECONNABORTED"):o}finally{clearTimeout(c)}}};function v(h){let t=h.indexOf(`\r
2
+ \r
3
+ `),e=h.indexOf(`
4
+
5
+ `);return t!==-1&&(e===-1||t<=e)?t+4:e!==-1?e+2:-1}function O(h,t){let e=[],n=h.split(`--${t}`);for(let s of n){let i=s.trim();if(!i||i==="--")continue;let r=v(s);if(r===-1)continue;let a=s.slice(r),u=v(a);if(u===-1)continue;let o=(a.trimStart().split(/\r?\n/)[0]??"").match(/^HTTP\/\d+\.\d+\s+(\d{3})/),d=o?parseInt(o[1],10):0,p=d>=200&&d<300,m=a.slice(u).trim();if(!m){e.push(p?null:{error:"Empty response body",status:d});continue}try{let g=JSON.parse(m);e.push(p?g:{error:g,status:d})}catch{e.push({error:m,status:d})}}return e}function l(h){return h.replace(/'/g,"''")}var P="9.2",q={opportunities:"opportunityid",territories:"territoryid",categories:"categoryid",activityparties:"activitypartyid",activitymimeattachments:"activitymimeattachmentid",queues:"queueid",queueitems:"queueitemid"},$=class{http;authProvider;maxRetries;constructor(t,e=3,n=3e4){this.authProvider=t,this.maxRetries=e,this.http=new R({baseURL:`${t.environmentUrl}/api/data/v${P}/`,timeout:n,headers:{"OData-MaxVersion":"4.0","OData-Version":"4.0",Accept:"application/json","Content-Type":"application/json; charset=utf-8"},tokenProvider:()=>t.getToken()})}async requestWithRetry(t,e=0){try{return await t()}catch(n){if(n instanceof y){if(n.status===401&&e===0)return this.authProvider.invalidateToken(),this.requestWithRetry(t,e+1);if([429,503,504].includes(n.status)&&e<this.maxRetries){let i=n.responseHeaders["retry-after"],r=i?parseInt(i,10)*1e3:Math.pow(2,e)*1e3;return await new Promise(a=>setTimeout(a,r)),this.requestWithRetry(t,e+1)}}throw this.formatError(n)}}formatError(t){if(t instanceof y){let e=t.data?.error;return e?new Error(`Dataverse error ${e.code??""}: ${e.message??"Unknown error"}`):t.code==="ECONNABORTED"?new Error("Request timed out. Check your Dataverse environment URL."):t}return t instanceof Error?t:new Error(String(t))}async whoAmI(){return this.requestWithRetry(async()=>{let t=await this.http.get("WhoAmI"),{UserId:e,BusinessUnitId:n,OrganizationId:s}=t.data,i="";try{i=(await this.http.get(`organizations(${s})?$select=name`)).data.name??""}catch{i=""}let r=this.authProvider.environmentUrl;return{UserId:e,BusinessUnitId:n,OrganizationId:s,OrganizationName:i,EnvironmentUrl:r}})}async listTables(t=!1){let s=["$select=LogicalName,SchemaName,DisplayName,EntitySetName,PrimaryIdAttribute,PrimaryNameAttribute,IsCustomEntity",t?"$filter=IsCustomEntity eq true":""].filter(Boolean).join("&");return this.requestWithRetry(()=>this.http.get(`EntityDefinitions?${s}`).then(i=>i.data.value))}async getTableMetadata(t,e=!0){let n=e?"$expand=Attributes":"",s=`EntityDefinitions(LogicalName='${l(t)}')${n?"?"+n:""}`;return this.requestWithRetry(()=>this.http.get(s).then(i=>i.data))}async getRelationships(t){let e=l(t),[n,s,i]=await Promise.all([this.requestWithRetry(()=>this.http.get(`EntityDefinitions(LogicalName='${e}')/OneToManyRelationships`).then(r=>r.data.value)),this.requestWithRetry(()=>this.http.get(`EntityDefinitions(LogicalName='${e}')/ManyToOneRelationships`).then(r=>r.data.value)),this.requestWithRetry(()=>this.http.get(`EntityDefinitions(LogicalName='${e}')/ManyToManyRelationships`).then(r=>r.data.value))]);return[...n,...s,...i]}async query(t,e={}){let n=a=>encodeURIComponent(a).replace(/%28/g,"(").replace(/%29/g,")").replace(/%2C/g,",").replace(/%27/g,"'").replace(/%40/g,"@"),s=[];e.select?.length&&s.push(`$select=${e.select.join(",")}`),e.filter&&s.push(`$filter=${n(e.filter)}`),e.orderby&&s.push(`$orderby=${n(e.orderby)}`),e.top&&s.push(`$top=${e.top}`),e.expand&&s.push(`$expand=${e.expand}`),e.count&&s.push("$count=true"),e.apply&&s.push(`$apply=${n(e.apply)}`);let i=`${t}${s.length?"?"+s.join("&"):""}`,r=e.formattedValues?{headers:{Prefer:'odata.include-annotations="OData.Community.Display.V1.FormattedValue"'}}:void 0;return this.requestWithRetry(()=>this.http.get(i,r).then(a=>a.data))}async executeFetchXml(t,e,n){let s=encodeURIComponent(e),i=n?{headers:{Prefer:'odata.include-annotations="OData.Community.Display.V1.FormattedValue"'}}:void 0;return this.requestWithRetry(()=>this.http.get(`${t}?fetchXml=${s}`,i).then(r=>r.data))}async getRecord(t,e,n,s){let i=[];n?.length&&i.push(`$select=${n.join(",")}`),s&&i.push(`$expand=${s}`);let r=i.length?`?${i.join("&")}`:"";return this.requestWithRetry(async()=>{let a=await this.http.get(`${t}(${e})${r}`,{headers:{Prefer:'odata.include-annotations="*"'}}),u=a.headers["odata-etag"]??a.data["@odata.etag"]??null;return{record:a.data,etag:u}})}async createRecord(t,e){return this.requestWithRetry(async()=>{let n=await this.http.post(t,e,{headers:{Prefer:"return=representation"}}),i=n.headers["odata-entityid"]?.match(/\(([^)]+)\)/)?.[1];if(i)return i;let r=n.data,a=r["@odata.id"]?.match(/\(([^)]+)\)/)?.[1];if(a)return a;let u=q[t]??t.replace(/s$/,"")+"id",c=r[u];return c||(n.headers.location?.match(/\(([^)]+)\)/)?.[1]??"")})}async updateRecord(t,e,n,s){await this.requestWithRetry(()=>this.http.patch(`${t}(${e})`,n,{headers:{"If-Match":s??"*"}}))}async deleteRecord(t,e){await this.requestWithRetry(()=>this.http.delete(`${t}(${e})`))}async upsertRecord(t,e,n,s,i="upsert",r){return this.requestWithRetry(async()=>{let a=r?`${t}(${r})`:`${t}(${l(e)}='${l(n)}')`,u={Prefer:"return=representation"};i==="createOnly"&&(u["If-None-Match"]="*"),i==="updateOnly"&&(u["If-Match"]="*");try{let c=await this.http.put(a,s,{headers:u}),o=c.status===201?"created":"updated",p=c.headers["odata-entityid"]?.match(/\(([^)]+)\)/)?.[1],m=c.data,g=q[t]??t.replace(/s$/,"")+"id",f=p??m?.[g]??n;return{operation:o,id:f}}catch(c){if(c instanceof y&&c.status===412){if(i==="createOnly")throw new Error("Record already exists");if(i==="updateOnly")throw new Error("Record not found")}throw c}})}async associate(t,e,n,s,i){let r=`${this.authProvider.environmentUrl}/api/data/v${P}/${s}(${i})`;await this.requestWithRetry(()=>this.http.post(`${t}(${e})/${n}/$ref`,{"@odata.id":r}))}async disassociate(t,e,n,s,i){let r=s?`?$id=${this.authProvider.environmentUrl}/api/data/v${P}/${i??t}(${s})`:"";await this.requestWithRetry(()=>this.http.delete(`${t}(${e})/${n}/$ref${r}`))}};var w=class extends ${async executeAction(t,e={}){return this.requestWithRetry(()=>this.http.post(t,e).then(n=>n.data))}async executeFunction(t,e={}){let n=Object.entries(e).map(([i,r])=>`${l(i)}='${l(r)}'`).join(","),s=n?`${t}(${n})`:`${t}()`;return this.requestWithRetry(()=>this.http.get(s).then(i=>i.data))}async executeBoundAction(t,e,n,s={}){return this.requestWithRetry(()=>this.http.post(`${t}(${e})/Microsoft.Dynamics.CRM.${n}`,s).then(i=>i.data))}};var b=class extends w{async listDependencies(t,e){return this.requestWithRetry(()=>this.http.get(`RetrieveDependenciesForDelete(ComponentType=${t},ObjectId=${e})`).then(n=>n.data.value))}async listTableDependencies(t,e){let n={0:"Workflow",1:"Dialog",2:"BusinessRule",3:"Action",4:"BusinessProcessFlow",5:"Flow"},s={0:"Draft",1:"Active",2:"Inactive"},r=(await this.requestWithRetry(()=>this.http.get(`workflows?$filter=primaryentity eq '${l(t)}' and statecode ne 2&$select=name,workflowid,statecode,category,triggeroncreate,triggerondelete,triggeronupdateattributelist`).then(o=>o.data))).value.map(o=>{let d=[];return o.triggeroncreate&&d.push("Create"),o.triggerondelete&&d.push("Delete"),o.triggeronupdateattributelist&&d.push("Update"),{componentType:n[o.category]??`Category${o.category}`,name:o.name,id:o.workflowid,state:s[o.statecode]??`State${o.statecode}`,triggerEvent:d.length?d.join(","):null,solutionName:null}}),a=e?.length?r.filter(o=>e.includes(o.componentType)):r,c=e?.some(o=>o==="Plugin"||o==="CustomAPI")?"Plugin and CustomAPI types require additional SDK message queries and are not yet implemented. Results show Workflow/BusinessRule/Flow/Action dependencies only.":null;return{tableName:t,dependencies:a,count:a.length,warning:c}}async listGlobalOptionSets(){return this.requestWithRetry(()=>this.http.get("GlobalOptionSetDefinitions").then(t=>t.data.value))}async getOptionSet(t){return this.requestWithRetry(()=>this.http.get(`GlobalOptionSetDefinitions(Name='${l(t)}')`).then(e=>e.data))}async getAttributeOptionSet(t,e){let n=["PicklistAttributeMetadata","StatusAttributeMetadata","StateAttributeMetadata"];for(let s of n)try{let i=`EntityDefinitions(LogicalName='${l(t)}')/Attributes(LogicalName='${l(e)}')/Microsoft.Dynamics.CRM.${s}?$select=LogicalName,DisplayName&$expand=OptionSet`,c=((await this.requestWithRetry(()=>this.http.get(i).then(o=>o.data))).OptionSet?.Options??[]).map(o=>({label:o.Label?.UserLocalizedLabel?.Label??"",value:o.Value}));return{entityLogicalName:t,attributeLogicalName:e,attributeType:s.replace("AttributeMetadata",""),options:c}}catch{continue}throw new Error(`Attribute '${e}' on entity '${t}' is not a Picklist, Status, or State attribute, or does not exist.`)}async getEntityKeys(t){return this.requestWithRetry(async()=>(await this.http.get(`EntityDefinitions(LogicalName='${l(t)}')/Keys?$select=SchemaName,LogicalName,KeyAttributes,IsCustomizable,EntityKeyIndexStatus`)).data.value.map(n=>({schemaName:n.SchemaName,logicalName:n.LogicalName,keyAttributes:n.KeyAttributes,isCustomizable:n.IsCustomizable?.Value??!1,indexStatus:n.EntityKeyIndexStatus})))}async updateEntityDefinition(t,e){await this.requestWithRetry(()=>this.http.patch(`EntityDefinitions(LogicalName='${l(t)}')`,e,{headers:{"If-Match":"*"}}))}async createAttribute(t,e){return this.requestWithRetry(async()=>(await this.http.post(`EntityDefinitions(LogicalName='${l(t)}')/Attributes`,e,{headers:{Prefer:"return=representation"}})).data.MetadataId??"")}async updateAttribute(t,e,n){await this.requestWithRetry(()=>this.http.put(`EntityDefinitions(LogicalName='${l(t)}')/Attributes(LogicalName='${l(e)}')`,n))}async deleteAttribute(t,e){await this.requestWithRetry(()=>this.http.delete(`EntityDefinitions(LogicalName='${l(t)}')/Attributes(LogicalName='${l(e)}')`))}};var T=class extends b{async batchExecute(t,e=!1){let n=`batch_${Date.now()}`,s="";if(e){let r=`changeset_${Date.now()+1}`,a=t.filter(c=>c.method==="GET"),u=t.filter(c=>c.method!=="GET");for(let c of a)s+=`--${n}
6
+ `,s+=`Content-Type: application/http
7
+ `,s+=`Content-Transfer-Encoding: binary
8
+
9
+ `,s+=`${c.method} ${this.http.baseURL}${c.url} HTTP/1.1
10
+ `,s+=`Accept: application/json
11
+
12
+
13
+ `;if(u.length>0){s+=`--${n}
14
+ `,s+=`Content-Type: multipart/mixed; boundary=${r}
15
+
16
+ `;let c=1;for(let o of u)s+=`--${r}
17
+ `,s+=`Content-Type: application/http
18
+ `,s+=`Content-Transfer-Encoding: binary
19
+ `,s+=`Content-ID: ${o.contentId??c++}
20
+
21
+ `,s+=`${o.method} ${this.http.baseURL}${o.url} HTTP/1.1
22
+ `,s+=`Content-Type: application/json
23
+
24
+ `,o.body&&(s+=JSON.stringify(o.body)),s+=`
25
+
26
+ `;s+=`--${r}--
27
+ `}}else t.forEach(r=>{s+=`--${n}
28
+ `,s+=`Content-Type: application/http
29
+ `,s+=`Content-Transfer-Encoding: binary
30
+
31
+ `,s+=`${r.method} ${this.http.baseURL}${r.url} HTTP/1.1
32
+ `,s+=`Content-Type: application/json
33
+
34
+ `,r.body&&(s+=JSON.stringify(r.body)),s+=`
35
+ `});s+=`--${n}--`;let i=await this.requestWithRetry(()=>this.http.post("$batch",s,{headers:{"Content-Type":`multipart/mixed;boundary=${n}`},responseType:"text"}));try{let a=(i.headers["content-type"]??"").match(/boundary=(?:"([^"]+)"|([^;"\s]+))/),u=a?.[1]??a?.[2];return u?O(i.data,u):(process.stderr.write(`[batchExecute] No multipart boundary in response Content-Type; returning raw data.
36
+ `),[i.data])}catch(r){return process.stderr.write(`[batchExecute] Failed to parse multipart response; returning raw data. ${String(r)}
37
+ `),[i.data]}}};var x={1:"Entity",2:"Attribute",3:"Relationship",9:"OptionSet",29:"Workflow",61:"SystemForm",71:"SiteMap",90:"PluginAssembly",92:"PluginType",97:"WebResource",95:"ServiceEndpoint",79:"ConnectionRole"};function k(h){return h.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;")}var E=class extends T{async executeBoundFunction(t,e,n,s={}){let i=Object.entries(s).map(([a,u])=>`${l(a)}='${l(u)}'`).join(","),r=`${t}(${e})/${n}(${i})`;return this.requestWithRetry(()=>this.http.get(r).then(a=>a.data))}async queryWithPaging(t,e={}){let n=Math.min(e.maxTotal??5e3,5e4),s=[],i=0,r={};e.select!==void 0&&(r.select=e.select),e.filter!==void 0&&(r.filter=e.filter),e.orderby!==void 0&&(r.orderby=e.orderby),e.expand!==void 0&&(r.expand=e.expand);let a=await this.query(t,r);for(s.push(...a.value),i++;a["@odata.nextLink"]&&s.length<n;){let c=a["@odata.nextLink"];a=await this.requestWithRetry(()=>this.http.get(c).then(o=>o.data)),s.push(...a.value),i++}let u=s.slice(0,n);return{records:u,totalRetrieved:u.length,pageCount:i}}async getChangedRecords(t,e,n){let s,i={};if(e===null){let p=n?.length?`?$select=${n.join(",")}`:"";s=`${t}${p}`,i.Prefer="odata.track-changes"}else{let p=n?.length?`&$select=${n.join(",")}`:"";s=`${t}?$deltatoken=${e}${p}`}let r=await this.requestWithRetry(()=>this.http.get(s,{headers:i}).then(p=>p.data)),a=r.value??[],u=[],c=[];for(let p of a)if("@removed"in p){let m=String(p["@id"]??""),g=m.match(/\(([^)]+)\)$/);c.push({id:g?g[1]:m})}else u.push(p);let o=r["@odata.deltaLink"],d=null;if(o){let p=o.match(/\$deltatoken=([^&]+)/);d=p?decodeURIComponent(p[1]):null}return{newAndModified:u,deleted:c,nextDeltaToken:d}}async getSolutionComponents(t,e,n=200){return this.requestWithRetry(async()=>{let i=(await this.http.get(`solutions?$filter=uniquename eq '${l(t)}'&$select=solutionid,uniquename,friendlyname,version&$top=1`)).data.value;if(!i.length)throw new Error(`Solution '${t}' not found`);let r=i[0],a=r.solutionid,u=`_solutionid_value eq ${a}`;e!==void 0&&(u+=` and componenttype eq ${e}`);let o=(await this.http.get(`solutioncomponents?$filter=${u}&$select=componenttype,objectid&$top=${n}&$orderby=componenttype`)).data.value.map(d=>({componentType:d.componenttype,componentTypeName:x[d.componenttype]??`Type${d.componenttype}`,objectId:d.objectid}));return{solutionName:r.uniquename,solutionId:a,friendlyName:r.friendlyname,version:r.version,components:o,count:o.length}})}async publishCustomizations(t){return this.requestWithRetry(async()=>{if(!(t&&((t.entities?.length??0)>0||(t.webResources?.length??0)>0||(t.optionSets?.length??0)>0)))return await this.http.post("PublishAllXml",{},{timeoutMs:12e4}),{published:!0,message:"All customizations published successfully"};let n="<importexportxml>";return t.entities?.length&&(n+="<entities>"+t.entities.map(s=>`<entity>${k(s)}</entity>`).join("")+"</entities>"),t.webResources?.length&&(n+="<webresources>"+t.webResources.map(s=>`<webresource>${k(s)}</webresource>`).join("")+"</webresources>"),t.optionSets?.length&&(n+="<optionsets>"+t.optionSets.map(s=>`<optionset>${k(s)}</optionset>`).join("")+"</optionsets>"),n+="</importexportxml>",await this.http.post("PublishXml",{ParameterXml:n},{timeoutMs:12e4}),{published:!0,message:"Selected customizations published successfully"}})}async listDataverseWorkflows(t){return this.requestWithRetry(async()=>{let e=[];t.category!==void 0&&e.push(`category eq ${t.category}`),t.nameContains&&e.push(`contains(name,'${l(t.nameContains)}')`);let n=`workflows?$select=workflowid,name,description,category,statecode,statuscode,type,modifiedon&$orderby=name asc&$top=${t.top??50}`;return e.length>0&&(n+=`&$filter=${e.join(" and ")}`),(await this.http.get(n)).data.value??[]})}async getDataverseWorkflow(t){return this.requestWithRetry(()=>this.http.get(`workflows(${t})?$select=workflowid,name,description,category,statecode,statuscode,type,modifiedon`).then(e=>e.data))}};export{l as a,E as b};
@@ -0,0 +1 @@
1
+ import{a as r}from"./chunk-24RDOMG4.js";function i(e){return new r(e.environmentUrl)}export{i as a};
@@ -0,0 +1,3 @@
1
+ import{readFileSync as v,existsSync as f}from"fs";import{join as u}from"path";import{homedir as g}from"os";import{z as t}from"zod";var c=t.object({environmentUrl:t.string().url("Must be a valid Dataverse environment URL").refine(e=>e.startsWith("https://"),{message:"Dataverse environment URL must use HTTPS"}).refine(e=>{try{return new URL(e).hostname.toLowerCase().endsWith(".dynamics.com")}catch{return!1}},{message:"environmentUrl must be a *.dynamics.com host"}),requestTimeoutMs:t.number().positive().default(3e4),maxRetries:t.number().min(0).max(10).default(3)});var p="config.json";function S(){let e=u(g(),".mcp-dataverse",p),o=process.env.MCP_CONFIG_PATH??(f(e)?e:u(process.cwd(),p)),n={};if(f(o)){let r=v(o,"utf-8");try{n=JSON.parse(r)}catch{throw new Error(`Invalid JSON in ${o}. Check for syntax errors (trailing commas, missing quotes).`)}}let i=process.env.DATAVERSE_ENV_URL??process.env.environmentUrl;i&&(n.environmentUrl=i);let a=process.env.REQUEST_TIMEOUT_MS??process.env.requestTimeoutMs;a&&(n.requestTimeoutMs=Number(a));let m=process.env.MAX_RETRIES??process.env.maxRetries;m&&(n.maxRetries=Number(m));let s=c.safeParse(n);if(!s.success)throw new Error(`Invalid configuration:
2
+ ${s.error.issues.map(r=>` - ${r.path.join(".")}: ${r.message}`).join(`
3
+ `)}`);return s.data}export{S as a};
@@ -0,0 +1 @@
1
+ import{a}from"./chunk-SUDI4JM6.js";export{a as loadConfig};
@@ -0,0 +1 @@
1
+ import{b as a}from"./chunk-OXKMMPM3.js";export{a as DataverseAdvancedClient};
package/dist/doctor.js CHANGED
@@ -1,103 +1,3 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * Diagnostic CLI command for mcp-dataverse.
4
- * Usage: npx mcp-dataverse doctor
5
- */
6
- export async function runDoctor() {
7
- const out = (msg) => process.stdout.write(msg + "\n");
8
- const ok = (msg) => out(` ✅ ${msg}`);
9
- const fail = (msg) => out(` ❌ ${msg}`);
10
- out("");
11
- out("🏥 mcp-dataverse doctor");
12
- out("─".repeat(50));
13
- out("");
14
- let allPassed = true;
15
- // 1. Node.js version check
16
- out("📋 Environment");
17
- const nodeVersion = process.version;
18
- const major = parseInt(nodeVersion.slice(1).split(".")[0], 10);
19
- if (major >= 20) {
20
- ok(`Node.js ${nodeVersion}`);
21
- }
22
- else {
23
- fail(`Node.js ${nodeVersion} — requires v20+`);
24
- allPassed = false;
25
- }
26
- out("");
27
- // 2. Configuration check
28
- out("⚙️ Configuration");
29
- try {
30
- const { loadConfig } = await import("./config/config.loader.js");
31
- const config = loadConfig();
32
- ok(`Environment URL: ${config.environmentUrl}`);
33
- ok(`Timeout: ${config.requestTimeoutMs}ms, Retries: ${config.maxRetries}`);
34
- }
35
- catch (err) {
36
- const msg = err instanceof Error ? err.message : String(err);
37
- fail(`Configuration error: ${msg}`);
38
- allPassed = false;
39
- // Can't proceed without config
40
- out("");
41
- out(allPassed ? "✅ All checks passed!" : "❌ Some checks failed.");
42
- process.exit(allPassed ? 0 : 1);
43
- }
44
- out("");
45
- // 3. Authentication check
46
- out("🔑 Authentication");
47
- try {
48
- const { loadConfig } = await import("./config/config.loader.js");
49
- const config = loadConfig();
50
- const { createAuthProvider } = await import("./auth/auth-provider.factory.js");
51
- const authProvider = createAuthProvider(config);
52
- const token = await authProvider.getToken();
53
- if (token) {
54
- ok("Token acquired successfully");
55
- const parts = token.split(".");
56
- if (parts.length === 3) {
57
- ok("Valid JWT token format");
58
- }
59
- }
60
- else {
61
- fail("No token returned");
62
- allPassed = false;
63
- }
64
- }
65
- catch (err) {
66
- const msg = err instanceof Error ? err.message : String(err);
67
- fail(`Auth failed: ${msg}`);
68
- allPassed = false;
69
- }
70
- out("");
71
- // 4. API Connectivity (WhoAmI)
72
- out("🌐 Dataverse API");
73
- try {
74
- const { loadConfig } = await import("./config/config.loader.js");
75
- const config = loadConfig();
76
- const { createAuthProvider } = await import("./auth/auth-provider.factory.js");
77
- const { DataverseAdvancedClient } = await import("./dataverse/dataverse-client-advanced.js");
78
- const authProvider = createAuthProvider(config);
79
- const client = new DataverseAdvancedClient(authProvider, config.maxRetries, config.requestTimeoutMs);
80
- const result = await client.whoAmI();
81
- ok(`Organization: ${result.OrganizationName || "N/A"}`);
82
- ok(`User ID: ${result.UserId || "N/A"}`);
83
- ok(`Business Unit: ${result.BusinessUnitId || "N/A"}`);
84
- ok(`Environment: ${result.EnvironmentUrl || config.environmentUrl}`);
85
- }
86
- catch (err) {
87
- const msg = err instanceof Error ? err.message : String(err);
88
- fail(`API call failed: ${msg}`);
89
- allPassed = false;
90
- }
91
- out("");
92
- // Summary
93
- out("─".repeat(50));
94
- if (allPassed) {
95
- out("✅ All checks passed! Your mcp-dataverse setup is healthy.");
96
- }
97
- else {
98
- out("❌ Some checks failed. Review the errors above.");
99
- }
100
- out("");
101
- process.exit(allPassed ? 0 : 1);
102
- }
103
- //# sourceMappingURL=doctor.js.map
2
+ async function f(){let t=e=>process.stdout.write(e+`
3
+ `),o=e=>t(` \u2705 ${e}`),n=e=>t(` \u274C ${e}`);t(""),t("\u{1F3E5} mcp-dataverse doctor"),t("\u2500".repeat(50)),t("");let r=!0;t("\u{1F4CB} Environment");let c=process.version;parseInt(c.slice(1).split(".")[0],10)>=20?o(`Node.js ${c}`):(n(`Node.js ${c} \u2014 requires v20+`),r=!1),t(""),t("\u2699\uFE0F Configuration");try{let{loadConfig:e}=await import("./config.loader-VTIKUDN7.js"),s=e();o(`Environment URL: ${s.environmentUrl}`),o(`Timeout: ${s.requestTimeoutMs}ms, Retries: ${s.maxRetries}`)}catch(e){let s=e instanceof Error?e.message:String(e);n(`Configuration error: ${s}`),r=!1,t(""),t(r?"\u2705 All checks passed!":"\u274C Some checks failed."),process.exit(r?0:1)}t(""),t("\u{1F511} Authentication");try{let{loadConfig:e}=await import("./config.loader-VTIKUDN7.js"),s=e(),{createAuthProvider:l}=await import("./auth-provider.factory-MSMLSOX3.js"),i=await l(s).getToken();i?(o("Token acquired successfully"),i.split(".").length===3&&o("Valid JWT token format")):(n("No token returned"),r=!1)}catch(e){let s=e instanceof Error?e.message:String(e);n(`Auth failed: ${s}`),r=!1}t(""),t("\u{1F310} Dataverse API");try{let{loadConfig:e}=await import("./config.loader-VTIKUDN7.js"),s=e(),{createAuthProvider:l}=await import("./auth-provider.factory-MSMLSOX3.js"),{DataverseAdvancedClient:m}=await import("./dataverse-client-advanced-T5ZJMRLK.js"),i=l(s),a=await new m(i,s.maxRetries,s.requestTimeoutMs).whoAmI();o(`Organization: ${a.OrganizationName||"N/A"}`),o(`User ID: ${a.UserId||"N/A"}`),o(`Business Unit: ${a.BusinessUnitId||"N/A"}`),o(`Environment: ${a.EnvironmentUrl||s.environmentUrl}`)}catch(e){let s=e instanceof Error?e.message:String(e);n(`API call failed: ${s}`),r=!1}t(""),t("\u2500".repeat(50)),t(r?"\u2705 All checks passed! Your mcp-dataverse setup is healthy.":"\u274C Some checks failed. Review the errors above."),t(""),process.exit(r?0:1)}export{f as runDoctor};
@@ -1,61 +1,3 @@
1
- import { createServer } from "http";
2
- import { randomUUID } from "crypto";
3
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
- export async function startHttpTransport(server, port, version, toolCount) {
5
- const transport = new StreamableHTTPServerTransport({
6
- sessionIdGenerator: () => randomUUID(),
7
- enableJsonResponse: true,
8
- });
9
- await server.connect(transport);
10
- const httpServer = createServer(async (req, res) => {
11
- // CORS headers for browser-based clients
12
- res.setHeader("Access-Control-Allow-Origin", "*");
13
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
14
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id");
15
- if (req.method === "OPTIONS") {
16
- res.writeHead(204);
17
- res.end();
18
- return;
19
- }
20
- const url = new URL(req.url ?? "/", `http://localhost:${port}`);
21
- if (url.pathname === "/health" && req.method === "GET") {
22
- res.writeHead(200, { "Content-Type": "application/json" });
23
- res.end(JSON.stringify({ status: "ok", version, tools: toolCount }));
24
- return;
25
- }
26
- if (url.pathname === "/mcp") {
27
- try {
28
- await transport.handleRequest(req, res);
29
- }
30
- catch {
31
- if (!res.headersSent) {
32
- res.writeHead(500, { "Content-Type": "application/json" });
33
- res.end(JSON.stringify({ error: "Internal server error" }));
34
- }
35
- }
36
- return;
37
- }
38
- res.writeHead(404, { "Content-Type": "application/json" });
39
- res.end(JSON.stringify({ error: "Not found" }));
40
- });
41
- await new Promise((resolve) => {
42
- httpServer.listen(port, () => {
43
- process.stderr.write(`MCP Dataverse HTTP server listening on http://localhost:${port}/mcp\n`);
44
- resolve();
45
- });
46
- });
47
- // Keep the process alive; clean shutdown on signals
48
- const shutdown = async () => {
49
- process.stderr.write("Shutting down HTTP server...\n");
50
- await transport.close();
51
- httpServer.close();
52
- process.exit(0);
53
- };
54
- process.on("SIGINT", shutdown);
55
- process.on("SIGTERM", shutdown);
56
- // Block until server closes
57
- await new Promise((resolve) => {
58
- httpServer.on("close", resolve);
59
- });
60
- }
61
- //# sourceMappingURL=http-server.js.map
1
+ import{createServer as m}from"http";import{randomUUID as f}from"crypto";import{StreamableHTTPServerTransport as T}from"@modelcontextprotocol/sdk/server/streamableHttp.js";async function u(c,o,p,l){let t=new Map,r=m(async(n,s)=>{if(s.setHeader("Access-Control-Allow-Origin","*"),s.setHeader("Access-Control-Allow-Methods","GET, POST, DELETE, OPTIONS"),s.setHeader("Access-Control-Allow-Headers","Content-Type, mcp-session-id"),n.method==="OPTIONS"){s.writeHead(204),s.end();return}let a=new URL(n.url??"/",`http://localhost:${o}`);if(a.pathname==="/health"&&n.method==="GET"){s.writeHead(200,{"Content-Type":"application/json"}),s.end(JSON.stringify({status:"ok",version:p,tools:l}));return}if(a.pathname==="/mcp"){try{let d=n.headers["mcp-session-id"];if(d){let e=t.get(d);if(!e){s.writeHead(404,{"Content-Type":"application/json"}),s.end(JSON.stringify({error:"Session not found. Please reinitialize."}));return}await e.handleRequest(n,s)}else{let e=new T({sessionIdGenerator:()=>f(),enableJsonResponse:!0});e.sessionId&&(t.set(e.sessionId,e),e.onclose=()=>{e.sessionId&&t.delete(e.sessionId)}),await c().connect(e),await e.handleRequest(n,s),e.sessionId&&(t.has(e.sessionId)||(t.set(e.sessionId,e),e.onclose=()=>{e.sessionId&&t.delete(e.sessionId)}))}}catch{s.headersSent||(s.writeHead(500,{"Content-Type":"application/json"}),s.end(JSON.stringify({error:"Internal server error"})))}return}s.writeHead(404,{"Content-Type":"application/json"}),s.end(JSON.stringify({error:"Not found"}))});await new Promise(n=>{r.listen(o,()=>{process.stderr.write(`MCP Dataverse HTTP server listening on http://localhost:${o}/mcp
2
+ `),n()})});let i=async()=>{process.stderr.write(`Shutting down HTTP server...
3
+ `);for(let n of t.values())await n.close();r.close(),process.exit(0)};process.on("SIGINT",i),process.on("SIGTERM",i),await new Promise(n=>{r.on("close",n)})}export{u as startHttpTransport};