mcp-dataverse 0.4.5 → 0.5.0

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/CAPABILITIES.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # MCP Dataverse Server — Complete Capabilities Reference
2
2
 
3
- > **Version**: 0.4.5 | **API Version**: Dataverse Web API v9.2 | **Transport**: stdio · HTTP/SSE
3
+ > **Version**: 0.4.6 | **API Version**: Dataverse Web API v9.2 | **Transport**: stdio · HTTP/SSE
4
4
 
5
- 67 tools across 25 categories for full Dataverse lifecycle: schema, CRUD, FetchXML, solutions, plugins, audit, files, users, teams, RBAC, attribute management, environment variables, workflows, and more.
5
+ 73 tools across 25 categories for full Dataverse lifecycle: schema, CRUD, FetchXML, solutions, plugins, audit, files, users, teams, RBAC, attribute management, environment variables, workflows, and more.
6
6
 
7
7
  ---
8
8
 
@@ -10,19 +10,19 @@
10
10
 
11
11
  - [Quick Start](#quick-start)
12
12
  - [Architecture Overview](#architecture-overview)
13
- - [Tool Reference (63 tools)](#tool-reference-63-tools)
13
+ - [Tool Reference (73 tools)](#tool-reference-73-tools)
14
14
  - [1. Auth (1)](#1-auth-1-tool)
15
- - [2. Metadata (8)](#2-metadata-8-tools)
15
+ - [2. Metadata (9)](#2-metadata-9-tools)
16
16
  - [3. Query (3)](#3-query-3-tools)
17
- - [4. CRUD (5)](#4-crud-5-tools)
18
- - [5. Relations (2)](#5-relations-2-tools)
17
+ - [4. CRUD (6)](#4-crud-6-tools)
18
+ - [5. Relations (4)](#5-relations-4-tools)
19
19
  - [6. Actions & Functions (6)](#6-actions--functions-6-tools)
20
20
  - [7. Batch (1)](#7-batch-1-tool)
21
21
  - [8. Change Tracking (1)](#8-change-tracking-1-tool)
22
- - [9. Solutions (3)](#9-solutions-3-tools)
22
+ - [9. Solutions (2)](#9-solutions-2-tools)
23
23
  - [10. Impersonation (1)](#10-impersonation-1-tool)
24
- - [11. Customization (3)](#11-customization-3-tools)
25
- - [12. Environment (2)](#12-environment-2-tools)
24
+ - [11. Customization (4)](#11-customization-4-tools)
25
+ - [12. Environment (4)](#12-environment-4-tools)
26
26
  - [13. Trace (2)](#13-trace-2-tools)
27
27
  - [14. Search (1)](#14-search-1-tool)
28
28
  - [15. Audit (1)](#15-audit-1-tool)
@@ -31,10 +31,10 @@
31
31
  - [18. Users (2)](#18-users-2-tools)
32
32
  - [19. Views (1)](#19-views-1-tool)
33
33
  - [20. Files (2)](#20-files-2-tools)
34
- - [21. Org (1)](#21-org-1-tool)
35
- - [22. RBAC (3)](#22-rbac-3-tools)
36
- - [23. Workflows (2)](#23-workflows-2-tools)
37
- - [24. Assistance (4)](#24-assistance-4-tools)
34
+ - [21. Org (2)](#21-org-2-tools)
35
+ - [22. RBAC (7)](#22-rbac-7-tools)
36
+ - [23. Workflows (4)](#23-workflows-4-tools)
37
+ - [24. Assistance (2)](#24-assistance-2-tools)
38
38
  - [25. Attributes (4)](#25-attributes-4-tools)
39
39
  - [Error Handling & Retry Behavior](#error-handling--retry-behavior)
40
40
  - [Security](#security)
@@ -95,32 +95,32 @@ Server communicates over **stdio** (MCP SDK `StdioServerTransport`). Connect fro
95
95
 
96
96
  ```mermaid
97
97
  graph LR
98
- MCP["MCP Dataverse Server<br/><i>67 tools · 25 categories</i>"]
98
+ MCP["MCP Dataverse Server<br/><i>73 tools · 25 categories</i>"]
99
99
 
100
100
  MCP --> AUTH["🔑 Auth (1)"]
101
- MCP --> META["📋 Metadata (8)"]
101
+ MCP --> META["📋 Metadata (9)"]
102
102
  MCP --> QUERY["🔍 Query (3)"]
103
103
  MCP --> CRUD["✏️ CRUD (6)"]
104
- MCP --> REL["🔗 Relations (2)"]
104
+ MCP --> REL["🔗 Relations (4)"]
105
105
  MCP --> ACT["⚡ Actions & Functions (6)"]
106
106
  MCP --> BATCH["📦 Batch (1)"]
107
107
  MCP --> TRACK["🔄 Change Tracking (1)"]
108
- MCP --> SOL["🧩 Solutions (3)"]
108
+ MCP --> SOL["🧩 Solutions (2)"]
109
109
  MCP --> IMP["👤 Impersonation (1)"]
110
- MCP --> CUST["🔧 Customization (3)"]
111
- MCP --> ENV["⚙️ Environment (3)"]
110
+ MCP --> CUST["🔧 Customization (4)"]
111
+ MCP --> ENV["⚙️ Environment (4)"]
112
112
  MCP --> TRACE["🔎 Trace (2)"]
113
113
  MCP --> SRCH["🔍 Search (1)"]
114
114
  MCP --> AUDIT["📜 Audit (1)"]
115
115
  MCP --> QUAL["✅ Quality (1)"]
116
116
  MCP --> NOTE["📝 Annotations (2)"]
117
117
  MCP --> USR["👥 Users (2)"]
118
- MCP --> RBAC["🛡️ RBAC (3)"]
118
+ MCP --> RBAC["🛡️ RBAC (7)"]
119
119
  MCP --> VIEWS["👁️ Views (1)"]
120
120
  MCP --> FILES["📁 Files (2)"]
121
121
  MCP --> ORG["🏢 Org (2)"]
122
- MCP --> WF["⚙️ Workflows (2)"]
123
- MCP --> ASSIST["🤖 Assistance (4)"]
122
+ MCP --> WF["⚙️ Workflows (4)"]
123
+ MCP --> ASSIST["🤖 Assistance (2)"]
124
124
  MCP --> ATTR["🏗️ Attributes (4)"]
125
125
  ```
126
126
 
@@ -128,7 +128,7 @@ All tool handlers validate inputs with **Zod** before calling the `DataverseAdva
128
128
 
129
129
  ---
130
130
 
131
- ## Tool Reference (67 tools)
131
+ ## Tool Reference (73 tools)
132
132
 
133
133
  ### 1. Auth (1 tool)
134
134
 
@@ -140,7 +140,7 @@ Returns the current authenticated user context (userId, businessUnitId, organiza
140
140
 
141
141
  ---
142
142
 
143
- ### 2. Metadata (8 tools)
143
+ ### 2. Metadata (9 tools)
144
144
 
145
145
  #### `dataverse_list_tables`
146
146
 
@@ -279,7 +279,7 @@ Retrieves ALL matching records by auto-following `@odata.nextLink` pages. Use wh
279
279
 
280
280
  ---
281
281
 
282
- ### 4. CRUD (5 tools)
282
+ ### 4. CRUD (6 tools)
283
283
 
284
284
  #### `dataverse_get`
285
285
 
@@ -360,7 +360,22 @@ Create-or-update via an alternate key (no GUID needed). Returns `"created"` or `
360
360
 
361
361
  ---
362
362
 
363
- ### 5. Relations (2 tools)
363
+ #### `dataverse_assign`
364
+
365
+ Assigns a Dataverse record to a different user or team owner. Sets the `ownerid` lookup field using OData bind syntax.
366
+
367
+ | Parameter | Type | Req | Notes |
368
+ | --------------- | ---------------------- | --- | ----------------------- |
369
+ | `entitySetName` | `string` | ✓ | OData entity set name |
370
+ | `id` | `string (UUID)` | ✓ | Record GUID to reassign |
371
+ | `ownerType` | `"systemuser"\|"team"` | ✓ | Target owner type |
372
+ | `ownerId` | `string (UUID)` | ✓ | GUID of user or team |
373
+
374
+ > "Reassign account a1b2c3d4 to user u1v2w3x4"
375
+
376
+ ---
377
+
378
+ ### 5. Relations (4 tools)
364
379
 
365
380
  #### `dataverse_associate`
366
381
 
@@ -520,7 +535,7 @@ Delta-query for incremental sync. Pass `deltaToken: null` for initial snapshot;
520
535
 
521
536
  ---
522
537
 
523
- ### 9. Solutions (3 tools)
538
+ ### 9. Solutions (2 tools)
524
539
 
525
540
  #### `dataverse_list_solutions`
526
541
 
@@ -597,7 +612,7 @@ Executes any other tool on behalf of a different Dataverse user by injecting `MS
597
612
 
598
613
  ---
599
614
 
600
- ### 11. Customization (3 tools)
615
+ ### 11. Customization (4 tools)
601
616
 
602
617
  #### `dataverse_list_custom_actions`
603
618
 
@@ -657,7 +672,7 @@ Activates or deactivates a classic Dataverse workflow (statecode/statuscode upda
657
672
 
658
673
  ---
659
674
 
660
- ### 12. Environment (2 tools)
675
+ ### 12. Environment (4 tools)
661
676
 
662
677
  #### `dataverse_get_environment_variable`
663
678
 
@@ -694,6 +709,24 @@ Sets or updates an environment variable's current value (creates or updates the
694
709
 
695
710
  ---
696
711
 
712
+ #### `dataverse_create_environment_variable`
713
+
714
+ Creates a new Dataverse environment variable definition and sets its initial value. Use when the variable does not yet exist.
715
+
716
+ | Parameter | Type | Req | Notes |
717
+ | -------------- | ---------------------------------------- | --- | ------------------------------------------------------- |
718
+ | `schemaName` | `string` | ✓ | Schema name (publisher prefix required, e.g. `new_MyVar`) |
719
+ | `displayName` | `string` | ✓ | Human-readable label |
720
+ | `type` | `"String"\|"Integer"\|"Boolean"\|"JSON"` | ✓ | Variable data type |
721
+ | `value` | `string` | ✓ | Initial value |
722
+ | `description` | `string` | — | Optional description |
723
+ | `defaultValue` | `string` | — | Optional default value |
724
+ | `confirm` | `true` | ✓ | Explicit confirmation required |
725
+
726
+ > "Create environment variable new_MaxRetries of type Integer with value 3"
727
+
728
+ ---
729
+
697
730
  ### 13. Trace (2 tools)
698
731
 
699
732
  #### `dataverse_get_plugin_trace_logs`
@@ -977,7 +1010,7 @@ Lists Dataverse teams (owner teams and access teams) within one or all business
977
1010
 
978
1011
  ---
979
1012
 
980
- ### 22. RBAC (3 tools)
1013
+ ### 22. RBAC (7 tools)
981
1014
 
982
1015
  #### `dataverse_list_roles`
983
1016
 
@@ -1025,7 +1058,21 @@ Removes a security role from a system user.
1025
1058
 
1026
1059
  ---
1027
1060
 
1028
- ### 23. Workflows (2 tools)
1061
+ #### `dataverse_assign_role_to_team`
1062
+
1063
+ Assigns a security role to a Dataverse team. All team members inherit the role permissions.
1064
+
1065
+ | Parameter | Type | Req | Notes |
1066
+ | --------- | --------------- | --- | ------------------------------ |
1067
+ | `teamId` | `string (UUID)` | ✓ | Team GUID |
1068
+ | `roleId` | `string (UUID)` | ✓ | Security role GUID |
1069
+ | `confirm` | `true` | ✓ | Explicit confirmation required |
1070
+
1071
+ > "Assign role r1s2t3u4 to team t1e2a3m4"
1072
+
1073
+ ---
1074
+
1075
+ ### 23. Workflows (4 tools)
1029
1076
 
1030
1077
  #### `dataverse_list_workflows`
1031
1078
 
@@ -1053,7 +1100,7 @@ Retrieves a single workflow definition by ID, including its trigger, steps, and
1053
1100
 
1054
1101
  ---
1055
1102
 
1056
- ### 24. Assistance (4 tools)
1103
+ ### 24. Assistance (2 tools)
1057
1104
 
1058
1105
  #### `dataverse_suggest_tools`
1059
1106
 
@@ -1105,6 +1152,18 @@ Lists connection references used in solutions (Power Automate connectors).
1105
1152
 
1106
1153
  ---
1107
1154
 
1155
+ #### `dataverse_list_tool_tags`
1156
+
1157
+ Lists all available tool tags with the number of tools in each category. Use this to discover what kinds of operations are available before calling `dataverse_suggest_tools`.
1158
+
1159
+ | Parameter | Type | Req | Notes |
1160
+ | --------- | ---- | --- | ------------- |
1161
+ | — | — | — | No parameters |
1162
+
1163
+ > "What categories of tools are available in this MCP server?"
1164
+
1165
+ ---
1166
+
1108
1167
  ### 25. Attributes (4 tools)
1109
1168
 
1110
1169
  Attribute tools manage **column-level schema** in Dataverse tables. All write operations require `confirm: true` and auto-publish the entity definition by default.
@@ -1301,4 +1360,4 @@ Certain tools include an `errorCategory` field in the error text when the failur
1301
1360
 
1302
1361
  ---
1303
1362
 
1304
- _This document reflects the MCP Dataverse server codebase as of v0.4.067 tools across 25 categories._
1363
+ _This document reflects the MCP Dataverse server codebase as of v0.4.673 tools across 25 categories._
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  **The most complete MCP server for Microsoft Dataverse.**
8
8
 
9
- 67 tools · 4 resources · 10 guided workflows · Zero config auth
9
+ 73 tools · 4 resources · 10 guided workflows · Three auth modes
10
10
 
11
11
  [![npm](https://img.shields.io/npm/v/mcp-dataverse)](https://www.npmjs.com/package/mcp-dataverse)
12
12
  [![npm downloads](https://img.shields.io/npm/dm/mcp-dataverse)](https://www.npmjs.com/package/mcp-dataverse)
@@ -25,7 +25,7 @@
25
25
 
26
26
  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).
27
27
 
28
- - **No Azure AD app registration** — device code flow, zero pre-configuration
28
+ - **Three auth modes** — device code (local), client credentials (CI/CD), managed identity (Azure-hosted)
29
29
  - **Works with any MCP client** — VS Code, Claude, Cursor, Windsurf, Gemini, Codex CLI
30
30
  - **Atomic tools** — each tool does one thing well; the AI picks the right one
31
31
  - **Structured outputs** — every response returns `{summary, data, suggestions}`
@@ -48,31 +48,45 @@ The interactive wizard configures your environment, registers the server in VS C
48
48
 
49
49
  ## Authentication
50
50
 
51
- **No PAC CLI, no app registration, no client secret.** Uses Microsoft's device code flow (MSAL):
51
+ Three modes choose based on where the server runs:
52
52
 
53
- 1. **First tool call** a sign-in code appears in the MCP Output panel (`View → Output → MCP`)
54
- 2. Open `https://microsoft.com/devicelogin` → enter the code → sign in with your work account
55
- 3. **Done.** Token is cached encryptedall future starts are silent
53
+ | Mode | When to use |
54
+ |:-----|:------------|
55
+ | **Device Code** (default) | Local development interactive Microsoft login, token cached on disk |
56
+ | **Client Credentials** | Unattended: CI/CD, Docker, Azure services — `authMethod: "client-credentials"` + App Registration |
57
+ | **Managed Identity** | Azure-hosted (App Service, Container Apps) — zero secrets, `authMethod: "managed-identity"` |
56
58
 
57
- Re-authenticate after ~90 days of inactivity: `npx mcp-dataverse-auth`
59
+ **Device code quick start:** authentication triggers on the first tool call.
60
+
61
+ 1. Open `View → Output → MCP` — a sign-in code appears
62
+ 2. Go to `https://microsoft.com/devicelogin`, enter the code, sign in with your work account
63
+ 3. Token is cached encrypted — all future starts are silent
64
+
65
+ For client credentials and managed identity setup, see [Authentication docs](https://codeurali.github.io/mcp-dataverse/authentication).
58
66
 
59
67
  ---
60
68
 
61
69
  ## Capabilities
62
70
 
63
- | Category | Count | Description |
71
+ | Category | Count | Description |
64
72
  | ----------------------- | ----- | -------------------------------------------------------------- |
65
- | **Metadata** | 8 | Tables, schema, relationships, option sets, entity keys |
66
- | **Query** | 3 | OData, FetchXML, paginated retrieval |
67
- | **CRUD** | 6 | Get, create, update, delete, upsert, assign |
68
- | **Actions & Functions** | 6 | Bound/unbound Dataverse actions and functions |
69
- | **Batch** | 1 | Up to 1000 operations atomically |
70
- | **Solutions** | 3 | List solutions, components, publish customizations |
71
- | **Search** | 1 | Full-text Relevance Search |
72
- | **Users & Teams** | 3 | Users, roles, teams |
73
- | **Files** | 2 | Upload/download file and image columns |
74
- | **+ more** | | Audit, trace logs, delta tracking, impersonation, annotations… |
75
- | **Assistance** | 4 | Tool router, workflow guide |
73
+ | **Metadata** | 9 | Tables, schema, relationships, option sets, entity keys |
74
+ | **Query** | 3 | OData, FetchXML, paginated retrieval |
75
+ | **CRUD** | 6 | Get, create, update, delete, upsert, assign |
76
+ | **Relations** | 4 | Associate, associate bulk, disassociate, query associations |
77
+ | **Actions & Functions** | 6 | Bound/unbound Dataverse actions and functions |
78
+ | **Batch** | 1 | Up to 1000 operations atomically |
79
+ | **Solutions** | 2 | Publish customizations, create sitemap |
80
+ | **Search** | 1 | Full-text Relevance Search |
81
+ | **Users & Teams** | 4 | Users, roles, teams, role assignment |
82
+ | **RBAC** | 7 | Role privileges: list, assign, remove, add, replace, get, team |
83
+ | **Files** | 2 | Upload/download file and image columns |
84
+ | **Audit & Trace** | 3 | Audit log, plugin trace logs, workflow trace logs |
85
+ | **Annotations** | 2 | Notes and file attachments |
86
+ | **Customization** | 4 | Custom actions, plugins, env variables, connection references |
87
+ | **Attributes** | 5 | Create, update, delete columns; lookup and multi-select types |
88
+ | **Assistance** | 2 | Tool router, tool tags |
89
+ | **+ more** | … | Delta sync, impersonation, views, business units, duplicate detection |
76
90
 
77
91
  [→ Full Capabilities Reference](https://codeurali.github.io/mcp-dataverse/capabilities)
78
92
 
@@ -126,7 +140,8 @@ MCP Dataverse is designed to be comprehensive, but most AI models work best with
126
140
  | Version | Feature | Status |
127
141
  | ------- | ------- | ------ |
128
142
  | **v0.4** | HTTP transport + attribute management + schema consistency | ✅ Released |
129
- | **v0.5** | Enterprise auth (Client Credentials, Managed Identity) + MCP Prompts | 🔴 Planned |
143
+ | **v0.5** | Enterprise auth (Client Credentials, Managed Identity, Entra JWT) | Released |
144
+ | **v0.6** | MCP Prompts + ERD generator + API snippet generator | 🔜 Planned |
130
145
 
131
146
  [→ Full Roadmap](https://codeurali.github.io/mcp-dataverse/roadmap)
132
147
 
@@ -0,0 +1 @@
1
+ import{a}from"./chunk-M5UROAVJ.js";import"./chunk-RYRO3QPE.js";import"./chunk-KJ3HM2VM.js";export{a as createAuthProvider};
@@ -0,0 +1,35 @@
1
+ var y=class extends Error{constructor(e,n,s,r,i={}){super(e);this.status=n;this.data=s;this.code=r;this.responseHeaders=i;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 r=this.resolveUrl(e),i={...this.defaultHeaders,...s?.headers};this.tokenProvider&&(i.Authorization=`Bearer ${await this.tokenProvider()}`);let c=s?.timeoutMs??this.timeoutMs,u=new AbortController,a=setTimeout(()=>u.abort(),c);try{let o={method:t,headers:i,signal:u.signal};n!==void 0&&(o.body=typeof n=="string"?n:JSON.stringify(n));let d=await fetch(r,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(a)}}};function O(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 v(h,t){let e=[],n=h.split(`--${t}`);for(let s of n){let r=s.trim();if(!r||r==="--")continue;let i=O(s);if(i===-1)continue;let c=s.slice(i),u=O(c);if(u===-1)continue;let o=(c.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=c.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",x={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 r=n.responseHeaders["retry-after"],i=r?parseInt(r,10)*1e3:Math.pow(2,e)*1e3;return await new Promise(c=>setTimeout(c,i)),this.requestWithRetry(t,e+1)}if(n.status===400){let r=n.data?.error?.code;if(r==="0x80071151"&&e<5){let u=Math.pow(2,e)*5e3;return await new Promise(a=>setTimeout(a,u)),this.requestWithRetry(t,e+1)}let i=n.data?.error?.message?.toLowerCase()??"";if((r==="0x80044181"||i.includes("publish")&&(i.includes("already")||i.includes("in progress")||i.includes("another user")))&&e<4)return await new Promise(u=>setTimeout(u,3e4)),this.requestWithRetry(t,e+1)}}throw this.formatError(n)}}formatError(t){if(t instanceof y){let e=t.data?.error;return e?.code==="0x80060888"?new Error(`Dataverse error 0x80060888: The Web API endpoint does not exist in this organization. This means the action/function name is wrong OR the entity set name is incorrect \u2014 NOT necessarily a permissions error. Original message: ${e.message??""}. Tip: verify action names via $metadata or use dataverse_resolve_entity_name for entity names.`):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,r="";try{r=(await this.http.get(`organizations(${s})?$select=name`)).data.name??""}catch{r=""}let i=this.authProvider.environmentUrl;return{UserId:e,BusinessUnitId:n,OrganizationId:s,OrganizationName:r,EnvironmentUrl:i}})}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(r=>r.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(r=>r.data))}async fetchAllPagesOData(t){let e=[],n=t;for(;n;){let s=await this.requestWithRetry(()=>this.http.get(n).then(r=>r.data));e.push(...s.value??[]),n=s["@odata.nextLink"]}return e}async getRelationships(t){let e=l(t),[n,s,r]=await Promise.all([this.fetchAllPagesOData(`EntityDefinitions(LogicalName='${e}')/OneToManyRelationships`),this.fetchAllPagesOData(`EntityDefinitions(LogicalName='${e}')/ManyToOneRelationships`),this.fetchAllPagesOData(`EntityDefinitions(LogicalName='${e}')/ManyToManyRelationships`)]);return[...n,...s,...r]}async query(t,e={}){let n=c=>encodeURIComponent(c).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 r=`${t}${s.length?"?"+s.join("&"):""}`,i=e.formattedValues?{headers:{Prefer:'odata.include-annotations="OData.Community.Display.V1.FormattedValue"'}}:void 0;return this.requestWithRetry(()=>this.http.get(r,i).then(c=>c.data))}async executeFetchXml(t,e,n){let s=encodeURIComponent(e),r=n?{headers:{Prefer:'odata.include-annotations="OData.Community.Display.V1.FormattedValue"'}}:void 0;return this.requestWithRetry(()=>this.http.get(`${t}?fetchXml=${s}`,r).then(i=>i.data))}async getRecord(t,e,n,s){let r=[];n?.length&&r.push(`$select=${n.join(",")}`),s&&r.push(`$expand=${s}`);let i=r.length?`?${r.join("&")}`:"";return this.requestWithRetry(async()=>{let c=await this.http.get(`${t}(${e})${i}`,{headers:{Prefer:'odata.include-annotations="*"'}}),u=c.headers["odata-etag"]??c.data["@odata.etag"]??null;return{record:c.data,etag:u}})}async createRecord(t,e){return this.requestWithRetry(async()=>{let n=await this.http.post(t,e,{headers:{Prefer:"return=representation"}}),r=n.headers["odata-entityid"]?.match(/\(([^)]+)\)/)?.[1];if(r)return r;let i=n.data,c=i["@odata.id"]?.match(/\(([^)]+)\)/)?.[1];if(c)return c;let u=x[t]??t.replace(/s$/,"")+"id",a=i[u];return a||(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,r="upsert",i){return this.requestWithRetry(async()=>{let c=i?`${t}(${i})`:`${t}(${l(e)}='${l(n)}')`,u={Prefer:"return=representation"};r==="createOnly"&&(u["If-None-Match"]="*"),r==="updateOnly"&&(u["If-Match"]="*");try{let a=await this.http.put(c,s,{headers:u}),o=a.status===201?"created":"updated",p=a.headers["odata-entityid"]?.match(/\(([^)]+)\)/)?.[1],m=a.data,g=x[t]??t.replace(/s$/,"")+"id",f=p??m?.[g]??n;return{operation:o,id:f}}catch(a){if(a instanceof y&&a.status===412){if(r==="createOnly")throw new Error("Record already exists");if(r==="updateOnly")throw new Error("Record not found")}throw a}})}async associate(t,e,n,s,r){let i=`${this.authProvider.environmentUrl}/api/data/v${P}/${s}(${r})`;await this.requestWithRetry(()=>this.http.post(`${t}(${e})/${n}/$ref`,{"@odata.id":i}))}async disassociate(t,e,n,s,r){let i=s?`?$id=${this.authProvider.environmentUrl}/api/data/v${P}/${r??t}(${s})`:"";await this.requestWithRetry(()=>this.http.delete(`${t}(${e})/${n}/$ref${i}`))}};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=[],s=[],r=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;Object.entries(e).forEach(([u,a])=>{if(typeof a=="object"&&a!==null){let o=`@${u}`;s.push(`${l(u)}=${o}`),n.push(`${o}=${encodeURIComponent(JSON.stringify(a))}`)}else typeof a=="string"&&r.test(a)?s.push(`${l(u)}=${a}`):typeof a=="string"?s.push(`${l(u)}='${l(a)}'`):s.push(`${l(u)}=${String(a)}`)});let i=s.length?`${t}(${s.join(",")})`:`${t}()`,c=n.length?`${i}?${n.join("&")}`:i;return this.requestWithRetry(()=>this.http.get(c).then(u=>u.data))}async search(t){let n=`${this.http.baseURL.replace(/\/api\/data\/v[\d.]+\/?$/,"")}/api/search/v2.0/query`;return this.requestWithRetry(()=>this.http.post(n,t).then(s=>s.data))}async executeBoundAction(t,e,n,s={}){return this.requestWithRetry(()=>this.http.post(`${t}(${e})/Microsoft.Dynamics.CRM.${n}`,s).then(r=>r.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"},i=(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}}),c=e?.length?i.filter(o=>e.includes(o.componentType)):i,a=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:c,count:c.length,warning:a}}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","MultiSelectPicklistAttributeMetadata","StatusAttributeMetadata","StateAttributeMetadata"];for(let s of n)try{let r=`EntityDefinitions(LogicalName='${l(t)}')/Attributes(LogicalName='${l(e)}')/Microsoft.Dynamics.CRM.${s}?$select=LogicalName,DisplayName&$expand=OptionSet`,a=((await this.requestWithRetry(()=>this.http.get(r).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:a}}catch{continue}throw new Error(`Attribute '${e}' on entity '${t}' is not a Picklist, MultiSelectPicklist, 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:{"MSCRM.MergeLabels":"true"}}))}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,{headers:{"MSCRM.MergeLabels":"true"}}))}async deleteAttribute(t,e){await this.requestWithRetry(()=>this.http.delete(`EntityDefinitions(LogicalName='${l(t)}')/Attributes(LogicalName='${l(e)}')`))}async createRelationship(t){return this.requestWithRetry(async()=>(await this.http.post("RelationshipDefinitions",t)).headers["odata-entityid"]?.match(/\(([^)]+)\)$/)?.[1]??"")}};var T=class extends b{async batchExecute(t,e=!1){let n=`batch_${Date.now()}`,s="";if(e){let i=`changeset_${Date.now()+1}`,c=t.filter(a=>a.method==="GET"),u=t.filter(a=>a.method!=="GET");for(let a of c)s+=`--${n}\r
6
+ `,s+=`Content-Type: application/http\r
7
+ `,s+=`Content-Transfer-Encoding: binary\r
8
+ \r
9
+ `,s+=`${a.method} ${this.http.baseURL}${a.url} HTTP/1.1\r
10
+ `,s+=`Accept: application/json\r
11
+ \r
12
+ `;if(u.length>0){s+=`--${n}\r
13
+ `,s+=`Content-Type: multipart/mixed; boundary=${i}\r
14
+ \r
15
+ `;let a=1;for(let o of u)s+=`--${i}\r
16
+ `,s+=`Content-Type: application/http\r
17
+ `,s+=`Content-Transfer-Encoding: binary\r
18
+ `,s+=`Content-ID: ${o.contentId??a++}\r
19
+ \r
20
+ `,s+=`${o.method} ${this.http.baseURL}${o.url} HTTP/1.1\r
21
+ `,s+=`Content-Type: application/json\r
22
+ \r
23
+ `,o.body&&(s+=JSON.stringify(o.body)),s+=`\r
24
+ `;s+=`--${i}--\r
25
+ `}}else t.forEach(i=>{s+=`--${n}\r
26
+ `,s+=`Content-Type: application/http\r
27
+ `,s+=`Content-Transfer-Encoding: binary\r
28
+ \r
29
+ `,s+=`${i.method} ${this.http.baseURL}${i.url} HTTP/1.1\r
30
+ `,s+=`Content-Type: application/json\r
31
+ \r
32
+ `,i.body&&(s+=JSON.stringify(i.body)),s+=`\r
33
+ `});s+=`--${n}--`;let r=await this.requestWithRetry(()=>this.http.post("$batch",s,{headers:{"Content-Type":`multipart/mixed;boundary=${n}`},responseType:"text"}));try{let c=(r.headers["content-type"]??"").match(/boundary=(?:"([^"]+)"|([^;"\s]+))/),u=c?.[1]??c?.[2];return u?v(r.data,u):(process.stderr.write(`[batchExecute] No multipart boundary in response Content-Type; returning raw data.
34
+ `),[r.data])}catch(i){return process.stderr.write(`[batchExecute] Failed to parse multipart response; returning raw data. ${String(i)}
35
+ `),[r.data]}}};var E={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 q=class extends T{async executeBoundFunction(t,e,n,s={}){let r=[],i=[];Object.entries(s).forEach(([o,d])=>{if(typeof d=="object"&&d!==null){let p=`@${o}`;i.push(`${l(o)}=${p}`),r.push(`${p}=${encodeURIComponent(JSON.stringify(d))}`)}else typeof d=="string"?i.push(`${l(o)}='${l(d)}'`):i.push(`${l(o)}=${String(d)}`)});let c=i.join(","),u=`${t}(${e})/Microsoft.Dynamics.CRM.${n}(${c})`,a=r.length?`${u}?${r.join("&")}`:u;return this.requestWithRetry(()=>this.http.get(a).then(o=>o.data))}async queryWithPaging(t,e={}){let n=Math.min(e.maxTotal??5e3,5e4),s=[],r=0,i={};e.select!==void 0&&(i.select=e.select),e.filter!==void 0&&(i.filter=e.filter),e.orderby!==void 0&&(i.orderby=e.orderby),e.expand!==void 0&&(i.expand=e.expand);let c=await this.query(t,i);for(s.push(...c.value),r++;c["@odata.nextLink"]&&s.length<n;){let a=c["@odata.nextLink"];c=await this.requestWithRetry(()=>this.http.get(a).then(o=>o.data)),s.push(...c.value),r++}let u=s.slice(0,n);return{records:u,totalRetrieved:u.length,pageCount:r}}async getChangedRecords(t,e,n){let s,r={};if(e===null){let p=n?.length?`?$select=${n.join(",")}`:"";s=`${t}${p}`,r.Prefer="odata.track-changes"}else{let p=n?.length?`&$select=${n.join(",")}`:"";s=`${t}?$deltatoken=${e}${p}`}let i=await this.requestWithRetry(()=>this.http.get(s,{headers:r}).then(p=>p.data)),c=i.value??[],u=[],a=[];for(let p of c)if("@removed"in p){let m=String(p["@id"]??""),g=m.match(/\(([^)]+)\)$/);a.push({id:g?g[1]:m})}else u.push(p);let o=i["@odata.deltaLink"],d=null;if(o){let p=o.match(/\$deltatoken=([^&]+)/);d=p?decodeURIComponent(p[1]):null}return{newAndModified:u,deleted:a,nextDeltaToken:d}}async getSolutionComponents(t,e,n=200){return this.requestWithRetry(async()=>{let r=(await this.http.get(`solutions?$filter=uniquename eq '${l(t)}'&$select=solutionid,uniquename,friendlyname,version&$top=1`)).data.value;if(!r.length)throw new Error(`Solution '${t}' not found`);let i=r[0],c=i.solutionid,u=`_solutionid_value eq ${c}`;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:E[d.componenttype]??`Type${d.componenttype}`,objectId:d.objectid}));return{solutionName:i.uniquename,solutionId:c,friendlyName:i.friendlyname,version:i.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))}async executeFetchXmlAllPages(t,e){let n=[],s=1,r;do{let i=e;s>1&&(i=i.replace(/<fetch\b([^>]*)>/i,(o,d)=>{let p=d.replace(/\s+page="[^"]*"/g,"").replace(/\s+paging-cookie="[^"]*"/g,""),m=r?` paging-cookie="${r.replace(/"/g,"&quot;").replace(/'/g,"&apos;")}"`:"";return`<fetch${p} page="${s}"${m}>`}));let c=await this.executeFetchXml(t,i),u=c.value??[];n.push(...u);let a=c["@Microsoft.Dynamics.CRM.fetchxmlpagingcookie"];if(!a||u.length===0)break;try{let d=decodeURIComponent(decodeURIComponent(String(a))).match(/pagingcookie="([^"]+)"/);r=d?d[1]:void 0}catch{r=String(a)}if(!r||(s++,s>100))break}while(!0);return n}};export{l as a,q as b};
@@ -0,0 +1 @@
1
+ import{createCipheriv as c,createDecipheriv as a,createHash as g,randomBytes as p}from"crypto";function i(){let e=[process.env.COMPUTERNAME??process.env.HOSTNAME??"",process.env.USERNAME??process.env.USER??"","mcp-dataverse-cache-v1"].join(".");return g("sha256").update(e).digest()}function d(e){let t=i(),s=p(16),r=c("aes-256-gcm",t,s),o=Buffer.concat([r.update(e,"utf-8"),r.final()]),n={v:1,iv:s.toString("hex"),tag:r.getAuthTag().toString("hex"),d:o.toString("hex")};return JSON.stringify(n)}function u(e){let t=JSON.parse(e);if(t.v!==1)throw new Error("Unknown encrypted-blob format version");let s=Buffer.from(t.iv,"hex"),r=Buffer.from(t.tag,"hex"),o=Buffer.from(t.d,"hex"),n=a("aes-256-gcm",i(),s);return n.setAuthTag(r),n.update(o).toString("utf-8")+n.final("utf-8")}function v(e){try{let t=JSON.parse(e);return t.v===1&&typeof t.iv=="string"&&typeof t.tag=="string"&&typeof t.d=="string"}catch{return!1}}export{d as a,u as b,v as c};
@@ -0,0 +1,4 @@
1
+ import{a as o}from"./chunk-RYRO3QPE.js";import{ConfidentialClientApplication as c}from"@azure/msal-node";var r=class{environmentUrl;cca;scopes;cachedToken=null;tokenExpiresAt=0;pendingAuth=null;constructor(e,t,i,s){this.environmentUrl=e.replace(/\/$/,""),this.scopes=[`${this.environmentUrl}/.default`],this.cca=new c({auth:{clientId:i,authority:`https://login.microsoftonline.com/${t}`,clientSecret:s}})}async getToken(){let e=Date.now();return this.cachedToken!==null&&this.tokenExpiresAt>e+6e4?this.cachedToken:this.pendingAuth!==null?this.pendingAuth:(this.pendingAuth=this.acquireToken().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 acquireToken(){let e;try{e=await this.cca.acquireTokenByClientCredential({scopes:this.scopes})}catch(t){let i=t instanceof Error?t.message:String(t);throw new Error(`Client credentials token acquisition failed: ${i}
2
+ Verify that tenantId, clientId, and clientSecret are correct and that the app registration has been granted access to Dataverse.`)}if(!e?.accessToken)throw new Error("Client credentials flow returned no access token. Ensure the app registration has the Dataverse Application User configured.");return this.cachedToken=e.accessToken,this.tokenExpiresAt=e.expiresOn?e.expiresOn.getTime():Date.now()+36e5,this.cachedToken}};import{ManagedIdentityApplication as h}from"@azure/msal-node";var a=class{environmentUrl;mia;resource;cachedToken=null;tokenExpiresAt=0;pendingAuth=null;constructor(e,t){this.environmentUrl=e.replace(/\/$/,"");let{protocol:i,hostname:s}=new URL(this.environmentUrl);this.resource=`${i}//${s}`,this.mia=new h(t?{managedIdentityIdParams:{userAssignedClientId:t}}:{})}async getToken(){let e=Date.now();return this.cachedToken!==null&&this.tokenExpiresAt>e+6e4?this.cachedToken:this.pendingAuth!==null?this.pendingAuth:(this.pendingAuth=this.acquireToken().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 acquireToken(){let e;try{e=await this.mia.acquireToken({resource:this.resource})}catch(t){let i=t instanceof Error?t.message:String(t);throw new Error(`Managed Identity token acquisition failed: ${i}
3
+ This method only works on Azure-hosted environments (App Service, Container Apps, VMs, AKS).
4
+ Ensure the managed identity has been assigned an Application User role in Dataverse.`)}if(!e?.accessToken)throw new Error("Managed Identity flow returned no access token. Verify that the managed identity has been added as a Dataverse Application User.");return this.cachedToken=e.accessToken,this.tokenExpiresAt=e.expiresOn?e.expiresOn.getTime():Date.now()+36e5,this.cachedToken}};function A(n){switch(n.authMethod){case"client-credentials":return new r(n.environmentUrl,n.tenantId,n.clientId,n.clientSecret);case"managed-identity":return new a(n.environmentUrl,n.managedIdentityClientId);case"device-code":default:return new o(n.environmentUrl)}}export{A as a};
@@ -0,0 +1,3 @@
1
+ import{b as p,c as I}from"./chunk-KJ3HM2VM.js";import{readFileSync as C,existsSync as v}from"fs";import{join as g}from"path";import{homedir as S}from"os";import{z as t}from"zod";var h=t.object({environmentUrl:t.string().url("Must be a valid Dataverse environment URL").refine(n=>n.startsWith("https://"),{message:"Dataverse environment URL must use HTTPS"}).refine(n=>{try{return new URL(n).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),authMethod:t.enum(["device-code","client-credentials","managed-identity"]).default("device-code"),tenantId:t.string().min(1).optional(),clientId:t.string().min(1).optional(),clientSecret:t.string().min(1).optional(),managedIdentityClientId:t.string().min(1).optional()}).superRefine((n,s)=>{n.authMethod==="client-credentials"&&(n.tenantId||s.addIssue({code:t.ZodIssueCode.custom,path:["tenantId"],message:"tenantId is required when authMethod is 'client-credentials'"}),n.clientId||s.addIssue({code:t.ZodIssueCode.custom,path:["clientId"],message:"clientId is required when authMethod is 'client-credentials'"}),n.clientSecret||s.addIssue({code:t.ZodIssueCode.custom,path:["clientSecret"],message:"clientSecret is required when authMethod is 'client-credentials'. Prefer AZURE_CLIENT_SECRET env var over storing it in config.json."}))});var E="config.json";function w(){let n=g(S(),".mcp-dataverse",E),s=process.env.MCP_CONFIG_PATH??(v(n)?n:g(process.cwd(),E)),e={};if(v(s)){let i=C(s,"utf-8");try{e=JSON.parse(i)}catch{throw new Error(`Invalid JSON in ${s}. Check for syntax errors (trailing commas, missing quotes).`)}}let o=process.env.DATAVERSE_ENV_URL??process.env.environmentUrl;o&&(e.environmentUrl=o);let c=process.env.REQUEST_TIMEOUT_MS??process.env.requestTimeoutMs;c&&(e.requestTimeoutMs=Number(c));let a=process.env.MAX_RETRIES??process.env.maxRetries;a&&(e.maxRetries=Number(a));let d=process.env.AUTH_METHOD??process.env.authMethod;d&&(e.authMethod=d);let m=process.env.AZURE_TENANT_ID??process.env.tenantId;m&&(e.tenantId=m);let l=process.env.AZURE_CLIENT_ID??process.env.clientId;l&&(e.clientId=l);let u=process.env.AZURE_CLIENT_SECRET??process.env.clientSecret;if(u)e.clientSecret=u;else if(typeof e.clientSecret=="string"&&I(e.clientSecret))try{e.clientSecret=p(e.clientSecret)}catch{throw new Error("Failed to decrypt clientSecret from config.json. The config may have been created on a different machine or user account. Re-run 'npx mcp-dataverse install' or set AZURE_CLIENT_SECRET env var.")}let f=process.env.AZURE_MANAGED_IDENTITY_CLIENT_ID??process.env.managedIdentityClientId;f&&(e.managedIdentityClientId=f);let r=h.safeParse(e);if(!r.success)throw new Error(`Invalid configuration:
2
+ ${r.error.issues.map(i=>` - ${i.path.join(".")}: ${i.message}`).join(`
3
+ `)}`);return r.data}export{w as a};
@@ -0,0 +1,29 @@
1
+ import{a as o,b as a}from"./chunk-KJ3HM2VM.js";import{PublicClientApplication as u}from"@azure/msal-node";import{existsSync as p,mkdirSync as d,readFileSync as m,writeFileSync as f}from"fs";import{homedir as w}from"os";import{join as l}from"path";import{exec as y}from"child_process";import{platform as c}from"process";function v(n){let t=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(t,()=>{})}var g="1950a258-227b-4e31-a9cf-717495945fc2",h=l(w(),".mcp-dataverse"),r=l(h,"msal-cache.json"),C=300*1e3;function k(){return{beforeCacheAccess:async n=>{if(p(r))try{let t=m(r,"utf-8"),e;try{e=a(t)}catch{e=t}n.tokenCache.deserialize(e)}catch{}},afterCacheAccess:async n=>{n.cacheHasChanged&&(d(h,{recursive:!0}),f(r,o(n.tokenCache.serialize()),{encoding:"utf-8",mode:384}))}}}var s=class{environmentUrl;pca;cachedToken=null;tokenExpiresAt=0;pendingAuth=null;constructor(t){this.environmentUrl=t.replace(/\/$/,""),this.pca=new u({auth:{clientId:g,authority:"https://login.microsoftonline.com/common"},cache:{cachePlugin:k()}})}async getToken(){let t=Date.now();return this.cachedToken!==null&&this.tokenExpiresAt>t+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(e){let i=e instanceof Error?e.message:String(e);throw new Error(`Authentication setup failed: ${i}
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(e){this.cachedToken=null;let i=e instanceof Error?e.message:String(e);throw new Error(`Re-authentication failed: ${i}
15
+
16
+ To authenticate manually:
17
+ npx mcp-dataverse-auth ${this.environmentUrl}
18
+ Then restart the MCP server in VS Code.`)}}}async acquireSilently(){let t=await this.pca.getAllAccounts();if(t.length===0)throw new Error("No account found in cache after authentication.");let e=await this.pca.acquireTokenSilent({scopes:[`${this.environmentUrl}/.default`],account:t[0]});if(!e?.accessToken)throw new Error("Token acquisition returned an empty access token.");return this.cacheResult(e),e.accessToken}async runDeviceCodeFlow(){let t=await Promise.race([this.pca.acquireTokenByDeviceCode({scopes:[`${this.environmentUrl}/.default`],deviceCodeCallback:e=>{v(e.userCode),process.stderr.write(`
19
+ [mcp-dataverse] Sign in required
20
+
21
+ 1. Open ${e.verificationUri} in your browser
22
+ (use the browser profile linked to your Power Platform account)
23
+ 2. Paste the code: ${e.userCode} (already copied to your clipboard)
24
+ 3. Sign in with your work account
25
+
26
+ `)}}),new Promise((e,i)=>setTimeout(()=>i(new Error("Authentication timed out after 5 minutes. Please try again.")),C))]);t&&(this.cacheResult(t),process.stderr.write(`
27
+ [mcp-dataverse] Authenticated \u2713 Token cached \u2014 no sign-in needed next time.
28
+
29
+ `))}cacheResult(t){this.cachedToken=t.accessToken,this.tokenExpiresAt=t.expiresOn?.getTime()??Date.now()+3300*1e3}};export{s as a};
@@ -0,0 +1 @@
1
+ import{a}from"./chunk-OQ46VPYS.js";import"./chunk-KJ3HM2VM.js";export{a as loadConfig};
@@ -0,0 +1 @@
1
+ import{b as a}from"./chunk-FSM3J3WD.js";export{a as DataverseAdvancedClient};
package/dist/doctor.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
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-ZG4OPCGR.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};
2
+ async function f(){let n=e=>process.stdout.write(e+`
3
+ `),s=e=>n(` \u2705 ${e}`),o=e=>n(` \u274C ${e}`);n(""),n("\u{1F3E5} mcp-dataverse doctor"),n("\u2500".repeat(50)),n("");let i=!0;n("\u{1F4CB} Environment");let c=process.version;parseInt(c.slice(1).split(".")[0],10)>=20?s(`Node.js ${c}`):(o(`Node.js ${c} \u2014 requires v20+`),i=!1),n(""),n("\u2699\uFE0F Configuration");try{let{loadConfig:e}=await import("./config.loader-YZJ7QTCV.js"),t=e();s(`Environment URL: ${t.environmentUrl}`),s(`Auth method: ${t.authMethod??"device-code"}`),s(`Timeout: ${t.requestTimeoutMs}ms, Retries: ${t.maxRetries}`)}catch(e){let t=e instanceof Error?e.message:String(e);o(`Configuration error: ${t}`),i=!1,n(""),n(i?"\u2705 All checks passed!":"\u274C Some checks failed."),process.exit(i?0:1)}n(""),n("\u{1F511} Authentication");try{let{loadConfig:e}=await import("./config.loader-YZJ7QTCV.js"),t=e(),r=t.authMethod??"device-code";s(`Auth method: ${r}`),r==="client-credentials"?(s(`Tenant ID : ${t.tenantId}`),s(`Client ID : ${t.clientId}`),t.clientSecret&&!process.env.AZURE_CLIENT_SECRET&&!process.env.clientSecret?n(" \u26A0\uFE0F clientSecret is stored in config file \u2014 prefer AZURE_CLIENT_SECRET env var for production"):s("Client Secret: supplied via env var")):r==="managed-identity"&&(t.managedIdentityClientId?s(`Identity type: user-assigned (clientId: ${t.managedIdentityClientId})`):s("Identity type: system-assigned"));let{createAuthProvider:d}=await import("./auth-provider.factory-IVYKBXVY.js"),l=await d(t).getToken();l?(s("Token acquired successfully"),l.split(".").length===3&&s("Valid JWT token format")):(o("No token returned"),i=!1)}catch(e){let t=e instanceof Error?e.message:String(e);o(`Auth failed: ${t}`),i=!1}n(""),n("\u{1F310} Dataverse API");try{let{loadConfig:e}=await import("./config.loader-YZJ7QTCV.js"),t=e(),{createAuthProvider:r}=await import("./auth-provider.factory-IVYKBXVY.js"),{DataverseAdvancedClient:d}=await import("./dataverse-client-advanced-EASNSX3M.js"),m=r(t),a=await new d(m,t.maxRetries,t.requestTimeoutMs).whoAmI();s(`Organization: ${a.OrganizationName||"N/A"}`),s(`User ID: ${a.UserId||"N/A"}`),s(`Business Unit: ${a.BusinessUnitId||"N/A"}`),s(`Environment: ${a.EnvironmentUrl||t.environmentUrl}`)}catch(e){let t=e instanceof Error?e.message:String(e);o(`API call failed: ${t}`),i=!1}n(""),n("\u2500".repeat(50)),n(i?"\u2705 All checks passed! Your mcp-dataverse setup is healthy.":"\u274C Some checks failed. Review the errors above."),n(""),process.exit(i?0:1)}export{f as runDoctor};
@@ -0,0 +1,2 @@
1
+ import{createRemoteJWKSet as g,jwtVerify as m}from"jose";function J(d){let{tenantId:s,audience:l,requiredScope:r}=d,u=`https://login.microsoftonline.com/${s}/v2.0`,f=new URL(`https://login.microsoftonline.com/${s}/discovery/v2.0/keys`),p=g(f,{cooldownDuration:3e4,cacheMaxAge:10*6e4});return async function(a){let i;try{i=(await m(a,p,{issuer:u,audience:l,algorithms:["RS256"],clockTolerance:30})).payload}catch(n){let o=n instanceof Error?n.message:String(n);try{let c=a.split(".");if(c.length===3){let t=JSON.parse(Buffer.from(c[1],"base64url").toString("utf8"));process.stderr.write(`[entra-jwt] validation failed: ${o} | aud=${JSON.stringify(t.aud)} iss=${t.iss} scp="${t.scp}" ver=${t.ver}
2
+ `)}}catch{}throw new Error(`Entra JWT validation failed: ${o}`)}if(r){let n=i.scp??"";if(!n.split(" ").includes(r))throw new Error(`Required scope '${r}' not present in token scp claim ('${n}')`)}let e=i;return{oid:e.oid??"",upn:e.upn??e.preferred_username??"",name:e.name??""}}}export{J as createEntraJwtValidator};
@@ -1,4 +1,8 @@
1
- import{createServer as S}from"http";import{timingSafeEqual as u,createHmac as m,randomUUID as v}from"crypto";import{StreamableHTTPServerTransport as w}from"@modelcontextprotocol/sdk/server/streamableHttp.js";function g(a,r){try{let i=Buffer.from("mcp-token-verify-constant"),c=m("sha256",i).update(a).digest(),d=m("sha256",i).update(r).digest();return u(c,d)}catch{return!1}}async function O(a,r,i,c){let d=process.env.MCP_HTTP_JSON_RESPONSE!=="false",l=process.env.MCP_HTTP_SECRET??process.env.BEARER_TOKEN,h=process.env.MCP_HTTP_CORS_ORIGIN??"*",n=new Map,p=S(async(s,e)=>{if(e.setHeader("Access-Control-Allow-Origin",h),e.setHeader("Access-Control-Allow-Methods","GET, POST, DELETE, OPTIONS"),e.setHeader("Access-Control-Allow-Headers","Content-Type, mcp-session-id, Authorization"),s.method==="OPTIONS"){e.writeHead(204),e.end();return}let T=new URL(s.url??"/",`http://localhost:${r}`);if(T.pathname==="/health"&&s.method==="GET"){e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify({status:"ok",version:i,tools:c}));return}if(T.pathname==="/mcp"){if(l){let o=s.headers.authorization??"",t=o.startsWith("Bearer ")?o.slice(7):"";if(!g(t,l)){e.writeHead(401,{"Content-Type":"application/json","WWW-Authenticate":'Bearer realm="MCP Dataverse"'}),e.end(JSON.stringify({error:"Unauthorized"}));return}}try{let o=s.headers["mcp-session-id"];if(o){let t=n.get(o);if(!t){e.writeHead(404,{"Content-Type":"application/json"}),e.end(JSON.stringify({error:"Session not found. Please reinitialize."}));return}await t.handleRequest(s,e)}else{if(s.method!=="POST"){e.writeHead(400,{"Content-Type":"application/json"}),e.end(JSON.stringify({error:"mcp-session-id header required for GET and DELETE"}));return}let t=new w({sessionIdGenerator:()=>v(),enableJsonResponse:d});t.sessionId&&(n.set(t.sessionId,t),t.onclose=()=>{t.sessionId&&n.delete(t.sessionId)}),await a().connect(t),await t.handleRequest(s,e),t.sessionId&&(n.has(t.sessionId)||(n.set(t.sessionId,t),t.onclose=()=>{t.sessionId&&n.delete(t.sessionId)}))}}catch(o){process.stderr.write(`[http-server] Unhandled error: ${String(o)}
2
- `),e.headersSent||(e.writeHead(500,{"Content-Type":"application/json"}),e.end(JSON.stringify({error:"Internal server error"})))}return}e.writeHead(404,{"Content-Type":"application/json"}),e.end(JSON.stringify({error:"Not found"}))});await new Promise(s=>{p.listen(r,()=>{process.stderr.write(`MCP Dataverse HTTP server listening on http://localhost:${r}/mcp
3
- `),s()})});let f=async()=>{process.stderr.write(`Shutting down HTTP server...
4
- `);for(let s of n.values())await s.close();p.close(),process.exit(0)};process.on("SIGINT",f),process.on("SIGTERM",f),await new Promise(s=>{p.on("close",s)})}export{O as startHttpTransport};
1
+ import{createServer as N}from"http";import{timingSafeEqual as y,createHmac as S,randomUUID as C}from"crypto";import{StreamableHTTPServerTransport as _}from"@modelcontextprotocol/sdk/server/streamableHttp.js";function A(p,a){try{let d=Buffer.from("mcp-token-verify-constant"),l=S("sha256",d).update(p).digest(),u=S("sha256",d).update(a).digest();return y(l,u)}catch{return!1}}async function U(p,a,d,l){let u=process.env.MCP_HTTP_JSON_RESPONSE!=="false",h=process.env.MCP_HTTP_SECRET??process.env.BEARER_TOKEN,E=process.env.MCP_HTTP_CORS_ORIGIN??"*",s=process.env.ENTRA_TENANT_ID&&process.env.ENTRA_CLIENT_ID?{tenantId:process.env.ENTRA_TENANT_ID,clientId:process.env.ENTRA_CLIENT_ID,audience:process.env.ENTRA_AUDIENCE??`api://${process.env.ENTRA_CLIENT_ID}`,requiredScope:process.env.ENTRA_REQUIRED_SCOPE}:null,T=null;if(s){let{createEntraJwtValidator:n}=await import("./entra-jwt-validator-DABIEBOV.js");T=n(s),process.stderr.write(`[http-server] Auth mode: entra-jwt (tenant=${s.tenantId}, audience=${s.audience})
2
+ `)}else process.stderr.write(h?`[http-server] Auth mode: static bearer token
3
+ `:`[http-server] WARNING: No auth configured \u2014 all /mcp requests accepted
4
+ `);let w=(process.env.MCP_PUBLIC_URL??`http://localhost:${a}`).replace(/\/$/,""),o=new Map,m=N(async(n,e)=>{if(e.setHeader("Access-Control-Allow-Origin",E),e.setHeader("Access-Control-Allow-Methods","GET, POST, DELETE, OPTIONS"),e.setHeader("Access-Control-Allow-Headers","Content-Type, mcp-session-id, Authorization"),n.method==="OPTIONS"){e.writeHead(204),e.end();return}let f=new URL(n.url??"/",`http://localhost:${a}`);if(f.pathname==="/health"&&n.method==="GET"){e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify({status:"ok",version:d,tools:l}));return}if(f.pathname==="/.well-known/oauth-protected-resource"&&n.method==="GET"){let c=s?.tenantId??"";e.writeHead(200,{"Content-Type":"application/json","Cache-Control":"max-age=3600"});let i=s?.clientId??"";e.end(JSON.stringify({resource:`${w}/mcp`,authorization_servers:c?[`https://login.microsoftonline.com/${c}/v2.0`]:[],bearer_methods_supported:["header"],scopes_supported:i?[`api://${i}/DataverseAccess`]:[],resource_documentation:"https://github.com/aileron-split/mcp-dataverse"}));return}if(f.pathname==="/mcp"){let c=n.headers.authorization??"",i=c.startsWith("Bearer ")?c.slice(7):"",g=`${w}/.well-known/oauth-protected-resource`,v=s?`Bearer resource_metadata="${g}", realm="MCP Dataverse"`:'Bearer realm="MCP Dataverse"';if(T){if(!i){e.writeHead(401,{"Content-Type":"application/json","WWW-Authenticate":v}),e.end(JSON.stringify({error:"Unauthorized"}));return}try{await T(i)}catch(r){let t=r instanceof Error?r.message:String(r);process.stderr.write(`[http-server] JWT rejected: ${t}
5
+ `),e.writeHead(401,{"Content-Type":"application/json","WWW-Authenticate":v}),e.end(JSON.stringify({error:"Unauthorized"}));return}}else if(h&&!A(i,h)){e.writeHead(401,{"Content-Type":"application/json","WWW-Authenticate":v}),e.end(JSON.stringify({error:"Unauthorized"}));return}try{let r=n.headers["mcp-session-id"];if(r){let t=o.get(r);if(!t){e.writeHead(404,{"Content-Type":"application/json"}),e.end(JSON.stringify({error:"Session not found. Please reinitialize."}));return}await t.handleRequest(n,e)}else{if(n.method!=="POST"){e.writeHead(400,{"Content-Type":"application/json"}),e.end(JSON.stringify({error:"mcp-session-id header required for GET and DELETE"}));return}let t=new _({sessionIdGenerator:()=>C(),enableJsonResponse:u});t.sessionId&&(o.set(t.sessionId,t),t.onclose=()=>{t.sessionId&&o.delete(t.sessionId)}),await p().connect(t),await t.handleRequest(n,e),t.sessionId&&(o.has(t.sessionId)||(o.set(t.sessionId,t),t.onclose=()=>{t.sessionId&&o.delete(t.sessionId)}))}}catch(r){process.stderr.write(`[http-server] Unhandled error: ${String(r)}
6
+ `),e.headersSent||(e.writeHead(500,{"Content-Type":"application/json"}),e.end(JSON.stringify({error:"Internal server error"})))}return}e.writeHead(404,{"Content-Type":"application/json"}),e.end(JSON.stringify({error:"Not found"}))});await new Promise(n=>{m.listen(a,"0.0.0.0",()=>{process.stderr.write(`MCP Dataverse HTTP server listening on http://0.0.0.0:${a}/mcp
7
+ `),n()})});let I=async()=>{process.stderr.write(`Shutting down HTTP server...
8
+ `);for(let n of o.values())await n.close();m.close(),process.exit(0)};process.on("SIGINT",I),process.on("SIGTERM",I),await new Promise(n=>{m.on("close",n)})}export{U as startHttpTransport};